startActivityForResult 被弃用后,如何优雅的启动 Activity ?

相关阅读:深圳一普通中学老师工资单曝光,秒杀程序员,网友:敢问是哪个学校毕业的?

Activity Result API已经出来有一段时间了,但是还是有很多朋友对这个API感到使用不便或疑惑,今天尽量用一篇简短的文章简述下registerForActivityResult的使用方法。

1

如何解决 startActivityForResult 被弃用?

可以明显的看到,在androidx.activity1.2.0-alpha04时开始,Android中这位你调用过无数次的startActivityForResult和onActivityResult,已经被官方标记为弃用了,继而推出了名为Activity Result API的组件。

弃用原因也许是onActivityResult里需要处理的各种判断、嵌套,也许是既要处理requestCode也要处理resultCode这种高耦合难以维护的Id判断模式。但其原因已不重要了,因为既然Android里已提供了更好的方案并把startActivityForResult标记为了弃用,那么我们就应该开始了解一下位于 ComponentActivity 或 Fragment 中的registerForActivityResult了。

这里先做一个简单的对比,来了解下registerForActivityResult的简单及清爽。

// startActivityForResult
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    startActivityForResult(Intent(this, SecondActivity::class.java), REQUEST_CODE)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    when (requestCode) {
        REQUEST_CODE -> {
           val code = resultCode
           val data = data
        }
    }
}

companion object {
    private const val REQUEST_CODE = 1024
}

// registerForActivityResult
private lateinit var resultLauncher: ActivityResultLauncher<Intent>

private val launcherCallback = ActivityResultCallback<ActivityResult> { result ->
    val code = result.resultCode
    val data = result.data
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    resultLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult(),
        launcherCallback)

    resultLauncher.launch(Intent(this,SecondActivity::class.java))
}

代码一看完,是不是第一感觉不对啊,怎么感觉比之前还复杂了,其实这里是为了让你更直观的了解到这个registerForActivityResult到底是什么东西,所以对载体、定义协定、回调3个类分别定义写出来。其实大部分情况我们像以下代码其实这么写就可以了。搜索公众号互联网架构师回复“2T”,送你一份惊喜礼包。

private val launcherActivity = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()) {
    val code = it.resultCode
    val data = it.data
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    launcherActivity.launch(Intent(this, SecondActivity::class.java))
}

是不是瞬间清爽了许多,但是…你还是觉得比使用startActivityForResult更复杂?其实不然,因为上面代码的需求是一个单一的回调,所以看着似乎startActivityForResult更便于维护和使用。

但倘若编写一个稍复杂的页面,需要同时请求相册、需要在其它Activity选择数据并回调、需要判断权限等等时,继续使用startActivityForResult,会导致onActivityResult里掺杂各种嵌套及判断,导致代码难以维护。而使用registerForActivityResult()可以多次调用以注册多个 ActivityResultLauncher 实例,用来处理不同的Activity结果,让代码更便于维护。

优势了解到了,但既然需要使用新的功能,那么我们就必须要先了解一下,刚说到的ActivityResultLauncher、ActivityResultContract、ActivityResultCallback到底是些什么东西?

ActivityResultLauncher 从字面意思其实就能很好理解,可以理解它就是一个Activity的启动器,它的作用就是承载启动对象与返回对象,通过registerForActivityResult返回该对象,这时并不会立即启动另一个Activity。

ActivityResultContract 是用来协定所需的输入类型以及结果的输出类型,Android默认提供了一些常用的定义,例如上面所使用到到ActivityResultContracts.StartActivityForResult()。当然这里你也可以通过继承ActivityResultContract实现自己的定义。

ActivityResultCallback 通过名字就可以了解到这是启动Activity并返回到当前Activity时的结果回调。搜索公众号互联网架构师回复“2T”,送你一份惊喜礼包。

对于这3个类,其实只需重点了解ActivityResultContract,就能很轻松的理解并使用好Activity Result API了。

同时,引用一个官方文档的警告 ↓

注意:虽然在 fragment 或 activity 创建完毕之前可安全地调用 registerForActivityResult(),但在 fragment 或 activity 的 Lifecycle 变为 CREATED 状态之前,您无法启动 ActivityResultLauncher

2

ActivityResultContract 该如何使用?

刚才的例子中,其实已经简单的使用到Android提供的一个默认协定ActivityResultContracts.StartActivityForResult()来启动了一个Activity并获得想要的返回值。除了StartActivityForResult(),Android还提供了以下的默认协定以便于开发者的使用。

