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

本文由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):

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

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

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

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

  1. 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é)

  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)容合作請聯(lián)系作者
  • 序言:七十年代末脂信,一起剝皮案震驚了整個(gè)濱河市癣蟋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狰闪,老刑警劉巖疯搅,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異埋泵,居然都是意外死亡幔欧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門丽声,熙熙樓的掌柜王于貴愁眉苦臉地迎上來礁蔗,“玉大人,你說我怎么就攤上這事雁社≡【” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵霉撵,是天一觀的道長磺浙。 經(jīng)常有香客問我,道長徒坡,這世上最難降的妖魔是什么撕氧? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮喇完,結(jié)果婚禮上伦泥,老公的妹妹穿的比我還像新娘。我一直安慰自己何暮,他們只是感情好奄喂,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著海洼,像睡著了一般跨新。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坏逢,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天域帐,我揣著相機(jī)與錄音,去河邊找鬼是整。 笑死肖揣,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的浮入。 我是一名探鬼主播龙优,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼事秀!你這毒婦竟也來了彤断?” 一聲冷哼從身側(cè)響起野舶,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宰衙,沒想到半個(gè)月后平道,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡供炼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年一屋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袋哼。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冀墨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涛贯,到底是詐尸還是另有隱情轧苫,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布疫蔓,位于F島的核電站,受9級特大地震影響身冬,放射性物質(zhì)發(fā)生泄漏衅胀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一酥筝、第九天 我趴在偏房一處隱蔽的房頂上張望滚躯。 院中可真熱鬧,春花似錦嘿歌、人聲如沸掸掏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丧凤。三九已至,卻和暖如春步脓,著一層夾襖步出監(jiān)牢的瞬間愿待,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工靴患, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留仍侥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓鸳君,卻偏偏與公主長得像农渊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子或颊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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