前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Activity之间的通信

Activity之间的通信

原创
作者头像
七秒246
发布2021-12-14 14:34:20
1.1K0
发布2021-12-14 14:34:20
举报

假设我们有这样一个常用的场景:

  • 有两个Activity,第一个Activity展示一段文本
  • 点击“编辑”按钮启动第二个Activity,并把这段文本当做参数传递到第二个Activity
  • 在第二个Activity编辑这个字符串
  • 编辑完成后点击保存将结果返回到第一个Activity
  • 第一个Activity展示修改后的字符串
如下图:

这是一个非常简单和常见的场景,我们一般通过 startActivityForResult 的方式传递参数,并在 onActivityResult 接收编辑后的结果,代码也很简单,如下:

代码语言:javascript
复制
//第一个Activity启动编辑Activity
btnEditByTradition.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)
    }
}
那这种方式有什么缺点呢?
  1. 代码分散,可读性差
  2. 封装不彻底,调用方需要到EditActivity才能知道需要传递什么参数,类型是什么,key是什么
  3. 调用方需要知道EditActivity是如何返回的参数类型和key是什么才能正确解析
  4. 约束性差,各种常量的定义(REQUEST_CODE,PARAM_KEY等),若项目管理不严谨,重复定义,导致后期重构和维护比较麻烦
那有没有一种方式能解决上面的缺点呢?我们期望的是:
  1. 一个对外提供某些功能的Activity应该有足够的封装性,调用者像调用普通方法一样,一行代码即可完成调用
  2. 方法的参数列表就是调用本服务需要传递的参数(参数数量,参数类型,是否必须)
  3. 方法的返回参数就是本服务的返回结果
  4. 提供服务的Activity像一个组件一样,能对外提供功能都是以一个个方法的形式体现
通过Kotlin 协程和一个不可见的Fragment来实现。
代码语言:javascript
复制
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 方法内部是如何实现的呢?看如下代码:

代码语言:javascript
复制
/**
  * 对指定的文本进行编辑
  * @param content 要编辑的文本
  *
  * @return 可空  不为null 表示编辑后的内容  为null表示用户取消了编辑
  */
@JvmStatic
suspend 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 ,将这个过程封装起来,代码如下:

代码语言:javascript
复制
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,如有侵权,请联系删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 如下图:
  • 那这种方式有什么缺点呢?
  • 那有没有一种方式能解决上面的缺点呢?我们期望的是:
  • 通过Kotlin 协程和一个不可见的Fragment来实现。
相关产品与服务
项目管理
CODING 项目管理(CODING Project Management,CODING-PM)工具包含迭代管理、需求管理、任务管理、缺陷管理、文件/wiki 等功能,适用于研发团队进行项目管理或敏捷开发实践。结合敏捷研发理念,帮助您对产品进行迭代规划,让每个迭代中的需求、任务、缺陷无障碍沟通流转, 让项目开发过程风险可控,达到可持续性快速迭代。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档