使用 PendingIntent 的過程中遇到的問題

需求是這樣的滞项,一個(gè) Android 應(yīng)用要求彈出一個(gè)通知腾供,同時(shí)通知上還有兩個(gè)按鈕澳窑,通知本身點(diǎn)擊和按鈕點(diǎn)擊都對應(yīng)不同的響應(yīng)钠怯,但是在實(shí)現(xiàn)后發(fā)現(xiàn)不管點(diǎn)哪個(gè)按鈕最后都響應(yīng)同一個(gè)事件, 既返回的參數(shù)都是同一個(gè)恋追。

我的實(shí)現(xiàn)方式是這樣的辕翰,BroadcastReceiver 接收到通知后根據(jù) key 作出不同的響應(yīng):

val pendingIntent1 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
            setClass(context, BroadcastReceiver::class.java)
            putExtra(key, value1)
        }, PendingIntent.FLAG_UPDATE_CURRENT)

val pendingIntent2 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
            setClass(context, BroadcastReceiver::class.java)
            putExtra(key, value2)
        }, PendingIntent.FLAG_UPDATE_CURRENT)

val pendingIntent3 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
            setClass(context, BroadcastReceiver::class.java)
            putExtra(key, value3)
        }, PendingIntent.FLAG_UPDATE_CURRENT)

一開始每個(gè)響應(yīng)PendingIntent都是全局變量嘁酿,只實(shí)例化一次璧瞬,我懷疑是因?yàn)楹罄m(xù)更新通知導(dǎo)致宁脊,所以改成每次更改通知時(shí)都新建 一個(gè) PendingIntent,結(jié)果還是不行撕彤。

后來我把每個(gè) PendingIntent 打印出來如下:

PendingIntent{2fd4457: android.os.BinderProxy@2f2cb44}
PendingIntent{5e7742d: android.os.BinderProxy@2f2cb44}
PendingIntent{c05d62: android.os.BinderProxy@2f2cb44}

而PendingIntent對象都toString() 方法是這樣的:

 @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(128);
        sb.append("PendingIntent{");
        sb.append(Integer.toHexString(System.identityHashCode(this)));
        sb.append(": ");
        sb.append(mTarget != null ? mTarget.asBinder() : null);
        sb.append('}');
        return sb.toString();
    }

從上面兩張圖可以得出結(jié)論鱼鸠,雖然 PendingIntent 每次都是一個(gè)新的對象猛拴,但是他們都指向一個(gè) IIntentSender,吶泥蚀狰,why愉昆?

首先, 我們看看 PendingIntent 是如何創(chuàng)建的?
PendingIntent 提供幾個(gè)靜態(tài)方法來實(shí)例化自己:

PendingIntent.getActivity()
PendingIntent.getBroadcast()
PendingIntent.getService()
...

