Android ActivityResult API 密法

Activity Result API 密法

背景

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

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

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

startActiivtyForResult 为何退出历史舞台?

在Android中,如果希望在获取Activity的返回值,我们可以使用startActiivtyForResult,它在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.

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

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

可以发现它特征很明显

  1. 发起调用结果处理是割裂的,代码结构复杂。
  2. 当处理多个结果时,导致onActivityResult代码更加复杂,可读性降低。
  3. 非类型安全,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,有以下提升:

  1. 处理多个返回结果时,代码逻辑更加清晰:只需横向注册即可,不需要在onActivityResult中堆代码了。
  2. 类型安全,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可以更愉快地编码了。

总结

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

相关推荐
有味道的男人8 小时前
Open Claw对接1688平台
android·rxjava
_李小白8 小时前
【android opencv学习笔记】Day 17: 目标追踪(MeanShift)
android·opencv·学习
用户86022504674729 小时前
AI 分析头部APP系统优化框架
android
用户86022504674729 小时前
AI分析头部APP优化框架
android
2501_9160074712 小时前
iOS开发中抓取HTTPS请求的完整解决方法与步骤详解
android·网络协议·ios·小程序·https·uni-app·iphone
lvronglee15 小时前
【数字图传第四步】Android App查看图传视频
android·音视频
90后的晨仔15 小时前
Android 程序入口与核心组件详解
android
90后的晨仔15 小时前
Kotlin 简介与开发环境搭建
android
BU摆烂会噶15 小时前
【LangGraph】House_Agent 实战(四):预定流程 —— 中断与人工干预
android·人工智能·python·langchain
AI玫瑰助手15 小时前
Python运算符:比较运算符(等于不等等于大于小于)与返回值
android·开发语言·python