PendingIntentRecord 更新問題

一、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

 

綜上:具體使用哪一種,根據需求決定

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章