博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
由集成ARouter引发的一些思考
阅读量:6246 次
发布时间:2019-06-22

本文共 9466 字,大约阅读时间需要 31 分钟。

引子

最近打算把项目的各个页面按模块的不同做拆分,也就是简单地想做下组件化的改造吧,那么这样一来不同模块的各个页面就互不依赖了,自然不能直接通过startActivity来显式跳转了,自带的隐式跳转又略显笨重,不够灵活,于是乎就想到了引入路由框架,在github上找找,看到现在用的最多的就是了吧,看了下主页的介绍,支持的功能还是挺多的,就它了!

因为今天想讲的是页面之间的数据交互,那先来看下关于这方面的使用方法:

// 构建标准的路由请求,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虽然也有startActivityForResultonActivityResult,但是根据上面对ARouter的源码简单分析来看,我们压根调用的都是它所依附的Activity的这两个方法。 github上的是这么解决的:

@Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        super.onActivityResult(requestCode, resultCode, data);        List
allFragments = getSupportFragmentManager().getFragments(); if (allFragments != null) { for (Fragment fragment : allFragments) { fragment.onActivityResult(requestCode, resultCode, data); } } }复制代码

手动把数据从Activity的onActivityResult传递到fragment里,这样简单粗暴,所有attach到这个Acttivty的Fragment都会收到数据,当然再在对应的Fragment里判断requestCoderesultCode,这样就没问题了吗?

源码分析

要解决这个问题,我们来分析下FragmentstartActivityForResultonActivityResult

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,所以会调用到这个FragmentActivitystartActivityFromFragment方法:

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的requestCodewho通过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();复制代码

所以通过调用FragmentstartActivityForResult,我们会生成一个requestIndex,来和fragment的mWho建立映射关系,至此Fragment对象的任务就完成了,然后调用的就是Ativity的startActivityForResult了,不过它的requestCode也不是Fragment的requestCode,而是((requestIndex + 1) << 16) + (requestCode & 0xffff)

onActivityResult

因为最终调用的是发起跳转的Fragment所attach的FragmentActivitystartActivityForResult,只是requestCode做了特殊处理了而已,Fragment并不需要参与跳转,所以最先被回调的也就是这个FragmentActivityonActivityResult

@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
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)        }复制代码

应用

可以利用上述方法,抛弃繁琐模板化的startActivityForResultonActivityResult和各种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。

转载地址:http://oblia.baihongyu.com/

你可能感兴趣的文章
什么是http?
查看>>
pthreads v3下的同步处理synchronized
查看>>
10.第一个小项目
查看>>
SDS(Simple Dynamic String)一个简易动态字符串库
查看>>
swfit-pod使用
查看>>
(九)easyUI之选项卡
查看>>
日志分析工具ELK(三)
查看>>
PAT (Advanced Level) 1049. Counting Ones (30)
查看>>
HDU 5763 Another Meaning
查看>>
session详解
查看>>
scroll滚动条
查看>>
mysql链接超时错误
查看>>
Win10 安装 Linux子系统 Ubuntu18.04 / Kali Linux 的体验
查看>>
才发现用git Gui也能上传项目到gitHub,下面分享一下经验
查看>>
P1462 通往奥格瑞玛的道路
查看>>
关于ajax
查看>>
一份关于jvm内存调优及原理的学习笔记
查看>>
怎么查看80端口占用情况- 如何查看端口占用情况?
查看>>
搭建测试框架
查看>>
position:absolute在IE8浏览器下无法显示正确位置
查看>>