徹底搞懂startActivityForResult在FragmentActivity和Fragment中的異同

本文由BarryZhang原創(chuàng),同時(shí)首發(fā)于diycode.cc、barryzhang.com,簡(jiǎn)書(shū)非商業(yè)轉(zhuǎn)載請(qǐng)注明作者和原文鏈接。

徹底搞懂startActivityForResult在FragmentActivity和Fragment中的異同

1. 前言

Activity、FragmentActivity、Fragment中都有startActivityForResult()方法,也都有用以接收結(jié)果的onActivityResult()方法,那他們有什么區(qū)別嗎?用法上有什么不同嗎?

之所以注意到這個(gè)問(wèn)題,是因?yàn)樽罱淮卧贔ragment中使用了getActivity().startActivityForResult()去調(diào)用圖片選擇器,結(jié)果發(fā)現(xiàn)在Fragment的onActivityResult無(wú)法接收到返回的結(jié)果。

仔細(xì)研究了一下原因,發(fā)現(xiàn)了一些以前沒(méi)注意到的問(wèn)題,于是寫(xiě)出來(lái)分享給大家。

2. 表現(xiàn)

假設(shè)有一個(gè)FragmentActivity中嵌套一個(gè)Fragment,它們各自使用startActivityForResult發(fā)起數(shù)據(jù)請(qǐng)求。
經(jīng)測(cè),目標(biāo)所返回結(jié)果數(shù)據(jù),能否被它們各自的onActivityResult方法所接收的情況如下:

對(duì)比圖
  • Fragment和FragmentActivity都能接收到自己的發(fā)起的請(qǐng)求所返回的結(jié)果
  • FragmentActivity發(fā)起的請(qǐng)求,F(xiàn)ragment完全接收不到結(jié)果
  • Fragment發(fā)起的請(qǐng)求,雖然在FragmentActivity中能獲取到結(jié)果,但是requestCode完全對(duì)應(yīng)不上

為什么會(huì)有這種表現(xiàn)呢?往下看。

3. 找原因:Show me your code !

仔細(xì)看文檔的話,發(fā)現(xiàn)了一個(gè)以前沒(méi)注意到的點(diǎn):FragmentActivity相對(duì)于它的父類Activity,對(duì)startActivityForResult的描述是有些改動(dòng)的。

FragmentActivity.startActivityForResult的文檔是這樣的:

修改了標(biāo)準(zhǔn)行為,以使它能夠把結(jié)果傳遞到Fragment。
添加了一個(gè)限制:requestCode必須<=0xffff

這里的標(biāo)準(zhǔn)行為,自然指的是正常的Activity.startActivityForResult的功能。而新增加的對(duì)requestCode的大小限制看起來(lái)很蹊蹺,估計(jì)是有什么貓膩在里面了。

OK,不賣關(guān)子,直接看源碼!

3.1 Fragment.startActivityForResult

從Fragment的startActivityForResult開(kāi)始:

    public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
        if (mHost == null) {
            throw new IllegalStateException("Fragment " + this + " not attached to Activity");
        }
        mHost.onStartActivityFromFragment(this /*fragment*/, intent, requestCode, options);
    }

Fragment.startActivityForResult本身的代碼很簡(jiǎn)單,就是調(diào)用了一個(gè)mHost.onStartActivityFromFragment的方法。
—— Fragment被添加到一個(gè)FragmentActivity中之后,這里的mHost即是當(dāng)前FragmentActivity的一個(gè)內(nèi)部類FragmentActivity.HostCallbacks,它持有對(duì)FragmentActivity的引用,mHost.onStartActivityFromFragment被簡(jiǎn)單轉(zhuǎn)發(fā)到當(dāng)前FragmentActivity的
startActivityFromFragment()方法。

Fragment.startActivityForResult

FragmentActivitymHost.HostCallbacks.onStartActivityFromFragment

FragmentActivity.startActivityFromFragment

接下來(lái)到FragmentActivity.startActivityFromFragment:

3.2 FragmentActivity.startActivityFromFragment

