PendingIntentRecord 更新问题

原创
2021/02/02 14:01
阅读数 401

一、PendingIntent

在Android系统中,Intent经常被用来跨组建和跨进程通信,但是Intent只能被即时使用,为此,Android系统中引入了PendingIntent,它本身并不是Intent,而是用来暂时性缓存Intent,等到相关Action触发,再发送Intent。事实上,PendingIntent也可以通过send方法触发,当然也是可以跨进程的,参考如下代码。

Intent ia = new Intent(MainActivity.this,TestBroadcastReciever.class);
                ia.putExtra("args1","aaaaaa");
PendingIntent pa = PendingIntent.getBroadcast(MainActivity.this,0,ia,PendingIntent.FLAG_UPDATE_CURRENT);
try {
       pa.send();
} catch (PendingIntent.CanceledException e) {
       e.printStackTrace();
}

 

目前为止只提供FLAG_ONE_SHOT, FLAG_NO_CREATE, FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT这四个flag

 FLAG_ONE_SHOT

获取的PendingIntent只能使用一次,即使再次利用上面三个方法重新获取,再使用PendingIntent也将失败。

FLAG_NO_CREATE

从PendingIntentRecord缓存中获取因存在的PendingIntent,若描述的Intent不存在则返回NULL值.

FLAG_CANCEL_CURRENT
如果描述的PendingIntent已经存在,则在使用新的Intent之前会先取消掉当前的。你可以通过FLAG_NO_CREATE去取回,并且PendingIntent的cancel通过取消先前的Intent,更新在Intent中的数据。这能确保对象被给予新的数据。如果无法保证唯一,考虑使用flag_update_current。

 

FLAG_UPDATE_CURRENT

如果描述的Intent存在,想继续持有他但是需要修改部分数据。这就可以被使用如果你创建了一个新的Intent当你只想修改部分数据,而且不想关注他那些以前就存在的Intent会收到新的更新数据甚至不是特别的给它的。

二、PendingIntentRecord

我们从PendingIntent相关flag得知,PendingIntent实际上被缓存在了AMS中,而缓存PendingIntent的类为PendingIntentRecord.java,其中key值包含如下数据

    final static class Key {
        final int type;
        final String packageName;
        final ActivityRecord activity;
        final String who;
        final int requestCode;
        final Intent requestIntent;
        final String requestResolvedType;
        final Bundle options;
        Intent[] allIntents;
        String[] allResolvedTypes;
        final int flags;
        final int hashCode;
        final int userId;

// 次数省略一些代码
}

通过Key值我们才能在AMS中查询到之前设置的PendingIntent。


三、PendingIntentRecord更新问题

通过flag我们发现能获取PendingIntent缓存,那么是如何查找的呢?只能看Key类中的全部代码

    final static class Key {
        final int type;
        final String packageName;
        final ActivityRecord activity;
        final String who;
        final int requestCode;
        final Intent requestIntent;
        final String requestResolvedType;
        final Bundle options;
        Intent[] allIntents;
        String[] allResolvedTypes;
        final int flags;
        final int hashCode;
        final int userId;

        private static final int ODD_PRIME_NUMBER = 37;

        Key(int _t, String _p, ActivityRecord _a, String _w,
                int _r, Intent[] _i, String[] _it, int _f, Bundle _o, int _userId) {
            type = _t;
            packageName = _p;
            activity = _a;
            who = _w;
            requestCode = _r;
            requestIntent = _i != null ? _i[_i.length-1] : null;
            requestResolvedType = _it != null ? _it[_it.length-1] : null;
            allIntents = _i;
            allResolvedTypes = _it;
            flags = _f;
            options = _o;
            userId = _userId;

            int hash = 23;
            hash = (ODD_PRIME_NUMBER*hash) + _f;
            hash = (ODD_PRIME_NUMBER*hash) + _r;
            hash = (ODD_PRIME_NUMBER*hash) + _userId;
            if (_w != null) {
                hash = (ODD_PRIME_NUMBER*hash) + _w.hashCode();
            }
            if (_a != null) {
                hash = (ODD_PRIME_NUMBER*hash) + _a.hashCode();
            }
            if (requestIntent != null) {
                hash = (ODD_PRIME_NUMBER*hash) + requestIntent.filterHashCode();
            }
            if (requestResolvedType != null) {
                hash = (ODD_PRIME_NUMBER*hash) + requestResolvedType.hashCode();
            }
            hash = (ODD_PRIME_NUMBER*hash) + (_p != null ? _p.hashCode() : 0);
            hash = (ODD_PRIME_NUMBER*hash) + _t;
            hashCode = hash;
            //Slog.i(ActivityManagerService.TAG, this + " hashCode=0x"
            //        + Integer.toHexString(hashCode));
        }

        public boolean equals(Object otherObj) {
            if (otherObj == null) {
                return false;
            }
            try {
                Key other = (Key)otherObj;
                if (type != other.type) {
                    return false;
                }
                if (userId != other.userId){
                    return false;
                }
                if (!Objects.equals(packageName, other.packageName)) {
                    return false;
                }
                if (activity != other.activity) {
                    return false;
                }
                if (!Objects.equals(who, other.who)) {
                    return false;
                }
                if (requestCode != other.requestCode) {
                    return false;
                }
                if (requestIntent != other.requestIntent) {
                    if (requestIntent != null) {
                        if (!requestIntent.filterEquals(other.requestIntent)) {
                            return false;
                        }
                    } else if (other.requestIntent != null) {
                        return false;
                    }
                }
                if (!Objects.equals(requestResolvedType, other.requestResolvedType)) {
                    return false;
                }
                if (flags != other.flags) {
                    return false;
                }
                return true;
            } catch (ClassCastException e) {
            }
            return false;
        }

        public int hashCode() {
            return hashCode;
        }

        public String toString() {
            return "Key{" + typeName() + " pkg=" + packageName
                + " intent="
                + (requestIntent != null
                        ? requestIntent.toShortString(false, true, false, false) : "<null>")
                + " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}";
        }

        String typeName() {
            switch (type) {
                case ActivityManager.INTENT_SENDER_ACTIVITY:
                    return "startActivity";
                case ActivityManager.INTENT_SENDER_BROADCAST:
                    return "broadcastIntent";
                case ActivityManager.INTENT_SENDER_SERVICE:
                    return "startService";
                case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE:
                    return "startForegroundService";
                case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT:
                    return "activityResult";
            }
            return Integer.toString(type);
        }
    }

