本文由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方法所接收的情況如下:

- 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):
- Fragment和FragmentActivity都能接收到自己的發(fā)起的請(qǐng)求所返回的結(jié)果
那當(dāng)然,就是這么設(shè)計(jì)的。
- FragmentActivity發(fā)起的請(qǐng)求,F(xiàn)ragment完全接收不到結(jié)果
被FragmentActivity攔截了,沒(méi)有轉(zhuǎn)發(fā)到Fragment。
- 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é)
- 使用startActivityForResult的時(shí)候,requestCode一定不要大于0xffff(65535)。
- 如果希望在Fragment的onActivityResult接收數(shù)據(jù),就要調(diào)用Fragment.startActivityForResult,而不是Fragment.getActivity().startActivityForResult。
- 看源碼果然是學(xué)習(xí)的好方法~
- Google的工程師果然牛逼。