本文由BarryZhang原創(chuàng),同時(shí)首發(fā)于diycode.cc流礁、barryzhang.com,簡書非商業(yè)轉(zhuǎn)載請注明作者和原文鏈接。
徹底搞懂startActivityForResult在FragmentActivity和Fragment中的異同
1. 前言
Activity、FragmentActivity踱承、Fragment中都有startActivityForResult()
方法倡缠,也都有用以接收結(jié)果的onActivityResult()
方法,那他們有什么區(qū)別嗎茎活?用法上有什么不同嗎?
之所以注意到這個(gè)問題琢唾,是因?yàn)樽罱淮卧贔ragment中使用了getActivity().startActivityForResult()去調(diào)用圖片選擇器载荔,結(jié)果發(fā)現(xiàn)在Fragment的onActivityResult無法接收到返回的結(jié)果。
仔細(xì)研究了一下原因采桃,發(fā)現(xiàn)了一些以前沒注意到的問題懒熙,于是寫出來分享給大家。
2. 表現(xiàn)
假設(shè)有一個(gè)FragmentActivity中嵌套一個(gè)Fragment普办,它們各自使用startActivityForResult發(fā)起數(shù)據(jù)請求工扎。
經(jīng)測,目標(biāo)所返回結(jié)果數(shù)據(jù)衔蹲,能否被它們各自的onActivityResult方法所接收的情況如下:
- Fragment和FragmentActivity都能接收到自己的發(fā)起的請求所返回的結(jié)果
- FragmentActivity發(fā)起的請求肢娘,F(xiàn)ragment完全接收不到結(jié)果
- Fragment發(fā)起的請求,雖然在FragmentActivity中能獲取到結(jié)果舆驶,但是requestCode完全對應(yīng)不上
為什么會有這種表現(xiàn)呢橱健?往下看。
3. 找原因:Show me your code !
仔細(xì)看文檔的話沙廉,發(fā)現(xiàn)了一個(gè)以前沒注意到的點(diǎn):FragmentActivity相對于它的父類Activity拘荡,對startActivityForResult的描述是有些改動的。
FragmentActivity.startActivityForResult的文檔是這樣的:
修改了標(biāo)準(zhǔn)行為撬陵,以使它能夠把結(jié)果傳遞到Fragment珊皿。
添加了一個(gè)限制:requestCode必須<=0xffff
這里的標(biāo)準(zhǔn)行為
,自然指的是正常的Activity.startActivityForResult的功能巨税。而新增加的對requestCode的大小限制看起來很蹊蹺蟋定,估計(jì)是有什么貓膩在里面了。
OK垢夹,不賣關(guān)子溢吻,直接看源碼!
3.1 Fragment.startActivityForResult
從Fragment的startActivityForResult開始:
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本身的代碼很簡單果元,就是調(diào)用了一個(gè)mHost.onStartActivityFromFragment的方法促王。
—— Fragment被添加到一個(gè)FragmentActivity中之后,這里的mHost即是當(dāng)前FragmentActivity的一個(gè)內(nèi)部類FragmentActivity.HostCallbacks而晒,它持有對FragmentActivity的引用蝇狼,mHost.onStartActivityFromFragment被簡單轉(zhuǎn)發(fā)到當(dāng)前FragmentActivity的
startActivityFromFragment()方法。
Fragment.startActivityForResult
↓
FragmentActivitymHost.HostCallbacks.onStartActivityFromFragment
↓
FragmentActivity.startActivityFromFragment
接下來到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)記一下請求是來自于Fragment迅耘。
2贱枣,if(requestCode == 1)
的內(nèi)容不用管,它是來自于startActivity(沒有ForResult)的情況颤专。
3纽哥,然后的代碼添加了對requestCode必須小于0xffff的限制 if((requestCode&0xffff0000) != 0){/*拋異常*/}
我們是從Fragment.startActivityForResult追蹤到這里的栖秕,所以雖然文檔沒有明確說春塌,但是從這里可以看出: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)就是吼句,通過一個(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)
是做了什么呢?
通過分析分蓖,得知requestIndex是請求的序號尔艇,值為從0遞增的整數(shù)值。
又從前面得知么鹤,requestCode的本身的值是小于0xffff的终娃,所以((requestIndex+1)<<16)+(requestCode&0xffff)簡化一下就是:(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);
}
可以看到棠耕,判斷了一下如果請求不是來自于Fragment,也就是來自于FragmentActivity自身柠新,就限制requestCode不能大于0xffff窍荧。
再加上前文所說的,F(xiàn)ragment.startActivityForResult最終映射的requestCode值必定大于0xffff恨憎,所以蕊退,現(xiàn)在可以得出了一個(gè)初步的結(jié)果:
SDK把Fragment和FragmentActivity的的ruquestCode都限制在了0xffff以內(nèi),然后對于Fragment所發(fā)起的請求憔恳,都通過一個(gè)映射瓤荔,把最終的requestCode變成了一個(gè)大于0xffff的值。
——到現(xiàn)在钥组,已經(jīng)可以推測到:在獲取的結(jié)果的時(shí)候输硝,也是會通過跟0xffff這個(gè)數(shù)值來比較,來區(qū)分是要把結(jié)果交給FragmentActivity還是Fragment來處理程梦。
來驗(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ān)系褒翰,還原成最初Fragment所指定的requestCode如蚜,交給Fragment.onActivityResult進(jìn)行處理。
4. 解釋最初的問題
所以影暴,現(xiàn)在也能明白了為什么會有前面說的這幾個(gè)表現(xiàn):
- Fragment和FragmentActivity都能接收到自己的發(fā)起的請求所返回的結(jié)果
那當(dāng)然,就是這么設(shè)計(jì)的探赫。
- FragmentActivity發(fā)起的請求型宙,F(xiàn)ragment完全接收不到結(jié)果
被FragmentActivity攔截了,沒有轉(zhuǎn)發(fā)到Fragment伦吠。
- Fragment發(fā)起的請求妆兑,雖然在FragmentActivity中能獲取到結(jié)果,但是requestCode完全對應(yīng)不上
如果是Fragment發(fā)起的請求毛仪,那么在FragmentActivity.onActivityResult獲取到的requestCode搁嗓,其實(shí)是經(jīng)過映射之后一個(gè)的大于0xffff的值,已經(jīng)不是最初Fragment發(fā)請求時(shí)的requestCode了箱靴。
5. 思考
為什么要用映射requestCode的方法來區(qū)分請求是否來自Fragment呢腺逛?繞這么一個(gè)彎子,直接使用一個(gè)變量來標(biāo)記不行么衡怀?
直接使用一個(gè)變量來標(biāo)記還真不行:
- 因?yàn)槲覀冏约鹤罱K寫業(yè)務(wù)代碼MyFragmentActivity肯定是繼承自FragmentActivity的棍矛,而MyFragmentActivity.onActivityResult的調(diào)用會先于FragmentActivity.onActivityResult。
- 所以無論是Fragment還是MyFragmentActivity所發(fā)起的startActivityForResult請求抛杨,最終在獲取結(jié)果的時(shí)候是一定是會通過MyFragmentActivity.onActivityResult的够委。
- 如果在這里使用一個(gè)變量來標(biāo)記請求的來源,那實(shí)質(zhì)上就是依賴于開發(fā)者自己來判斷——這是繁瑣而且不可控的怖现。
- 而相比較而言茁帽,使用一個(gè)簡單的映射規(guī)則,就能把來自Fragment的請求和來自FragmentActivity自身請求區(qū)分開來——十分簡單可靠屈嗤。
6. 總結(jié)
- 使用startActivityForResult的時(shí)候潘拨,requestCode一定不要大于0xffff(65535)。
- 如果希望在Fragment的onActivityResult接收數(shù)據(jù)恢共,就要調(diào)用Fragment.startActivityForResult战秋,而不是Fragment.getActivity().startActivityForResult。
- 看源碼果然是學(xué)習(xí)的好方法~
- Google的工程師果然牛逼讨韭。