public void startActivityFromFragment(Fragment fragment, Intent intent,
        int requestCode, @Nullable Bundle options) {
    mStartedActivityFromFragment = true;
    try {
        if (requestCode == -1) {
            ActivityCompat.startActivityForResult(this, intent, -1, options);
            return;
        }
        if ((requestCode&0xffff0000) != 0) {
            throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
        }
        int requestIndex = allocateRequestIndex(fragment);
        ActivityCompat.startActivityForResult(
            this, intent, ((requestIndex+1)<<16) + (requestCode&0xffff), options);
    } finally {
        mStartedActivityFromFragment = false;
    }
}

分析一下這段代碼:
1,mStartedActivityFromFragment = true首先標(biāo)記一下請(qǐng)求是來(lái)自于Fragment。
2,if(requestCode == 1)的內(nèi)容不用管,它是來(lái)自于startActivity(沒(méi)有ForResult)的情況。
3,然后的代碼添加了對(duì)requestCode必須小于0xffff的限制 if((requestCode&0xffff0000) != 0){/*拋異常*/}
我們是從Fragment.startActivityForResult追蹤到這里的,所以雖然文檔沒(méi)有明確說(shuō),但是從這里可以看出:Fragment.startActivityForResult的requestCode也是必須要<=0xffff的。

然后,下面是關(guān)鍵點(diǎn)了:

ActivityCompat.startActivityForResult(
            this, intent, ((requestIndex+1)<<16) + (requestCode&0xffff), options);

——其中ActivityCompat是一個(gè)幫助類,ActivityCompat.startActivityForResult最終還是調(diào)用的Activity.startActivityForResult,這個(gè)先不表。
這里的關(guān)鍵點(diǎn)就是,通過(guò)一個(gè)requestCode=>((requestIndex+1)<<16)+(requestCode&0xffff)的映射,F(xiàn)ragment.startActivityForResult最終還是調(diào)用了Activity.startActivityForResult。

調(diào)用了Activity.startActivityForResult其實(shí)是意料之中的事情,只是從requestCode((requestIndex+1)<<16)+(requestCode&0xffff)是做了什么呢?

通過(guò)分析,得知requestIndex是請(qǐng)求的序號(hào),值為從0遞增的整數(shù)值。
又從前面得知,requestCode的本身的值是小于0xffff的,所以((requestIndex+1)<<16)+(requestCode&0xffff)簡(jiǎn)化一下就是:(requestIndex+1)*65536+requestCode。
——所以這個(gè)值是必定大于0xffff的。

在看一下FragmentActivity.startActivityForResult的代碼:

3.3 FragmentActivity.startActivityForResult

@Override
public void startActivityForResult(Intent intent, int requestCode) {
    // If this was started from a Fragment we've already checked the upper 16 bits were not in
    // use, and then repurposed them for the Fragment's index.
    if (!mStartedActivityFromFragment) {
        if (requestCode != -1 && (requestCode&0xffff0000) != 0) {
            throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
        }
    }
    super.startActivityForResult(intent, requestCode);
}

可以看到,判斷了一下如果請(qǐng)求不是來(lái)自于Fragment,也就是來(lái)自于FragmentActivity自身,就限制requestCode不能大于0xffff。

再加上前文所說(shuō)的,F(xiàn)ragment.startActivityForResult最終映射的requestCode值必定大于0xffff,所以,現(xiàn)在可以得出了一個(gè)初步的結(jié)果:
SDK把Fragment和FragmentActivity的的ruquestCode都限制在了0xffff以內(nèi),然后對(duì)于Fragment所發(fā)起的請(qǐng)求,都通過(guò)一個(gè)映射,把最終的requestCode變成了一個(gè)大于0xffff的值。

——到現(xiàn)在,已經(jīng)可以推測(cè)到:在獲取的結(jié)果的時(shí)候,也是會(huì)通過(guò)跟0xffff這個(gè)數(shù)值來(lái)比較,來(lái)區(qū)分是要把結(jié)果交給FragmentActivity還是Fragment來(lái)處理。

來(lái)驗(yàn)證一下看看:

3.4 FragmentActivity.onActivityResult

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    mFragments.noteStateNotSaved();
    int requestIndex = requestCode>>16;
    if (requestIndex != 0) {
        requestIndex--;

        String who = mPendingFragmentActivityResults.get(requestIndex);
        mPendingFragmentActivityResults.remove(requestIndex);
        if (who == null) {
            Log.w(TAG, "Activity result delivered for unknown Fragment.");
            return;
        }
        Fragment targetFragment = mFragments.findFragmentByWho(who);
        if (targetFragment == null) {
            Log.w(TAG, "Activity result no fragment exists for who: " + who);
        } else {
            targetFragment.onActivityResult(requestCode&0xffff, resultCode, data);
        }
        return;
    }

    super.onActivityResult(requestCode, resultCode, data);
}

OK,一目了然,證實(shí)了我們上面的推論。
在FragmentActivity.onActivityResult中,只有requestCode>0xffff時(shí),這里得到的requestIndex才能滿足requestIndex != 0,然后進(jìn)入下面的邏輯:把requestCode通過(guò)反向之前的映射關(guān)系,還原成最初Fragment所指定的requestCode,交給Fragment.onActivityResult進(jìn)行處理。

4. 解釋最初的問(wèn)題

所以,現(xiàn)在也能明白了為什么會(huì)有前面說(shuō)的這幾個(gè)表現(xiàn):

  1. Fragment和FragmentActivity都能接收到自己的發(fā)起的請(qǐng)求所返回的結(jié)果

那當(dāng)然,就是這么設(shè)計(jì)的。

  1. FragmentActivity發(fā)起的請(qǐng)求,F(xiàn)ragment完全接收不到結(jié)果

被FragmentActivity攔截了,沒(méi)有轉(zhuǎn)發(fā)到Fragment。

  1. Fragment發(fā)起的請(qǐng)求,雖然在FragmentActivity中能獲取到結(jié)果,但是requestCode完全對(duì)應(yīng)不上

如果是Fragment發(fā)起的請(qǐng)求,那么在FragmentActivity.onActivityResult獲取到的requestCode,其實(shí)是經(jīng)過(guò)映射之后一個(gè)的大于0xffff的值,已經(jīng)不是最初Fragment發(fā)請(qǐng)求時(shí)的requestCode了。

5. 思考

為什么要用映射requestCode的方法來(lái)區(qū)分請(qǐng)求是否來(lái)自Fragment呢?繞這么一個(gè)彎子,直接使用一個(gè)變量來(lái)標(biāo)記不行么?

直接使用一個(gè)變量來(lái)標(biāo)記還真不行:

  • 因?yàn)槲覀冏约鹤罱K寫(xiě)業(yè)務(wù)代碼MyFragmentActivity肯定是繼承自FragmentActivity的,而MyFragmentActivity.onActivityResult的調(diào)用會(huì)先于FragmentActivity.onActivityResult。
  • 所以無(wú)論是Fragment還是MyFragmentActivity所發(fā)起的startActivityForResult請(qǐng)求,最終在獲取結(jié)果的時(shí)候是一定是會(huì)通過(guò)MyFragmentActivity.onActivityResult的。
  • 如果在這里使用一個(gè)變量來(lái)標(biāo)記請(qǐng)求的來(lái)源,那實(shí)質(zhì)上就是依賴于開(kāi)發(fā)者自己來(lái)判斷——這是繁瑣而且不可控的。
  • 而相比較而言,使用一個(gè)簡(jiǎn)單的映射規(guī)則,就能把來(lái)自Fragment的請(qǐng)求和來(lái)自FragmentActivity自身請(qǐng)求區(qū)分開(kāi)來(lái)——十分簡(jiǎn)單可靠。

6. 總結(jié)

  1. 使用startActivityForResult的時(shí)候,requestCode一定不要大于0xffff(65535)。
  2. 如果希望在Fragment的onActivityResult接收數(shù)據(jù),就要調(diào)用Fragment.startActivityForResult,而不是Fragment.getActivity().startActivityForResult。
  3. 看源碼果然是學(xué)習(xí)的好方法~
  4. Google的工程師果然牛逼。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容