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用于简化代码调用。

相关推荐
Meteors.8 小时前
安卓源码阅读——01.grade设置binding为true时,xml如何进行映射
android·xml
_李小白9 小时前
【android opencv学习笔记】Day 26: 滤波算法之低通滤波与图像缩放插值
android·opencv·学习
NiceCloud喜云9 小时前
Claude Code Routines 实战:三种触发器跑通云端自动化编码
android·运维·数据库·人工智能·自动化·json·飞书
我命由我1234512 小时前
Bugly - Bugly 基本使用( App 质量追踪平台)
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
weiggle13 小时前
第二篇:搭建你的第一个 Compose 项目——开发环境与项目结构
android·前端
阿巴斯甜13 小时前
为什么 AIDL 接口客户端、服务端要写两份一模一样的?
android
weiggle14 小时前
第一篇:Jetpack Compose 宣言——为什么 Android 开发需要声明式 UI
android
城管不管15 小时前
什么是Prompt?
android·java·数据库·语言模型·llm·prompt
weiggle16 小时前
Jetpack Compose 重组机制与性能优化深度剖析
android