Android ActivityResult API 密法

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

在这里插入图片描述

里面有个很关键的类: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用于简化代码调用。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值