3c04e9441e56190e707365410fa3c3cb.png

以上全部ActivityResultContracts可在GitHub查看完整示例源码。

https://github.com/TxcA/ManageStartActivity/blob/master/app/src/main/java/com/itxca/sample/msa/ActivityResultContractsActivity.kt

OK,到此是不是慢慢开始感觉到Activity Result API的便捷了。搜索公众号互联网架构师回复“2T”,送你一份惊喜礼包。

虽然Android提供的默认协定ActivityResultContracts已经很丰富了,但是为了自己应用内Activity类型安全的传递或是解耦,有时我们需要自己创建一个ActivityResultContract。其实查看ActivityResultContracts任意一个类的源码,发现自己实现ActivityResultContract并不复杂,只需继承ActivityResultContract,即可实现类型安全的Activity启动与数据返回。

class ContractActivity : AppCompatActivity() {

......

    /**
     * 继承[ActivityResultContract]
     *
     * 泛型第一个参数为传入到 [ContractActivity] 的参数
     * 第二个参数为 [ContractActivity] 返回给启动Activity的返回值
     */
    class Contract : ActivityResultContract<String, String>() {
        /**
         * 创建启动Intent
         * @param context [Context]
         * @param input 当前类的第一个泛型参, 这里自己实现传递过程
         */
        override fun createIntent(context: Context, input: String): Intent =
            Intent(context, ContractActivity::class.java).apply {
                putExtra(EXTRA_NAME, "$input - ${System.currentTimeMillis()}")
            }

        /**
         * 解析结果
         * @param resultCode [Activity.setResult] 的 resultCode
         * @param intent [Activity.setResult] 的 intent
         * 其实这里类似 [Activity.onActivityResult] 处理过程,只是由于返回是明确的,所以少了requestCode
         */
        override fun parseResult(resultCode: Int, intent: Intent?): String {
            return if (resultCode == Activity.RESULT_OK && intent != null) {
                "${intent.getStringExtra(EXTRA_RESULT)} - ${System.currentTimeMillis()}"
            } else {
                "error"
            }
        }

        companion object {
            /** EXTRA */
            const val EXTRA_NAME = "extra_name"
            const val EXTRA_RESULT = "extra_result"
        }
    }
}

定义好一个Contract后,在使用Activity Result API启动ContractActivity时,只需调用:

val launcherContractActivity = registerForActivityResult(ContractActivity.Contract()) { result: String -> }

launcherContractActivity.launch("hi, Activity Result API!")

怎么样,是不是瞬间觉得特别方便?而且这种方式让启动Activity解耦得很彻底,启动方能明确的知道该传什么值给被启动的Activity,也能明确的知道被启动Activity会返回什么数据。

以上全部ActivityResultContracts可在GitHub查看完整示例源码。

https://github.com/TxcA/ManageStartActivity/blob/master/app/src/main/java/com/itxca/sample/msa/ActivityResultContractsActivity.kt

3

但是…我就想简单的使用startActivityForResult怎么办?

虽然Activity Result API非常强大与便捷,但在国内各厂商深度定制系统的情况下,权限申请操作一般我们还是会使用到第三方框架,拍照、视频录制大部分情况使用系统界面操作肯定也不适用。所以Activity Result API里,我的刚需似乎只是一个startActivityForResult那么简单, 那有更便捷的方法吗?

首先,尝试一个直接在按钮点击时创建一个ActivityResultLauncher并启动。

viewBinding.btnStartActivityForResult.setOnClickListener {
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result->
       val code = result.resultCode
       val data = result.data
     }.launch(Intent(this@ActivityResultContractsActivity, SecondActivity::class.java))
}

看着似乎很方便,又不需要提前注册,即用即回调。但是很不幸,这样使用你会收获一个IllegalStateException,异常里也说得很明白LifecycleOwner xxx is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.,必须在STARTED前调用registerForActivityResult。

看来我们必须每次都必须通过registerForActivityResult预定一个ActivityResultLauncher,在需要的时候再去launch,这…似乎还是比较麻烦。那么来看看一个ManageStartActivity小框架吧。

 ManageStartActivity - Github 一个基于Activity Result API搭建的纯粹启动Activity的小框架。

https://github.com/TxcA/ManageStartActivity

相关阅读:2T架构师学习资料干货分享

