假如咱们有这样一个罕用的场景:
- 有两个Activity,第一个Activity展现一段文本
- 点击“编辑”按钮启动第二个Activity,并把这段文本当做参数传递到第二个Activity
- 在第二个Activity编辑这个字符串
- 编辑实现后点击保留将后果返回到第一个Activity
- 第一个Activity展现批改后的字符串
如下图:
这是一个非常简单和常见的场景,咱们个别通过 startActivityForResult 的形式传递参数,并在 onActivityResult 接管编辑后的后果,代码也很简略,如下:
//第一个Activity启动编辑ActivitybtnEditByTradition.setOnClickListener { val content = tvContent.text.toString().trim() val intent = Intent(this, EditActivity::class.java).apply { putExtra(EditActivity.ARG_TAG_CONTENT, content) } startActivityForResult(intent, REQUEST_CODE_EDIT)} //EditActivity回传编辑后的后果 btnSave.setOnClickListener { val newContent = etContent.text.toString().trim() setResult(RESULT_OK, Intent().apply { putExtra(RESULT_TAG_NEW_CONTENT, newContent) }) finish()}//第一个Activity中承受编辑后的后果,并展现override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { REQUEST_CODE_EDIT -> { if (resultCode == RESULT_OK && data != null) { val newContent = data.getStringExtra(EditActivity.RESULT_TAG_NEW_CONTENT) tvContent.text = newContent } } else -> super.onActivityResult(requestCode, resultCode, data) }}
那这种形式有什么毛病呢?
- 代码扩散,可读性差
- 封装不彻底,调用方须要到EditActivity能力晓得须要传递什么参数,类型是什么,key是什么
- 调用方须要晓得EditActivity是如何返回的参数类型和key是什么能力正确解析
- 约束性差,各种常量的定义(REQUEST\_CODE,PARAM\_KEY等),若项目管理不谨严,反复定义,导致前期重构和保护比拟麻烦
那有没有一种形式能解决下面的毛病呢?咱们冀望的是:
- 一个对外提供某些性能的Activity应该有足够的封装性,调用者像调用一般办法一样,一行代码即可实现调用
- 办法的参数列表就是调用本服务须要传递的参数(参数数量,参数类型,是否必须)
- 办法的返回参数就是本服务的返回后果
- 提供服务的Activity像一个组件一样,能对外提供性能都是以一个个办法的模式体现
通过Kotlin 协程和一个不可见的Fragment来实现。
btnEditByCoroutine.setOnClickListener { GlobalScope.launch { val content = tvContent.text.toString().trim() // 调用EditActivity的 editContent 办法 // content为要编辑的内容 // editContent 即为编辑后的后果 val newContent = EditActivity.editContent(this@MainActivity, content) if (!newContent.isNullOrBlank()) { tvContent.text = newContent } }}
通过下面的代码,咱们看到,通过一个办法即可实现调用,根本实现了上文提到的冀望。 那 editContent 办法外部是如何实现的呢?看如下代码:
/** * 对指定的文本进行编辑 * @param content 要编辑的文本 * * @return 可空 不为null 示意编辑后的内容 为null示意用户勾销了编辑 */@JvmStaticsuspend fun editContent(activity: FragmentActivity, content: String): String? = suspendCoroutine { continuation -> val editFragment = BaseSingleFragment().apply { intentGenerator = { Intent(it, EditActivity::class.java).apply { putExtra(ARG_TAG_CONTENT, content) } } resultParser = { resultCode, data -> if (resultCode == RESULT_OK && data != null) { val result = data.getStringExtra(RESULT_TAG_NEW_CONTENT) continuation.resume(result) } else { continuation.resume(null) } removeFromActivity(activity.supportFragmentManager) } } editFragment.addToActivity(activity.supportFragmentManager) }
这里须要借助一个“BaseSingleFragment”来实现,这是因为我不能违反 ActivityManagerService 的规定,仍然须要通过 startActivityForResult 和 onActivityResult 来实现,所以咱们这里通过一个不可见(没有界面)的 Fragment ,将这个过程封装起来,代码如下:
class BaseSingleFragment : Fragment() { /** * 生成启动对应Activity的Intent,因为指定要启动的Activity,如何启动,传递参数,所以由具体的应用地位来实现这个Intent * * 使用者必须实现这个lambda,否则间接抛出一个异样 */ var intentGenerator: ((context: Context) -> Intent) = { throw RuntimeException("you should provide a intent here to start activity") } /** * 解析指标Activity返回的后果,有具体实现者解析,并回传 * * 使用者必须实现这个lambda,否则间接抛出一个异样 */ var resultParser: (resultCode: Int, data: Intent?) -> Unit = { resultCode, data -> throw RuntimeException("you should parse result data yourself") } companion object { const val REQUEST_CODE_GET_RESULT = 100 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val context = requireContext() startActivityForResult(intentGenerator.invoke(context), REQUEST_CODE_GET_RESULT) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == REQUEST_CODE_GET_RESULT) { resultParser.invoke(resultCode, data) } else { super.onActivityResult(requestCode, resultCode, data) } } /** * add current fragment to FragmentManager */ fun addToActivity(fragmentManager: FragmentManager) { fragmentManager.beginTransaction().add(this, this::class.simpleName) .commitAllowingStateLoss() } /** * remove current fragment from FragmentManager */ fun removeFromActivity(fragmentManager: FragmentManager) { fragmentManager.beginTransaction().remove(this).commitAllowingStateLoss() }}当然,这是一个 suspend 办法,java是不反对协程的,而现实情况是,很多我的项目都有中途集成Kotlin的,有很多遗留的java代码,对于这种状况,咱们须要提供相应的java实现吗?The answer is no. Java 代码同样能够调用 suspend 办法,调用形式如下:btnEditByCoroutine.setOnClickListener((view) -> { String content = tvContent.getText().toString().trim(); EditActivity.editContent(MainActivityJava.this, content, new Continuation<String>() { @NotNull @Override public CoroutineContext getContext() { return EmptyCoroutineContext.INSTANCE; } @Override public void resumeWith(@NotNull Object o) { String newContent = (String) o; if (!TextUtils.isEmpty(content)) { tvContent.setText(newContent); } } });});
尽管是通过回调的形式,在resumeWith办法中来承受后果,但也是比 startActivityForResult 的形式要好的多。
Perfect!!!
这种实现形式的灵感是来源于 RxPermission 对权限申请流程的实现,在此对 RxPermission 表白感激。 另外 Glide 3.X 版本对图片加载工作的启动,暂停,和勾销和Activity的和生命周期绑定也是通过向FragmentManager中增加了一个暗藏的Fragment来实现的。
本文转自 https://juejin.cn/post/7033598140766912549,如有侵权,请分割删除。