乐趣区

关于android:Activity之间的通信

假如咱们有这样一个罕用的场景:

  • 有两个 Activity,第一个 Activity 展现一段文本
  • 点击“编辑”按钮启动第二个 Activity,并把这段文本当做参数传递到第二个 Activity
  • 在第二个 Activity 编辑这个字符串
  • 编辑实现后点击保留将后果返回到第一个 Activity
  • 第一个 Activity 展现批改后的字符串

如下图:

这是一个非常简单和常见的场景,咱们个别通过 startActivityForResult 的形式传递参数,并在 onActivityResult 接管编辑后的后果,代码也很简略,如下:


// 第一个 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 来实现。


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 示意用户勾销了编辑
  */
@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,将这个过程封装起来,代码如下:


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,如有侵权,请分割删除。

退出移动版