彻底搞懂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开始:
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
|
|
分析一下这段代码:
1,mStartedActivityFromFragment = true
首先标记一下请求是来自于Fragment。
2,if(requestCode == 1)
的内容不用管,它是来自于startActivity(没有ForResult)的情况。
3,然后的代码添加了对requestCode必须小于0xffff的限制 if((requestCode&0xffff0000) != 0){/*抛异常*/}
我们是从Fragment.startActivityForResult追踪到这里的,所以虽然文档没有明确说,但是从这里可以看出:Fragment.startActivityForResult的requestCode也是必须要<=0xffff的。
然后,下面是关键点了:
——其中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
|
|
可以看到,判断了一下如果请求不是来自于Fragment,也就是来自于FragmentActivity自身,就限制requestCode不能大于0xffff。
再加上前文所说的,Fragment.startActivityForResult最终映射的requestCode值必定大于0xffff,所以,现在可以得出了一个初步的结果:
SDK把Fragment和FragmentActivity的的ruquestCode都限制在了0xffff以内,然后对于Fragment所发起的请求,都通过一个映射,把最终的requestCode变成了一个大于0xffff的值。
——到现在,已经可以推测到:在获取的结果的时候,也是会通过跟0xffff这个数值来比较,来区分是要把结果交给FragmentActivity还是Fragment来处理。
来验证一下看看:
3.4 FragmentActivity.onActivityResult
|
|
OK,一目了然,证实了我们上面的推论。
在FragmentActivity.onActivityResult中,只有requestCode>0xffff时,这里得到的requestIndex才能满足requestIndex != 0
,然后进入下面的逻辑:把requestCode通过反向之前的映射关系,还原成最初Fragment所指定的requestCode,交给Fragment.onActivityResult进行处理。
4. 解释最初的问题
所以,现在也能明白了为什么会有前面说的这几个表现:
Fragment和FragmentActivity都能接收到自己的发起的请求所返回的结果
那当然,就是这么设计的。
FragmentActivity发起的请求,Fragment完全接收不到结果
被FragmentActivity拦截了,没有转发到Fragment。
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. 总结
- 使用startActivityForResult的时候,requestCode一定不要大于0xffff(65535)。
- 如果希望在Fragment的onActivityResult接收数据,就要调用Fragment.startActivityForResult,而不是Fragment.getActivity().startActivityForResult。
- 看源码果然是学习的好方法~
- Google的工程师果然牛逼。
关于作者 :
http://www.barryzhang.com
https://github.com/barryhappy