何为纯粹?就是只负责启动Acitivity,权限、拍照、录像等等虽然Activity Result API都支持,但是该框架都不去实现这些功能。原因一,刚也描述过,这些预定的Contract可能在国内大环境下兼容不一定好,或者UI及功能上过于简单不能满足实际功能需求。原因二,是若想自己调用,使用其实也很简单,只需调用Android提供的这些默认协定即可。所以ManageStartActivity只是纯粹的为了减轻使用Activity Result API启动Activity时过于繁琐的那些步骤。

只需两步,轻松实现startActivityForResult。

1、ComponentActivity、Fragment 实现 IMangeStartActivity接口,并委托给MangeStartActivity处理。

2、onCreate时调用initManageStartActivity()即可。

abstract class BaseActivity : AppCompatActivity(), IMsa by msa() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initManageStartActivity()
    }
}

推荐在BaseActivity、BaseFragment里实现 IMangeStartActivity接口, 这样就能愉快的在任意继承Base的页面中使用startActivityForResult了。搜索公众号互联网架构师回复“2T”,送你一份惊喜礼包。

// 注意实现 `IMsa by msa()`
class SampleActivity : AppCompatActivity(), IMsa by msa() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // 推荐在super.onCreate 前初始化 `initMangeStartActivity`
        // 这样就能防止还未初始化`ActivityResultLauncher`就调用`launch`导致`UninitializedPropertyAccessException`异常
        initManageStartActivity()
        super.onCreate(savedInstanceState)
    }

    /**
     * 直接启动Activity
     * 3 种方式
     */
    fun startActivity() {
        // Android习惯模式,传入KClass即可
        startActivity(MainActivity::class) {
            putExtra("key", "value")
        }

        // KClass扩展方法模式
        MainActivity::class.start {
            putExtra("key", "value")
        }

        // Intent扩展方法模式
        Intent(this, MainActivity::class.java).apply {
            putExtra("key", "value")
        }.start()
    }

    /**
     * 启动Activity并需要回调结果
     * 4 种方法
     */
    fun startActivityForResult() {
        // Android习惯模式,传入KClass即可
        startActivityForResult(MainActivity::class, {
            putExtra("key", "value")
        }) { code: Int, data: Intent? ->
            // code = resultCode
        }

        // KClass扩展方法模式
        MainActivity::class.startForResult({
            putExtra("key", "value")
        }) {code: Int, data: Intent? ->
            // code = resultCode
        }

        // Android习惯模式,传入Intent即可
        startActivityForResult(Intent(this, MainActivity::class.java).apply {
            putExtra("key", "value")
        }){ code: Int, data: Intent? ->
            // code = resultCode
        }

        // Intent扩展方法模式
        Intent(this, MainActivity::class.java).apply {
            putExtra("key", "value")
        }.startForResult { code: Int, data: Intent? ->
            // code = resultCode
        }
    }

    /**
     * Kotlin协程挂起函数
     * 4 种方式
     */
    fun startActivityForResultCoroutine() {
        lifecycleScope.launch {
            // Android习惯模式,传入KClass即可
           val (code1: Int, data1: Intent?) = startActivityForResultSync(MainActivity::class) {
               putExtra("key", "value")
           }

            // KClass扩展方法模式
            val (code2: Int, data2: Intent?) = MainActivity::class.startForResultSync {
                putExtra("key", "value")
            }

            // Android习惯模式,传入Intent即可
            val (code3: Int, data3: Intent?) = startActivityForResultSync(Intent(this@SampleActivity, MainActivity::class.java).apply {
                putExtra("key", "value")
            })

            // Intent扩展方法模式
            val (code4: Int, data4: Intent?) = Intent(this@SampleActivity, MainActivity::class.java).apply {
                putExtra("key", "value")
            }.startForResultSync()
        }
    }
}

对的,要的就是这种纯粹的startActivityForResult的感觉。

总结

Activity Result API和更优雅的使用startActivityForResult现在想必你都已经会使用了,是不是比你想象的更简单?

作者:x024

https://blog.csdn.net/hx7013/article/details/120916287

1、2T架构师学习资料干货分享
2、985副教授工资曝光

3、心态崩了!税前2万4,到手1万4,年终奖扣税方式1月1日起施行~

4、雷军做程序员时写的博客,很强大!

5、人脸识别的时候,一定要穿上衣服啊!

6、清华大学:2021 元宇宙研究报告!

7、绩效被打3.25B,员工将支付宝告上了法院,判了
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值