以 getBroadcast() 為例

    public static PendingIntent getBroadcast(Context context, int requestCode,
            Intent intent, @Flags int flags) {
        return getBroadcastAsUser(context, requestCode, intent, flags, context.getUser());
    }

    /**
     * @hide
     * Note that UserHandle.CURRENT will be interpreted at the time the
     * broadcast is sent, not when the pending intent is created.
     */
    public static PendingIntent getBroadcastAsUser(Context context, int requestCode,
            Intent intent, int flags, UserHandle userHandle) {
        String packageName = context.getPackageName();
        String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
                context.getContentResolver()) : null;
        try {
            intent.prepareToLeaveProcess(context);
            IIntentSender target =
                ActivityManager.getService().getIntentSender(
                    ActivityManager.INTENT_SENDER_BROADCAST, packageName,
                    null, null, requestCode, new Intent[] { intent },
                    resolvedType != null ? new String[] { resolvedType } : null,
                    flags, null, userHandle.getIdentifier());
            return target != null ? new PendingIntent(target) : null;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

而上面打印出來的不同的 PendingIntent 對應(yīng)同一個(gè) target 就是上面通過 ActivityManager.getService().getIntentSender(...) 創(chuàng)建的麻蹋,這里 ActivityManager.getService 返回的是 ActivityManagerService跛溉,我們?nèi)タ纯催@個(gè)方法:


    @Override
    public IIntentSender getIntentSender(int type,
            String packageName, IBinder token, String resultWho,
            int requestCode, Intent[] intents, String[] resolvedTypes,
            int flags, Bundle bOptions, int userId) {
        //...刪掉部分不重要的代碼
        synchronized(this) {
            int callingUid = Binder.getCallingUid();
            int origUserId = userId;
            userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
                    type == ActivityManager.INTENT_SENDER_BROADCAST,
                    ALLOW_NON_FULL, "getIntentSender", null);
            if (origUserId == UserHandle.USER_CURRENT) {
                // We don't want to evaluate this until the pending intent is
                // actually executed.  However, we do want to always do the
                // security checking for it above.
                userId = UserHandle.USER_CURRENT;
            }
            try {
                if (callingUid != 0 && callingUid != SYSTEM_UID) {
                    final int uid = AppGlobals.getPackageManager().getPackageUid(packageName,
                            MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(callingUid));
                    if (!UserHandle.isSameApp(callingUid, uid)) {
                        String msg = "Permission Denial: getIntentSender() from pid="
                            + Binder.getCallingPid()
                            + ", uid=" + Binder.getCallingUid()
                            + ", (need uid=" + uid + ")"
                            + " is not allowed to send as package " + packageName;
                        Slog.w(TAG, msg);
                        throw new SecurityException(msg);
                    }
                }
                // ... 最終會(huì)調(diào)用和返回這個(gè)方法, 繼續(xù)往下看看
                return getIntentSenderLocked(type, packageName, callingUid, userId,
                        token, resultWho, requestCode, intents, resolvedTypes, flags, bOptions);

            } catch (RemoteException e) {
                throw new SecurityException(e);
            }
        }
    }

    IIntentSender getIntentSenderLocked(int type, String packageName,
            int callingUid, int userId, IBinder token, String resultWho,
            int requestCode, Intent[] intents, String[] resolvedTypes, int flags,
            Bundle bOptions) {
         // 刪掉部分不重要的代碼
        final boolean noCreate = (flags&PendingIntent.FLAG_NO_CREATE) != 0;
        final boolean cancelCurrent = (flags&PendingIntent.FLAG_CANCEL_CURRENT) != 0;
        final boolean updateCurrent = (flags&PendingIntent.FLAG_UPDATE_CURRENT) != 0;
        flags &= ~(PendingIntent.FLAG_NO_CREATE|PendingIntent.FLAG_CANCEL_CURRENT
                |PendingIntent.FLAG_UPDATE_CURRENT);
        
// 這里根據(jù)傳經(jīng)來的一些參數(shù)構(gòu)建來一個(gè) key, 這個(gè)里很關(guān)鍵
        PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, activity,
                resultWho, requestCode, intents, resolvedTypes, flags,
                SafeActivityOptions.fromBundle(bOptions), userId);
        WeakReference<PendingIntentRecord> ref;
        ref = mIntentSenderRecords.get(key);
        PendingIntentRecord rec = ref != null ? ref.get() : null;
        if (rec != null) {
            if (!cancelCurrent) {
                if (updateCurrent) {
                    if (rec.key.requestIntent != null) {
                        rec.key.requestIntent.replaceExtras(intents != null ?
                                intents[intents.length - 1] : null);
                    }
                    if (intents != null) {
                        intents[intents.length-1] = rec.key.requestIntent;
                        rec.key.allIntents = intents;
                        rec.key.allResolvedTypes = resolvedTypes;
                    } else {
                        rec.key.allIntents = null;
                        rec.key.allResolvedTypes = null;
                    }
                }
                return rec;
            }
            makeIntentSenderCanceledLocked(rec);
            mIntentSenderRecords.remove(key);
        }
        if (noCreate) {
            return rec;
        }
        rec = new PendingIntentRecord(this, key, callingUid);
        mIntentSenderRecords.put(key, rec.ref);
        if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
            if (activity.pendingResults == null) {
                activity.pendingResults
                        = new HashSet<WeakReference<PendingIntentRecord>>();
            }
            activity.pendingResults.add(rec.ref);
        }
        return rec;
    }

上面的代碼中可以看到最終返回的是PendingIntentRecord對象(既target = PendingIntentRecord),它是 IIntentSender.IIntentSender 的子類扮授,調(diào)用者傳入的參數(shù)會(huì)構(gòu)建成一個(gè)對象 PendingIntentRecord.Key芳室,而 mIntentSenderRecords 則是一個(gè) HashMap 緩存所有的 PendingIntentRecord。

上面用新建的 key 去 mIntentSenderRecords 查詢是否已經(jīng)存在對應(yīng)的 PendingIntentRecord刹勃,如存在則直接返回堪侯,否則新建一個(gè) PendingIntentRecord 塞入 mIntentSenderRecords 并返回。

到這里稍微有一點(diǎn)豁然開朗荔仁,但是別急伍宦,還有一點(diǎn)點(diǎn)迷霧等待我們?nèi)荛_。

既然 HashMap 覺得 PendingIntentRecord 已經(jīng)存在乏梁,那肯定是 PendingIntentRecord.Key 存在雹拄,那我們看看這個(gè)對象,它是 PendingIntentRecord 的內(nèi)部類:

    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 SafeActivityOptions 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, SafeActivityOptions _o, int _userId) {
       //  刪掉部分無用代碼
            requestIntent = _i != null ? _i[_i.length-1] : null; // 最后一個(gè)Intent
            requestResolvedType = _it != null ? _it[_it.length-1] : null;
       // 著重看一下 hash 生成的部分
            int hash = 23;
            // _f 是在 PendingIntent.getBroadcast 時(shí)傳入的 flags
            hash = (ODD_PRIME_NUMBER*hash) + _f;
            // _r 是在 PendingIntent.getBroadcast 時(shí)傳入的 requestCode
            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();
            }
            // requestIntent 就是你傳入的 Intent 數(shù)組里的最后一個(gè) Intent,
            // 一般使用的時(shí)候只有一個(gè) Intent
            // 這里為里生成 hashcode, 調(diào)用里 Intent.filterHashCode()
            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;
        }

        public int hashCode() {
            return hashCode;
        }
    }