我们重点查看equals方法。

通过比较发现,requestCode和和flag是最容易影响查询结果的参数。

我们还注意到,requestIntent的比较

  public boolean filterEquals(Intent other) {
        if (other == null) {
            return false;
        }
        if (!Objects.equals(this.mAction, other.mAction)) return false;
        if (!Objects.equals(this.mData, other.mData)) return false;
        if (!Objects.equals(this.mType, other.mType)) return false;
        if (!Objects.equals(this.mPackage, other.mPackage)) return false;
        if (!Objects.equals(this.mComponent, other.mComponent)) return false;
        if (!Objects.equals(this.mCategories, other.mCategories)) return false;

        return true;
    }

那么问题来了,如何更新Intent中的数据呢?

 

四、方案

假设我们的PendingIntent如下

Intent ia = new Intent(MainActivity.this,TestBroadcastReciever.class);
ia.putExtra("args1","aaaaaa");
PendingIntent pa =  PendingIntent.getBroadcast(MainActivity.this,0,ia,PendingIntent.FLAG_UPDATE_CURRENT);

 如果Intent只包含一般参数,那么PendingIntent将每次获取到同样的Record,发送给广播的参数同样是 “aaaaaa”

Intent ia = new Intent(MainActivity.this,TestBroadcastReciever.class);
ia.putExtra("args1","aaaaaa");
PendingIntent pa =  PendingIntent.getBroadcast(MainActivity.this,0,ia,PendingIntent.FLAG_UPDATE_CURRENT);
          
Intent ib = new Intent(MainActivity.this,TestBroadcastReciever.class);
ib.putExtra("args1","bbbbbb");
PendingIntent pb =  PendingIntent.getBroadcast(MainActivity.this,0,ib,PendingIntent.FLAG_UPDATE_CURRENT);

Log.d("PendingIntent","result :"+pb.equals(pa));

日志输出结果为true

那么我们想更新参数为bbbbbb或者cccccc如何做呢?

方案一:

查询之后cancel


Intent ib = new Intent(MainActivity.this,TestBroadcastReciever.class);
PendingIntent pb =  PendingIntent.getBroadcast(MainActivity.this,0,ib,PendingIntent.FLAG_NO_CREATE);
if(pb!=null){
    pb.cancel();
}

Intent ic = new Intent(MainActivity.this,TestBroadcastReciever.class);
ic.putExtra("args1","bbbbbb");
PendingIntent pc =  PendingIntent.getBroadcast(MainActivity.this,0,ic,PendingIntent.FLAG_UPDATE_CURRENT);
                Log.d("PendingIntent","result :"+pc.equals(pa));

缺陷:

如果在AlarmManager或者通知中设置了PendingIntent,cancel可能导致通知和alarmManager中的行为失效。

方案二:

构建具备唯一性的PengingIntent,我们可以在Intent的data中加入时间戳或者url等,或者每次变更requestcode,保证使用最新数据

由于Key中以及Intent具备很多可以设置的数据,因此这种方式最简单。

缺陷:这种本质上不属于更新,但具备唯一性,因此通过新老数据版本校验也可以更新最终传递的Intent

 

综上:具体使用哪一种,根据需求决定

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部