彻底搞懂startActivityForResult在FragmentActivity和Fragment中的异同

本文由BarryZhang原创,同时首发于diycode.ccbarryzhang.com简书非商业转载请注明作者和原文链接。

彻底搞懂startActivityForResult在FragmentActivity和Fragment中的异同

1. 前言

Activity、FragmentActivity、Fragment中都有startActivityForResult()方法,也都有用以接收结果的onActivityResult()方法,那他们有什么区别吗?用法上有什么不同吗?

之所以注意到这个问题,是因为最近一次在Fragment中使用了getActivity().startActivityForResult()去调用图片选择器,结果发现在Fragment的onActivityResult无法接收到返回的结果。

仔细研究了一下原因,发现了一些以前没注意到的问题,于是写出来分享给大家。

2. 表现

假设有一个FragmentActivity中嵌套一个Fragment,它们各自使用startActivityForResult发起数据请求。
经测,目标所返回结果数据,能否被它们各自的onActivityResult方法所接收的情况如下:

对比图
  • Fragment和FragmentActivity都能接收到自己的发起的请求所返回的结果
  • FragmentActivity发起的请求,Fragment完全接收不到结果
  • Fragment发起的请求,虽然在FragmentActivity中能获取到结果,但是requestCode完全对应不上

为什么会有这种表现呢?往下看。

3. 找原因:Show me your code !

仔细看文档的话,发现了一个以前没注意到的点:FragmentActivity相对于它的父类Activity,对startActivityForResult的描述是有些改动的。

FragmentActivity.startActivityForResult的文档是这样的:

修改了标准行为,以使它能够把结果传递到Fragment。
添加了一个限制:requestCode必须<=0xffff

这里的标准行为,自然指的是正常的Activity.startActivityForResult的功能。而新增加的对requestCode的大小限制看起来很蹊跷,估计是有什么猫腻在里面了。

OK,不卖关子,直接看源码!

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本身的代码很简单,就是调用了一个mHost.onStartActivityFromFragment的方法。
—— Fragment被添加到一个FragmentActivity中之后,这里的mHost即是当前FragmentActivity的一个内部类FragmentActivity.HostCallbacks,它持有对FragmentActivity的引用,mHost.onStartActivityFromFragment被简单转发到当前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首先标记一下请求是来自于Fragment。
2,if(requestCode == 1)的内容不用管,它是来自于startActivity(没有ForResult)的情况。
3,然后的代码添加了对requestCode必须小于0xffff的限制 if((requestCode&0xffff0000) != 0){/*抛异常*/}
我们是从Fragment.startActivityForResult追踪到这里的,所以虽然文档没有明确说,但是从这里可以看出:Fragment.startActivityForResult的requestCode也是必须要<=0xffff的。

然后,下面是关键点了:

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

——其中ActivityCompat是一个帮助类,ActivityCompat.startActivityForResult最终还是调用的Activity.startActivityForResult,这个先不表。
这里的关键点就是,通过一个requestCode=>((requestIndex+1)<<16)+(requestCode&0xffff)的映射,Fragment.startActivityForResult最终还是调用了Activity.startActivityForResult。

调用了Activity.startActivityForResult其实是意料之中的事情,只是从requestCode((requestIndex+1)<<16)+(requestCode&0xffff)是做了什么呢?

通过分析,得知requestIndex是请求的序号,值为从0递增的整数值。
又从前面得知,requestCode的本身的值是小于0xffff的,所以((requestIndex+1)<<16)+(requestCode&0xffff)简化一下就是:(requestIndex+1)*65536+requestCode
——所以这个值是必定大于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。

再加上前文所说的,Fragment.startActivityForResult最终映射的requestCode值必定大于0xffff,所以,现在可以得出了一个初步的结果:
SDK把Fragment和FragmentActivity的的ruquestCode都限制在了0xffff以内,然后对于Fragment所发起的请求,都通过一个映射,把最终的requestCode变成了一个大于0xffff的值。

——到现在,已经可以推测到:在获取的结果的时候,也是会通过跟0xffff这个数值来比较,来区分是要把结果交给FragmentActivity还是Fragment来处理。

来验证一下看看:

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,一目了然,证实了我们上面的推论
在FragmentActivity.onActivityResult中,只有requestCode>0xffff时,这里得到的requestIndex才能满足requestIndex != 0,然后进入下面的逻辑:把requestCode通过反向之前的映射关系,还原成最初Fragment所指定的requestCode,交给Fragment.onActivityResult进行处理。

4. 解释最初的问题

所以,现在也能明白了为什么会有前面说的这几个表现:

  1. Fragment和FragmentActivity都能接收到自己的发起的请求所返回的结果

那当然,就是这么设计的。

  1. FragmentActivity发起的请求,Fragment完全接收不到结果

被FragmentActivity拦截了,没有转发到Fragment。

  1. Fragment发起的请求,虽然在FragmentActivity中能获取到结果,但是requestCode完全对应不上

如果是Fragment发起的请求,那么在FragmentActivity.onActivityResult获取到的requestCode,其实是经过映射之后一个的大于0xffff的值,已经不是最初Fragment发请求时的requestCode了。

5. 思考

为什么要用映射requestCode的方法来区分请求是否来自Fragment呢?绕这么一个弯子,直接使用一个变量来标记不行么?

直接使用一个变量来标记还真不行:

  • 因为我们自己最终写业务代码MyFragmentActivity肯定是继承自FragmentActivity的,而MyFragmentActivity.onActivityResult的调用会先于FragmentActivity.onActivityResult。
  • 所以无论是Fragment还是MyFragmentActivity所发起的startActivityForResult请求,最终在获取结果的时候是一定是会通过MyFragmentActivity.onActivityResult的。
  • 如果在这里使用一个变量来标记请求的来源,那实质上就是依赖于开发者自己来判断——这是繁琐而且不可控的。
  • 而相比较而言,使用一个简单的映射规则,就能把来自Fragment的请求和来自FragmentActivity自身请求区分开来——十分简单可靠。

6. 总结

  1. 使用startActivityForResult的时候,requestCode一定不要大于0xffff(65535)。
  2. 如果希望在Fragment的onActivityResult接收数据,就要调用Fragment.startActivityForResult,而不是Fragment.getActivity().startActivityForResult。
  3. 看源码果然是学习的好方法~
  4. Google的工程师果然牛逼。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容