registerForActivityResult的学习

4,461 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

概述

一般情况下,我们在进行Activity之间的跳转的时候,如果需要被启动的Activity能够在工作完成后向之前的Activity中传递数据,就会使用startActivityForResult(intent: Intent,requestCode: Int)去启动这个Activity,然后在当前ActivityonActivityResult()中获取返回的数据,再做后续的操作。但是今天在使用这个方法的时候,AS提示这个方法已经被弃用,取而代之的是推荐使用registerForActivityResult(ActivityResultContract, ActivityResultCallback)这个方法去做之前的那种操作,这篇笔记主要是记录一下这个方法的作用和在使用过程中遇到的问题。

registerForActivityResult(ActivityResultContract, ActivityResultCallback)这个方法要求我们传递ActivityResultContractActivityResultCallback两个参数,同时会返回给我们一个ActivityResultLauncher对象,我们可以使用这个对象去进行Activity跳转。

ActivityResultContract

从代码中的注释可以看出,这是一个抽象类,主要是用于定义在Activity跳转前后的输入数据类型和输出数据类型,下面是这个类提供的几个方法的简单介绍:

createIntent()

这个方法包含Contextinput两个参数,input参数的类型就是我们定义的泛型中的输入参数类型,要求我们返回一个Intent,一般情况下我们可以指定输入的参数类型即为Intent,这样这个方法就可以直接返回输入的数据,如下是ActivityResultContracts中的StartActivityForResult中的实现:

        @NonNull
        @Override
        public Intent createIntent(@NonNull Context context, @NonNull Intent input) {
            return input;
        }

可以看到,这个类的实现就是输入类型就是Intent,并将这个数据直接返回。当然我们完全可以定义任意的数据类型,只不过需要在这里处理一下,创建一个Intent并将设置的输入数据设置进去,下面是我们实现的这个方法:

            override fun createIntent(context: Context, input: String): Intent =
                Intent(context, OtherActivity::class.java).apply{
                    this.putExtra(key,input)
                }

在我们自己的实现中,就是会传递进来一个String类型的参数,然后我们会在这里创建一个Intent,之后将传递进来的值设置到这个Intent中,在OtherActivity这个页面中就可以获取到这个数据了。

parseResult()

这个方法主要是处理页面返回之后的操作,这个方法包含了resultCode: Intintent: Intent这两个参数,一般情况下我们需要在页面返回之后获取上个页面退出时携带的数据,这个时候我们就可以通过resultCode来判断上个页面的操作结果,以及从Intent中获取上个页面退出时携带的数据。

一般情况下我们可以定义一个类,然后将数据处理成这个类的对象并返回,下面是ActivityResultContracts中的StartActivityForResult中的实现:

        @NonNull
        @Override
        public ActivityResult parseResult(
                int resultCode, @Nullable Intent intent) {
            return new ActivityResult(resultCode, intent);
        }

可以看到,这里就是创建了一个ActivityResult对象,并将这个对象返回。

如果我们从上个页面返回的时候携带的数据不多,我们也可以直接返回这个数据,比如我们在上个页面退出时会携带一个String类型的参数,那么我们的实现如下:

        override fun parseResult(resultCode: Int, intent: Intent?): String
        = intent?.getStringExtra(key) ?: ""

这里我们就直接将上个页面数据获取并返回。

ActivityResultCallback

这是一个接口,当我们从上个退出的时候会收到这个回调,这个接口中只有一个方法:onActivityResult(O result),其中O就是我们定义的从上个页面返回的时候需要携带的数据类型。上面的parseResult()方法处理后的数据就是提交给了onActivityResult(),所以我们可以在这个方法中执行后续的操作。比如在我们的实现中,我们需要上个页面传递过来一个String类型的数据,那么在上个页面退出之后我们从这个接口中就可以获取到相应的数据然后执行操作:

            {
            if (!TextUtils.isEmpty(it)) {
                //请求这个value对应的数据
                requestData(it)
            }

ActivityResultLauncher

当我们执行了registerForActivityResult()这个方法的时候,这个方法会返回一个ActivityResultLauncher对象,这个类也是一个抽象类,这个类中指定了需要实现launch(input I)方法,这个方法就是我们在真正的页面跳转的时候所使用的。当然,registerForActivityResult()方法已经帮我们有了一个默认的实现,我们只需要调用其中的launch()方法即可。

下面的代码演示了我们在页面跳转的时候执行的操作,如上面代码所示,我们在跳转的时候需要携带一个String类型的参数,因此我们会将这个数据设置到launch(value)中去,方便createIntent()能够正确携带参数:

    mToOtherActivity.launch("xxx")

如上所言,我们可以指定任意的输入类型,只要的launch()方法执行的时候成功设置进去即可。

ActivityResultContracts

这个类中定义了很多我们常用的操作的ActivityResultContract,比如申请权限,打开相册,打开文档等,我们可以根据自己的需要调用其中不同的类,如下所示:

普通的Activity跳转

如上面的例子,如果我们想要在Activity退出的时候携带一些数据,我们就可以使用其中的StartActivityForResult类,如下所示:

        //注册回调
        val toOtherActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
            Logs.i(tag,"result code is:${it.resultCode}")
            val value = it.data?.getStringExtra("key")
            Logs.i(tag,"value is $value")
        }
        
        //跳转的时候使用
        val intent = Intent(this,AddBillActivity::class.java)
        intent.putExtra("key","value")
        toOtherActivity.launch(intent)

权限申请

这个类中同样提供了用于权限申请的类型,我们可以使用它执行权限申请,如下所示:

        //请求精确定位权限
        registerForActivityResult(ActivityResultContracts.RequestPermission()){
            Logs.i(tag,"权限请求成功:$it")
        }.launch(Manifest.permission.ACCESS_FINE_LOCATION)

当我们在需要位置信息的时候可以通过这个方式去获取定位权限,在ActivityResultCallback可以直接返回权限是否申请成功。

还有更多的跳转类型可以直接查看这个类中的代码找到。

遇到的问题

我们必须在ActivityonResume()方法执行之前设置回调,不能在需要的时候设置回调再去使用,一般情况下在onCreate()中注册回调,在需要时候调用注册回调的launch()方法即可,就如同上文中的"普通的Activity跳转"所写的那样。否则可能出现如下的异常:

 java.lang.IllegalStateException: LifecycleOwner XXXActivity@7c57d7 is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.