接著看一下 Intent.filterHashCode 的生成算法

    public int filterHashCode() {
        int code = 0;
        if (mAction != null) {
            code += mAction.hashCode();
        }
        if (mData != null) {
            code += mData.hashCode();
        }
        if (mType != null) {
            code += mType.hashCode();
        }
        if (mPackage != null) {
            code += mPackage.hashCode();
        }
        if (mComponent != null) {
            code += mComponent.hashCode();
        }
        if (mCategories != null) {
            code += mCategories.hashCode();
        }
        return code;
    }

那我們捋一下 PendingIntentRecord.key 的 hashcode 和我們傳入的哪些參數(shù)有關(guān)系:
記得我們上面是如何創(chuàng)建 PendingIntent 的嗎掌呜?

val pendingIntent1 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
            setClass(context, BroadcastReceiver::class.java)
            putExtra(key, value1)
        }, PendingIntent.FLAG_UPDATE_CURRENT)

我們傳入的參數(shù)是:
context, requestCode, Intent, flags
而 Intent 又包含哪些參數(shù)呢?

    private String mAction;
    private Uri mData;
    private String mType;
    private String mPackage;
    private ComponentName mComponent;
    private int mFlags;
    private ArraySet<String> mCategories;
    private Bundle mExtras;
    private Rect mSourceBounds;
    private Intent mSelector;
    private ClipData mClipData;
    private int mContentUserHint = UserHandle.USER_CURRENT;
    private String mLaunchToken;

大概是上面這些, 其中參與了生成 hashcode 就是上面提到的 filterHashCode 這個(gè)方法里的參數(shù) 所以總共參與生成 hashcode 的和調(diào)用中有關(guān)的參數(shù)是:

context, requestCode, flags, Intent.action, Intent.data, Intent.package, Intent.component, Intent.categories

再回過頭來看我是如何創(chuàng)建 PendingIntent 的

val pendingIntent1 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
            setClass(context, BroadcastReceiver::class.java)
            putExtra(key, value1)
        }, PendingIntent.FLAG_UPDATE_CURRENT)

val pendingIntent2 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
            setClass(context, BroadcastReceiver::class.java)
            putExtra(key, value2)
        }, PendingIntent.FLAG_UPDATE_CURRENT)

val pendingIntent3 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
            setClass(context, BroadcastReceiver::class.java)
            putExtra(key, value3)
        }, PendingIntent.FLAG_UPDATE_CURRENT)

?? 唯一不同的是 Intent.extras坪哄,卻沒有參與 hashcode 計(jì)算质蕉,不知道是 google 故意為止還是一個(gè)小 bug ??。

總結(jié):

  • PendingIntent 中 的 target 是 PendingIntentRecord翩肌,它們都被緩存到 ActivityManagerService 中的一個(gè)HashMap中模暗,是弱引用類型;
  • 影響 PendingIntent 復(fù)用的參數(shù)主要是: requestCode, flags, Intent.action, Intent.data, Intent.package, Intent.component, Intent.categories
    所以在我的做法基礎(chǔ)上念祭,只要修改一下 requestCode 為不同的值就解決了兑宇。

參考:說說PendingIntent的內(nèi)部機(jī)制

完!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粱坤,一起剝皮案震驚了整個(gè)濱河市隶糕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌站玄,老刑警劉巖枚驻,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異株旷,居然都是意外死亡再登,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锉矢,“玉大人梯嗽,你說我怎么就攤上這事」了穑” “怎么了灯节?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缠俺。 經(jīng)常有香客問我显晶,道長,這世上最難降的妖魔是什么壹士? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任磷雇,我火速辦了婚禮,結(jié)果婚禮上躏救,老公的妹妹穿的比我還像新娘唯笙。我一直安慰自己,他們只是感情好盒使,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布崩掘。 她就那樣靜靜地躺著,像睡著了一般少办。 火紅的嫁衣襯著肌膚如雪苞慢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天英妓,我揣著相機(jī)與錄音挽放,去河邊找鬼。 笑死蔓纠,一個(gè)胖子當(dāng)著我的面吹牛辑畦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腿倚,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼纯出,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了敷燎?” 一聲冷哼從身側(cè)響起暂筝,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎硬贯,沒想到半個(gè)月后乖杠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澄成,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年胧洒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了畏吓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卫漫,死狀恐怖菲饼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情列赎,我是刑警寧澤宏悦,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站包吝,受9級特大地震影響饼煞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诗越,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一砖瞧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嚷狞,春花似錦块促、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至薇搁,卻和暖如春斋扰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背啃洋。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工褥实, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人裂允。 一個(gè)月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像哥艇,于是被迫代替她去往敵國和親绝编。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容