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

相关推荐
美狐美颜sdk10 小时前
直播APP开发技术栈详解:视频美颜SDK、人脸识别与实时渲染
android·人工智能·音视频·美颜sdk·直播美颜sdk
奈斯先生Vector16 小时前
当模型版本不断变化,RelayRouter 能否帮助 AI 应用摆脱深度绑定
android·java·人工智能·开源·aigc
TimeFine16 小时前
让强模型做“总工”,让高性价比模型写代码
android
又见情义17 小时前
RK3568 Android 13 屏蔽 healthd 电池日志经验分享
android
古法安卓21 小时前
Android-Fork 机制详解
android·java·android studio
Android打工仔21 小时前
不要在 Data 层随意把 Cold Flow 转换成 Hot Flow
android·架构·kotlin
峥嵘life21 小时前
2026免费的 opencode 使用分享:Windows端 + 服务器CLI 实战总结
android·大数据·开发语言
杉氧1 天前
拒绝重复造轮子:我写了一个生产级的 Kotlin 协程与 Flow 工具库(CoroutineKit)
android·kotlin·workflow
恋猫de小郭1 天前
Flutter GSoC 2026 提案进度解读,补上 DevTools、FFI 和原生平台的关键缺口
android·前端·flutter
2501_915909061 天前
怎么用 FlutterFlow 把应用发布到 App Store?
android·ios·小程序·https·uni-app·iphone·webview