由集成ARouter引发的少量思考
引子
最近打算把项目的各个页面按板块的不同做拆分,也就是简单地想做下组件化的改造吧,那么这样一来不同板块的各个页面就互不依赖了,自然不能直接通过startActivity
来显式跳转了,自带的隐式跳转又略显笨重,不够灵活,于是乎就想到了引入路由框架,在github上找找,看到现在用的最多的就是ARouter了吧,看了下主页的详情,支持的功能还是挺多的,就它了!
由于今天想讲的是页面之间的数据交互,那先来看下ARouter关于这方面的使用方法:
// 构建标准的路由请求,startActivityForResult// navigation的第一个参数必需是Activity,第二个参数则是RequestCodeARouter.getInstance().build("/test/1") .withLong("key1", 666L) .withString("key3", "888") .withObject("key4", new Test("Jack", "Rose")) .navigation(this, 5);
而后在对应的Activity中像解析startActivity
传递的数据解析这些数据就好了:
// 在支持路由的页面上增加注解(必选)// 这里的路径需要注意的是至少需要有两级,/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
对象里:
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)
的方式啰。
问题分析
问题是现在我项目里用了两三个Activity
,却有几十个Fragment
,大量板块间的页面跳转和数据传递都是由Fragment
发起的,这样就产生了一个问题,Fragment
尽管也有startActivityForResult
和onActivityResult
,但是根据上面对ARouter
的源码简单分析来看,我们压根调用的都是它所依附的Activity的这两个方法。
github上的issues49是这么处理的:
@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
。
startActivityForResult
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
方法:
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)
onActivityResult
由于最终调用的是Activity的startActivityForResult
,只是requestCode
做了特殊解决了而已,所以最先被回调的就是Activity
的onActivityResult,也就是我们的FragmentActivity
的onActivityResult
:
@Overrideprotected 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); } } }
那最后我采取这种方案了吗?
image
思考
通过上面的一系列的分析,我其实得到的最有用的信息是,FragmentActivity
原来还有这么一个方法:
public void startActivityFromFragment( Fragment fragment, Intent intent, int requestCode) {
注意这是个public方法,意味着不需要反射即可以调用了,所以我们就能很好地利用它了。
考虑到上面的兼容方法太粗暴了,不够优雅,而且我那个小项目也不需要ARouter
那些阻拦器啊,全局降级啊这些高级用法,所以我把ARouter
代码下下来,删删减减,并新添加了navigation(Fragment mFragment, int requestCode)
方法:
if (currentContext is FragmentActivity && fragment != null) { currentContext.startActivityFromFragment(fragment, intent, requestCode)} else { startActivity(requestCode, currentContext, intent, routeMeta, callback)}
应用
可以利用上述方法,抛弃繁琐模板化的startActivityForResult
、onActivityResult
和各种code,增加一个空白的Fragment,并采用回调的方式解决返回结果:
object FragmentActivityProxy { private var requestCode = AtomicInteger(1) fun startActivityCallback(fragmentActivity: FragmentActivity, intent: Intent, callback: ActivityResultCallBack) { val code = requestCode.getAndIncrement() val emptyFragment = EmptyFragment(code, callback, false) fragmentActivity.supportFragmentManager.beginTransaction().add(emptyFragment, "$code").commit() fragmentActivity.startActivityFromFragment(emptyFragment, intent, code) } fun startActivityCallback(fragment: Fragment, intent: Intent, callback: ActivityResultCallBack) { val code = requestCode.getAndIncrement() val emptyFragment = EmptyFragment(code, callback, true) fragment.activity?.startActivityFromFragment(emptyFragment, intent, code) } interface ActivityResultCallBack { fun onActivityResult(resultCode: Int, data: Intent?) }}
@SuppressLint("ValidFragment")class EmptyFragment constructor( @IntRange(to = 0xFFFF)val requestCode: Int=-1, val callback: FragmentActivityProxy.ActivityResultCallBack?=null, private val isFragment: Boolean = false) : Fragment() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) val code = if (isFragment) { requestCode and 0xffff } else { requestCode } if (this.requestCode == code) { callback?.onActivityResult(resultCode, data) } activity?.supportFragmentManager?.beginTransaction()?.remove(this@EmptyFragment)?.commit() }}
这样我们跳转和拿到返回数据的方式也就变得比较优雅了:
val intent = Intent(this@MainActivity, Main2Activity::class.java) FragmentActivityProxy.startActivityCallback( this, intent, object : FragmentActivityProxy.ActivityResultCallBack { override fun onActivityResult(resultCode: Int, data: Intent?) { Log.d("TAG", data?.getStringExtra("key1") + "") } })
顺手也把这种方式的跳转整合到了我的缩减版ARouter中了,假如有需要我传到Github。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 由集成ARouter引发的少量思考