startActivityForResult相关

startActivityResult这个方法其实做Android开发的都应该很熟悉了,但是可能还是有人不知道Fragment里也有这个方法。虽然之前在Fragment里写代码的时候看到过这个方法,也能猜出来能干啥,但是最近才第一次用到,所以总结一下。首先在使用上就不多说了,在Fragment中和在Activity中使用startActivityForResult的方式是一样的,Fragment也有onActivityResult方法可供重写,但是记住不要调用getActivity().startActivityForResult方法,这就是两回事了。

表现

首先看在使用上会有什么问题。我们遇到的比较典型的就是在一个Activity中嵌套一个Fragment,在两个页面里都有startActivityForResult的调用,也都重写了onActivityResult,然后调用方法并收到返回的结果。这时候我们会发现在各自页面里都收到返回的数据,也能对上对应的requestCode,但是,在Activity里也能收到在Fragment里的请求结果,但是requestCode对不上。从这个表现我们能大概猜测出来,Fragment的startActivityForResult和对应的onActivityResult也是借助Activity对应的方法来实现,只是做了一点处理来避免冲突。

源码

猜测当然是不严谨的,还是看源码来的实在。先看Fragment里的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* Called by Fragment.startActivityForResult() to implement its behavior.
*/

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;
}
checkForValidRequestCode(requestCode);
int requestIndex = allocateRequestIndex(fragment);
ActivityCompat.startActivityForResult(
this, intent, ((requestIndex + 1) << 16) + (requestCode & 0xffff), options);
} finally {
mStartedActivityFromFragment = false;
}
}

// Allocates the next available startActivityForResult request index.
private int allocateRequestIndex(Fragment fragment) {
// Sanity check that we havn't exhaused the request index space.
if (mPendingFragmentActivityResults.size() >= MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS) {
throw new IllegalStateException("Too many pending Fragment activity results.");
}

// Find an unallocated request index in the mPendingFragmentActivityResults map.
while (mPendingFragmentActivityResults.indexOfKey(mNextCandidateRequestIndex) >= 0) {
mNextCandidateRequestIndex =
(mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;
}

int requestIndex = mNextCandidateRequestIndex;
mPendingFragmentActivityResults.put(requestIndex, fragment.mWho);
mNextCandidateRequestIndex =
(mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;

return requestIndex;
}

/**
* Checks whether the given request code is a valid code by masking it with 0xffff0000. Throws
* an {@link IllegalArgumentException} if the code is not valid.
*/

static void checkForValidRequestCode(int requestCode) {
if ((requestCode & 0xffff0000) != 0) {
throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
}
}

源码很简单,注释也很清楚。首先对是否由Fragment发起请求做一个判断,然后如果requestCode为-1则直接正常调用父Activity的startActivityForResult方法,因为requestCode为-1代表调用的是startActivity方法。然后对requestCode做一个过滤,,必须小于0xffff0000。接着因为一个Activity可能有多个Fragment,所以还要拿到请求的序号。然后就是调用ActivityCompat.startActivityForResult了:

1
2
3
4
5
6
7
8
9

public static void startActivityForResult(Activity activity, Intent intent, int requestCode,
@Nullable Bundle options)
{

if (Build.VERSION.SDK_INT >= 16) {
ActivityCompatJB.startActivityForResult(activity, intent, requestCode, options);
} else {
activity.startActivityForResult(intent, requestCode);
}
}

这里就没啥好说的了。那么关键就在调用之前对requestCode的处理那里了,那里并没有直接传入requestCode,而是传入((requestIndex + 1) << 16) + (requestCode & 0xffff)。requestIndex是Fragment的请求序号,这一个转换比较明确的表达就是:

1
2

(requestIndex + 1) * 65536 + requestCode

ok,这里我们也能猜到一点东西了,就是用这个公式来对requestCode做区分,使对应的Activity和Fragment们都能得到正确的回调而不会发生冲突。那么我们再看ActivityonActivityResult方法来验证一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

/**
* Dispatch incoming result to the correct fragment.
*/

@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);
}

看得出来基本与猜测的一致,在onActivityResult里会拿着requestCode推出requestIndex,再推出对应的Fragment和对应的requestCode,再调用Fragment的onActivityResult方法完成回调。整个流程结束。

结论

根据源码的分析,基本能解释了开始提到的表现都是什么原因。Fragment的startActivityForResult这一整套流程都要借助父Activity对应的方法来实现,所以在Activity里自然也能拦截到返回的数据,但是因为requestCode被做了处理,所以无法映射到正确的requestCode。看了这个源码只能说Fragment给Google挖的坑太大了···

startActivityForResult在Adapter里如何解耦

在搜索今天这篇东西相关的资料的时候看到了这个问题,觉得还挺有意思的。现在的页面越来越复杂,Adapter也越来越复杂,虽然现在几乎Adapter里都会传入Activity,但是理论上来说Adapter只是一个适配器,应该只负责View和data的绑定,而不应该做太多的逻辑处理。所以我目前想到的比较合理的解耦方式应该是Adapter向外界暴露对应的startActivityForResultonActivityResult两个方法,类似今天分析的源码Fragment所做的那样,这样做也可以避免处理Fragment和Activity不同的问题,无论Adapter在哪里使用都不受影响。如果Adapter更复杂,具体的逻辑已经被拆成了单个的item来实现,那么对应的item也暴露这两个方法,然后交给Adapter来代理,requestCode也交给Adapter来管理。这应该是比较合理的解耦方式。