最近打算把项目的各个页面按模块的不一样作拆分,也就是简单地想作下组件化的改造吧,那么这样一来不一样模块的各个页面就互不依赖了,天然不能直接经过startActivity
来显式跳转了,自带的隐式跳转又略显笨重,不够灵活,因而乎就想到了引入路由框架,在github上找找,看到如今用的最多的就是ARouter了吧,看了下主页的介绍,支持的功能仍是挺多的,就它了!java
由于今天想讲的是页面之间的数据交互,那先来看下ARouter关于这方面的使用方法:git
// 构建标准的路由请求,startActivityForResult
// navigation的第一个参数必须是Activity,第二个参数则是RequestCode
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation(this, 5);
复制代码
而后在对应的Activity中像解析startActivity
传递的数据解析这些数据就行了:github
// 在支持路由的页面上添加注解(必选)
// 这里的路径须要注意的是至少须要有两级,/xx/xx
@Route(path = "/test/activity")
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
Long key1 = bundle.getLong("key1");
}
}
}
复制代码
看过源码就很简单了,之因此是这么作是由于ARouter只是用上面的withXXX
帮咱们把数据都存储到了mBundle
对象里:bash
public Postcard withString(@Nullable String key, @Nullable String value) {
mBundle.putString(key, value);
return this;
}
public Bundle getExtras() {
return mBundle;
}
复制代码
最终塞到了Intent对象里:框架
// Build intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
....//省略
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
复制代码
其实最终就是调用普通的startActivityForResult
来作页面跳转和传递数据的。那怎么返回数据给上一层页面呢?固然也就是同样用setResult(int resultCode, Intent data)
的方式啰。dom
问题是如今我项目里用了两三个Activity
,却有几十个Fragment
,大量模块间的页面跳转和数据传递都是由Fragment
发起的,这样就产生了一个问题,Fragment
虽然也有startActivityForResult
和onActivityResult
,可是根据上面对ARouter
的源码简单分析来看,咱们压根调用的都是它所依附的Activity的这两个方法。 github上的issues49是这么解决的:ide
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
List<Fragment> allFragments = getSupportFragmentManager().getFragments();
if (allFragments != null) {
for (Fragment fragment : allFragments) {
fragment.onActivityResult(requestCode, resultCode, data);
}
}
}
复制代码
手动把数据从Activity的onActivityResult
传递到fragment
里,这样简单粗暴,全部attach到这个Acttivty的Fragment都会收到数据,固然再在对应的Fragment里判断requestCode
和resultCode
,这样就没问题了吗?源码分析
要解决这个问题,咱们来分析下Fragment
的startActivityForResult
和onActivityResult
。组件化
public void startActivityForResult(@SuppressLint("UnknownNullness") 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);
}
复制代码
上面的mHost对应的就是Fragment
依附的FragmentActivity
,因此会调用到这个FragmentActivity
的startActivityFromFragment
方法:post
public void startActivityFromFragment(Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) {
....//省略
//检查requestCode大小,不能超过0xffff
checkForValidRequestCode(requestCode);
//分配给这个Fragment惟一的requestIndex,根据这个requestIndex能够获取到对应Fragment的惟一标识mWho
int requestIndex = allocateRequestIndex(fragment);
//以后就调用activity的startActivityForResult
ActivityCompat.startActivityForResult(
this, intent, ((requestIndex + 1) << 16) + (requestCode & 0xffff), options);
}
复制代码
每个Fragment
在内部都有一个惟一的标识字段who,在FragmentActivity
中把全部调用startActivityFromFragment
方法的fragment的requestCode
和who
经过key-value的方式保存在mPendingFragmentActivityResults
变量中
// Allocates the next available startActivityForResult request index.
private int allocateRequestIndex(@NonNull Fragment fragment) {
//找到一个还没有分配的requestIndex
while (mPendingFragmentActivityResults.indexOfKey(mNextCandidateRequestIndex) >= 0) {
mNextCandidateRequestIndex =
(mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;
}
//将requestIndex和fragment的mWho保存起来
int requestIndex = mNextCandidateRequestIndex;
mPendingFragmentActivityResults.put(requestIndex, fragment.mWho);
mNextCandidateRequestIndex =
(mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;
return requestIndex;
}
复制代码
mWho
是fragment一个变量,用来惟一标识一个Framgment。
@NonNull
String mWho = UUID.randomUUID().toString();
复制代码
因此经过调用Fragment
的startActivityForResult
,咱们会生成一个requestIndex
,来和fragment的mWho创建映射关系,至此Fragment
对象的任务就完成了,而后调用的就是Ativity的startActivityForResult
了,不过它的requestCode
也不是Fragment的requestCode
,而是((requestIndex + 1) << 16) + (requestCode & 0xffff)
由于最终调用的是发起跳转的Fragment所attach的FragmentActivity
的startActivityForResult
,只是requestCode
作了特殊处理了而已,Fragment
并不须要参与跳转,因此最早被回调的也就是这个FragmentActivity
的onActivityResult
:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mFragments.noteStateNotSaved();
//解析获得requestIndex
int requestIndex = requestCode>>16;
//requestIndex = 0就表示没有Fragment发起过startActivityForResult调用
if (requestIndex != 0) {
requestIndex--;
//根据requestIndex获取Fragment的who变量
String who = mPendingFragmentActivityResults.get(requestIndex);
mPendingFragmentActivityResults.remove(requestIndex);
if (who == null) {
Log.w(TAG, "Activity result delivered for unknown Fragment.");
return;
}
//而后根据who变量获取目标Fragment,也就是发起startActivityForResult的那个`fragment`
Fragment targetFragment = mFragments.findFragmentByWho(who);
if (targetFragment == null) {
Log.w(TAG, "Activity result no fragment exists for who: " + who);
} else {
////解析获得最初fragment的requestCode,最后调用Fragment的onActivityResult
targetFragment.onActivityResult(requestCode & 0xffff, resultCode, data);
}
return;
}
...
super.onActivityResult(requestCode, resultCode, data);
}
复制代码
下面总结下两种状况表现:
Fragment.onActivityResult | FragmentActivity.onActivityResult | |
---|---|---|
Fragment.startActivityForResult | 正常接收 | 异常接收,requestCode不对 |
FragmentActivity.startActivityForResult | 不能接收 | 正常接收 |
因此上面的兼容方法应该改为:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
List<Fragment> allFragments = getSupportFragmentManager().getFragments();
if (allFragments != null) {
for (Fragment fragment : allFragments) {
fragment.onActivityResult(requestCode& 0xffff, resultCode, data);
}
}
}
复制代码
那最后我采起这种方案了吗?
经过上面的一系列的分析,我其实获得的最有用的信息是,FragmentActivity
原来还有这么一个方法:
public void startActivityFromFragment( Fragment fragment, Intent intent, int requestCode) {
复制代码
注意这是个public方法,意味着不须要反射就能够调用了,因此咱们就能很好地利用它了。
考虑到上面的兼容方法太粗暴了,不够优雅,并且路由原本就是用来解耦代码的,这样处理反而产生了耦合。我那个小项目也不须要ARouter
那些拦截器啊,全局降级啊这些高级用法,因此我把ARouter
代码下下来,删删减减,并新增了navigation(Fragment mFragment, int requestCode)
方法:
if (requestCode >= 0) { // Need start for result
if (currentContext is FragmentActivity && fragment != null) {
currentContext.startActivityFromFragment(fragment, intent, requestCode)
} else if (currentContext is Activity) {
ActivityCompat.startActivityForResult(currentContext, intent, requestCode, null)
} else {
Logs.defaults.e("Must use [navigation(activity, ...)] to support [startActivityForResult]")
}
} else {
ActivityCompat.startActivity(currentContext, intent, null)
}
复制代码
能够利用上述方法,抛弃繁琐模板化的startActivityForResult
、onActivityResult
和各类code,添加一个空白的Fragment,并采用回调的方式处理返回结果:
object MyRouter {
private var requestCode = AtomicInteger(1)
fun navigation(fragmentActivity: FragmentActivity, intent: Intent, callback: (Int, Intent?) -> Unit) {
val code = requestCode.getAndIncrement()
val emptyFragment = EmptyFragment()
emptyFragment.callback=callback
emptyFragment.requestCode= code
fragmentActivity.supportFragmentManager.beginTransaction().add(emptyFragment, "$code").commit()
fragmentActivity.startActivityFromFragment(emptyFragment, intent, code)
}
fun navigation(fragment: Fragment, intent: Intent, callback: (Int, Intent?) -> Unit) {
val code = requestCode.getAndIncrement()
val emptyFragment = EmptyFragment()
emptyFragment.callback=callback
emptyFragment.requestCode= code
fragment.activity?.startActivityFromFragment(emptyFragment, intent, code)
}
}
class EmptyFragment: Fragment() {
@IntRange(to = 0xFFFF)
var requestCode: Int = -1
var callback: ((Int, Intent?) -> Unit)? = null
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (this.requestCode == requestCode) {
callback?.invoke(resultCode, data)
}
activity?.supportFragmentManager?.beginTransaction()?.remove(this@EmptyFragment)?.commit()
}
}
复制代码
这样咱们跳转和拿到返回数据的方式也就变得比较简洁和优雅了:
fun toMain2Activity() {
val intent = Intent(this@MainActivity, Main2Activity::class.java)
MyRouter.navigation(this, intent) { resultCode, data ->
Log.d("result", "$resultCode ${data?.getStringExtra("key1")}")
}
}
复制代码
顺手也把这种方式的跳转整合到了个人缩减版ARouter中了,代码已传到github。