美文网首页
Android ActivityResult API 密法

Android ActivityResult API 密法

作者: BlueSocks | 来源:发表于2024-01-15 18:20 被阅读0次

Activity Result API 密法

背景

笔者最近在做hybird开发,H5Fragment/H5Activity作为外层容器,它不会有任何业务代码,所有的jsbridge代码分散到不同NativeModule中,例如获取拍照,相册,权限申请等, 这就需要用到startActivityForResultregisterForActivityResult 来提供获取返回值的能力,但startActivtyForResult不好用被废弃了;registerForActivityResult必须在STARTED之前注册,与jsbridge调用时机冲突。

我希望能找到一种方式,能够满足

  1. 扩展性:增加页面返回场景时,无需修改H5Fragment/H5Activity
  2. 一次性消费:发起调用结果处理对应jsbridge的request->response
  3. 简洁的调用方式:在一个函数中完整描述输入、输出

startActivityForResult 为何退出历史舞台?

在Android中,如果希望在获取Activity的返回值,我们可以使用startActivityForResult,它在Android API level 1 就存在了,资历相当老,那为什么现在又被Google废弃了呢?

While the underlying startActivityForResult() and onActivityResult() APIs are available on the Activity class on all API levels, Google strongly recommends using the Activity Result APIs introduced in AndroidX Activity and Fragment classes.

我们先来简单看一下它的用法,比如:

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
    }
}

可以发现它特征很明显

  1. 发起调用结果处理是割裂的,代码结构复杂。
  2. 当处理多个结果时,导致onActivityResult代码更加复杂,可读性降低。
  3. 非类型安全,intent作为载体。

这也是它一直饱受开发者诟病的原因。

ActivityResult API引入

ActivityResult API 通过使用注册回调的方式,使代码更加清晰和简单,数据类型安全方面做得到保障。

基本使用及特征

class MainActivity : Activity() {  
    val pickContact = registerForActivityResult(ActivityResultContracts.PickContact()) { uri: Uri? ->
        //  ️2️⃣ 结果处理
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        button.setOnClickListener {
            //  1️⃣ 发起调用
            pickContact.launch(null)
        }
    }
}

可以看到,相比于startActivityForResult,有以下提升:

  1. 处理多个返回结果时,代码逻辑更加清晰:只需横向注册即可,不需要在onActivityResult中堆代码了。
  2. 类型安全,registerForActivityResult callback中直接就可以获取到消费者所需类型

ActivityResultContracts内置了常用的ActivityResultContract,开发者也可以仿照源码自定义扩展。

生命周期的约束

能不能将 1️⃣ 和 2️⃣ 放在一起(暂时忽略前后顺序)?

class MainActivity : Activity() {  
    override fun onCreate(savedInstanceState: Bundle?) {
        button.setOnClickListener {
            val pickContact = registerForActivityResult(ActivityResultContracts.PickContact()) { uri: Uri? ->
            //  ️2️⃣ 结果处理
            }
            //  1️⃣ 发起调用
            pickContact.launch(null)
        }
    }
}

会抛出异常:

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状态下进行调用:

image.png

里面有个很关键的类:ActivityResultRegistry,它有一个方法不需要传递LifecycleOwner

/**  
* 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

简单的封装

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可以更愉快地编码了。

总结

本文对比了startActivtyForResultActivityResult API,阐述了两者的优劣,通过借鉴Compose源码,封装了disposableLaunch用于简化代码调用。

相关文章

网友评论

      本文标题:Android ActivityResult API 密法

      本文链接:https://www.haomeiwen.com/subject/ewuhodtx.html