Activity Result API 密法
背景
笔者最近在做hybird开发,H5Fragment/H5Activity作为外层容器,它不会有任何业务代码,所有的jsbridge代码分散到不同NativeModule
中,例如获取拍照,相册,权限申请等, 这就需要用到startActiivtyForResult
或registerForActivityResult
来提供获取返回值的能力,但startActivtyForResult
不好用被废弃了;registerForActivityResult
必须在STARTED
之前注册,与jsbridge调用时机冲突。
我希望能找到一种方式,能够满足
- 扩展性:增加页面返回场景时,无需修改H5Fragment/H5Activity
- 一次性消费:
发起调用
和结果处理
对应jsbridge的request->response - 简洁的调用方式:在一个函数中完整描述输入、输出
startActiivtyForResult 为何退出历史舞台?
在Android中,如果希望在获取Activity的返回值,我们可以使用startActiivtyForResult
,它在Android API level 1 就存在了,资历相当老,那为什么现在又被Google废弃了呢?
While the underlying
startActivityForResult()
andonActivityResult()
APIs are available on theActivity
class on all API levels, Google strongly recommends using the Activity Result APIs introduced in AndroidXActivity
andFragment
classes.
我们先来简单看一下它的用法,比如:
kotlin
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
button.setOnClickListener {
// 1️⃣ 发起调用
val intent = Intent(Intent.ACTION_PICK).setType(ContactsContract.Contacts.CONTENT_TYPE)
startActivityForResult(intent, REQUEST_CODE_PICK_CONTACT)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
if (requestCode == REQUEST_CODE_PICK_CONTACT) {
// ️2️⃣ 结果处理
val uri = intent.takeIf { resultCode == Activity.RESULT_OK }?.data
// Handle the returned uri
}
}
companion object {
const val REQUEST_CODE_PICK_CONTACT = 1
}
}
可以发现它特征很明显
发起调用
和结果处理
是割裂的,代码结构复杂。- 当处理多个结果时,导致
onActivityResult
代码更加复杂,可读性降低。 - 非类型安全,intent作为载体。
这也是它一直饱受开发者诟病的原因。
ActivityResult API引入
ActivityResult API 通过使用注册回调的方式,使代码更加清晰和简单,数据类型安全方面做得到保障。
基本使用及特征
kotlin
class MainActivity : Activity() {
val pickContact = registerForActivityResult(ActivityResultContracts.PickContact()) { uri: Uri? ->
// ️2️⃣ 结果处理
}
override fun onCreate(savedInstanceState: Bundle?) {
button.setOnClickListener {
// 1️⃣ 发起调用
pickContact.launch(null)
}
}
}
可以看到,相比于startActivityForResult
,有以下提升:
- 处理多个返回结果时,代码逻辑更加清晰:只需横向注册即可,不需要在
onActivityResult
中堆代码了。 - 类型安全,
registerForActivityResult
callback中直接就可以获取到消费者所需类型
ActivityResultContracts 内置了常用的ActivityResultContract
,开发者也可以仿照源码自定义扩展。
生命周期的约束
能不能将 1️⃣ 和 2️⃣ 放在一起(暂时忽略前后顺序)?
kotlin
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
button.setOnClickListener {
val pickContact = registerForActivityResult(ActivityResultContracts.PickContact()) { uri: Uri? ->
// ️2️⃣ 结果处理
}
// 1️⃣ 发起调用
pickContact.launch(null)
}
}
}
会抛出异常:
kotlin
LifecycleOwner xx is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.
在Activity/Fragment中,对调用registerForActivityResult
的时机有限制,必须在STARTED
前进行调用,这在很大程度上约束了我们代码的风格,发起调用
和结果处理
不得不割裂。
如何生命周期的限制?
现在Compose
已逐步成为主流,Android为开发者提供了rememberLauncherForActivityResult
用于在Composable中获取Activity返回值,Composable作为典型的数据驱动UI渲染,很显然rememberLauncherForActivityResult
完全可能在RESUMED
状态下进行调用:
里面有个很关键的类:ActivityResultRegistry
,它有一个方法不需要传递LifecycleOwner
。
java
/**
* Register a new callback with this registry.
*
* This is normally called by a higher level convenience methods like
* {@link ActivityResultCaller#registerForActivityResult}.
*
* When calling this, you must call {@link ActivityResultLauncher#unregister()} on the
* returned {@link ActivityResultLauncher} when the launcher is no longer needed to
* release any values that might be captured in the registered callback.
*
* @param key a unique string key identifying this call
* @param contract the contract specifying input/output types of the call
* @param callback the activity result callback
*
* @return a launcher that can be used to execute an ActivityResultContract.
*/
@NonNull
@SuppressWarnings("deprecation")
public final <I, O> ActivityResultLauncher<I> register(
@NonNull final String key,
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultCallback<O> callback
)
在androidx.activity.ComponentActivity#getActivityResultRegistry
中可以获取其实例,该方法无需LifecycleOwner
,意味着它脱离了生命周期的管理,我们得手动register/unregister
。
简单的封装
kotlin
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
button.setOnClickListener {
disposableLaunch(
contract = ActivityResultContracts.PickContact(),
input = null // 1️⃣ 调用参数
) { // 2️⃣ 回调处理
Toast.makeText(this@MainActivity, "uri: ${it}", Toast.LENGTH_SHORT).show()
}
}
}
private fun <I, O> disposableLaunch(
contract: ActivityResultContract<I, O>,
input: I,
callback: ActivityResultCallback<O>
) {
val key = UUID.randomUUID().toString()
val state = MutableStateFlow<ActivityResultLauncher<*>?>(null)
activityResultRegistry.register(key, contract) {
state.value?.unregister()
callback.onActivityResult(it)
}.launch(input)
}
}
现在调用代码与结果处理放在一块了,基于disposableLaunch
可以更愉快地编码了。
总结
本文对比了startActivtyForResult
和 ActivityResult API
,阐述了两者的优劣,通过借鉴Compose源码,封装了disposableLaunch
用于简化代码调用。