startActivityForResult与ARouter在fragment中使用startActivityForResult

来自第二天的更新

关于文中使用空Fragment进行回调的方案,在activity被销毁重建后会有问题,具体是从这篇文章是时候丢掉 onActivityResult 了 !的评论一木艮呆毛的回复中了解到的,并且写demo试了一下的确存在这个情况。

例如以下的这种写法,activity的重建让原先方法引用中的btn不是现在重建后activity中的btn。

 

kotlin

复制代码

NoResult.startActivityForResult(activity, intent, requestCode){ _: Int, _: Int, _: Intent? -> btn?.text = "2002" }


关于startActivityForResult

startActivityForResult在平时的开发中用到的并不多,但是用到也是非常麻烦。需要重写onActivityResult,需要在老远的地方写回调处理,当然现在也有很多开源库处理了这些问题。 主要以下几个原因,决定还是想自己写一些工具来处理这个问题。

  1. 解决方案不复杂,自己能够完成基础的功能。在使用频度较低的情况下用kotlin的基础语法实现即可,不一定需要链式和Rx扩展。
  2. startActivityForResult用到的机会并不是特别多,在项目中因为很小的需求额外引入一个三方库是没必要的
  3. 自己写主动权更大

解决方案

解决方案和RxPermissions的类似,通过一个隐藏的fragment做转发,所有的startActivityForResult都由这个fragment发起,直接使用SparseArray存储方法引用可以少写接口(kotlin的便利)。retainInstance = true可以让fragment不会被销毁脱离Activity存在。fragment的方法startActivityForResult最终会回调它所在的activity的startActivityFromFragment保证结果会最终回调到该fragment。

 

kotlin

复制代码

class ResultFragment : Fragment() { private val callbacks = SparseArray<(requestCode: Int, resultCode: Int, data: Intent?) -> Unit>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) retainInstance = true } fun startActivityForResult( intent: Intent, requestCode: Int, callback: (requestCode: Int, resultCode: Int, data: Intent?) -> Unit ) { callbacks.put(requestCode, callback) startActivityForResult(intent, requestCode) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { callbacks.get(requestCode)?.invoke(requestCode, resultCode, data) callbacks.remove(requestCode) } }

然后通过方法查找该fragment

 

kotlin

复制代码

//通过tag找到fragment private fun findOnResultFragment(activity: FragmentActivity): ResultFragment? = activity.supportFragmentManager.findFragmentByTag(tag) as? ResultFragment //在找不到fragment的时候创建新的fragment private fun getOnResultFragment(activity: FragmentActivity): ResultFragment { return findOnResultFragment(activity) ?: activity.supportFragmentManager.run { ResultFragment().apply { beginTransaction().add(this, NoResult.tag).commitAllowingStateLoss() executePendingTransactions() } } }

最后提供startActivityForResult方法

 

kotlin

复制代码

fun startActivityForResult( fragment: Fragment, intent: Intent, requestCode: Int, callback: (requestCode: Int, resultCode: Int, data: Intent?) -> Unit) { startActivityForResult(fragment.requireActivity(), intent, requestCode, callback) } fun startActivityForResult( activity: FragmentActivity, intent: Intent, requestCode: Int, callback: (requestCode: Int, resultCode: Int, data: Intent?) -> Unit) { getOnResultFragment(activity).startActivityForResult(intent, requestCode, callback) }

实际使用的时候可以直接这样使用,调整上面的方法处理掉requestCode和resultCode可以让方法更简洁。

 

kotlin

复制代码

NoResult.startActivityForResult(fragment/activity, intent, requestCode){ requestCode: Int, resultCode: Int, intent: Intent? -> //处理结果... }

ARouter中fragment调用startActivityForResult的问题

参考问题#489 关于在fragment 使用 navigation(Activity mContext, int requestCode) 中的问题,使用ARouter在fragment中调用startActivityForResult相关逻辑时不回调fragment的onActivityResult方法。

这个问题是因为ARouter实现原理造成的,在fragment中使用ARouter的startActivityForResult会使用fragment所依附的activity的startActivityForResult方法,而实际fragment中startActivityForResult最终实现是调用它所依附的activity的startActivityFromFragment,所以这就造成了上述问题。

ARouter大致原理是通过注解和APT工具生成映射代码,然后在APP安装后首次打开或更新后首次打开时扫描文件通过反射加载映射缓存至SharedPreferences。在使用跳转时构建Postcard对象然后进行路由跳转,Postcard对象中包含了本次路由跳转的所有信息。

既然Postcard中包含了路由的所有信息,那就自己处理startActivityForResult时相关的逻辑。文章由集成ARouter引发的一些思考中通过修改源码做了相关的实现,但因为ARouter中Postcardpublic的,所以只要为它添加扩展方法来实现即可。

 

kotlin

复制代码

//预处理方法,原ARouter中的实现,不过舍弃了拦截器和自定义callback internal fun Postcard.pretreatment(context: Context? = null): Postcard? { val pretreatmentService = ARouter.getInstance().navigation(PretreatmentService::class.java) if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, this)) { // Pretreatment failed, navigation canceled. return null } try { LogisticsCenter.completion(this) } catch (e: Exception) { //这里弹toast 未找到router //回调call ARouter.getInstance().navigation(DegradeService::class.java) ?.apply { onLost(context, this@pretreatment) } return null } //不考虑实用拦截器 //只考虑activity if (type != RouteType.ACTIVITY) return null return this }

使用Postcard来构建Intent,不需要太复杂

 

kotlin

复制代码

internal fun Postcard.buildIntent(activity: Activity): Intent { return Intent(activity, destination).apply { putExtras(this@buildIntent.extras) if (this@buildIntent.flags != -1) flags = this@buildIntent.flags if (!this@buildIntent.action.isNullOrEmpty()) action = this@buildIntent.action } }

最后提供startActivityForResult的方法,包含了最上面的实现

 

kotlin

复制代码

fun Postcard.navigateForResult( activity: FragmentActivity, requestCode: Int, callback: (requestCode: Int, resultCode: Int, data: Intent?) -> Unit ) { pretreatment(activity)?.apply { NoResult.startActivityForResult(activity, buildIntent(activity), requestCode, callback) } } fun Postcard.navigateForResult( fragment: Fragment, requestCode: Int, callback: (requestCode: Int, resultCode: Int, data: Intent?) -> Unit ) { pretreatment(fragment.requireContext())?.apply { NoResult.startActivityForResult(fragment, buildIntent(fragment.requireActivity()), requestCode, callback) } }

这里其实还提供了一个普通的startActivity方法。ARouter中直接导航的方法是使用Application的context进行跳转的,这时候就不得不要为跳转intent添加FLAG_ACTIVITY_NEW_TASK。这里涉及taskAffinity属性用于Activity栈分配,如果有特殊需求的话,ARouter原有的跳转方式可能不太合适,所以可以添加以下这种跳转方式来避免添加FLAG_ACTIVITY_NEW_TASK。这里标记一篇文章以便日后复习:Activity的启动模式.

 

kotlin

复制代码

fun Postcard.navigateActivity(activity: FragmentActivity) { pretreatment(activity)?.apply { activity.startActivity(buildIntent(activity)) } }

结语

以上简单的实现基本能够满足日常使用,如果更复杂的使用场景那么可以选择更加完善易用的三方库。还有,不同的启动模式会对startActivityForResult造成各种影响,切记切记。

作者:Mtk
链接:https://juejin.cn/post/6861944938381672462
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值