使用registerForActivityResult替代onActivityResult

1. 老的实现方式

日常开发中,实现页面传值,通常通过startActivityForResult和onActivityResult配合,通过判断requestCode处理不同的业务场景:

startActivityForResult(intent, requestCode)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if(resultCode == Activity.RESULT_OK && requestCode == 100) {
           // 处理页面返回的参数
        }
}

2. 新Activity Result API

private val startForResult =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
            if (result.resultCode == Activity.RESULT_OK) {
                //页面返回值
                val data = result.data
            }
        }
// 打开页面
startForResult.launch(Intent(this, ActivityB::class.java))

上面的实现是不是简单明了,直接注册回调实现业务逻辑,有多个传值的业务就定义多个回调协定(ActivityResultContract),可以很好的单独处理,不用在onActivityResult里面通过requestCode判断,非常方便的实现业务解耦。

3.如何使用

3.1 添加依赖,Activity Result API是在Androidx Activity和Fragment中引入的

最新版本

    implementation "androidx.activity:activity-ktx:1.2.2"
    implementation "androidx.fragment:fragment-ktx:1.3.2"

3.2 注册协定,获取ActivityResultLauncher

private val selectData =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                val data = result.data
            }
        }

3.3 构造Intent传递参数,启动页面

 btnA.setOnClickListener {
       val intent = Intent(this, ActivityB::class.java)
       selectData.launch(intent)
 }

 启动一个activity需要一个laucher,这个laucher由registerForActivityResult返回,这个方法需要两个参数,一个参数为一个抽象类——ActivityResultContract<I, O>的实现,另一个参数是一个函数式接口的实现(用一个lambda表达式来代替)。

public abstract class ActivityResultContract<I, O> {

    /** Create an intent that can be used for {@link Activity#startActivityForResult} */
    public abstract @NonNull Intent createIntent(@NonNull Context context,
            @SuppressLint("UnknownNullness") I input);

    /** Convert result obtained from {@link Activity#onActivityResult} to O */
    @SuppressLint("UnknownNullness")
    public abstract O parseResult(int resultCode, @Nullable Intent intent);

第一个方法是配置结合input配置intent, 第二个方法是从intent取出数据返回。

第二个参数是对从intent取出的数据的处理。

因此:官方为我们提供了很多便捷的实现好的第一个参数

4. 已有的协定

预定义的Contract

大伙都看出来,新的Activity Results API使用起来好像有点麻烦,每次都得定义Contract。Google肯定考虑到了这个问题的,于是,Google 预定义了很多Contract,把你们能想到的使用场景基本上都想到了,它们都定义在类ActivityResultContracts中,有以下这些Contract:

StartActivityForResult() 
RequestMultiplePermissions()
RequestPermission()
TakePicturePreview()
TakePicture()
TakeVideo()
PickContact()
CreateDocument()
OpenDocumentTree()
OpenMultipleDocuments()
OpenDocument()
GetMultipleContents()
GetContent()

下面分别介绍一下这些Contract:

StartActivityForResult: 通用的Contract,不做任何转换,Intent作为输入,ActivityResult作为输出,这也是最常用的一个协定。

RequestMultiplePermissions: 用于请求一组权限

RequestPermission: 用于请求单个权限

TakePicturePreview: 调用MediaStore.ACTION_IMAGE_CAPTURE拍照,返回值为Bitmap图片

TakePicture: 调用MediaStore.ACTION_IMAGE_CAPTURE拍照,并将图片保存到给定的Uri地址,返回true表示保存成功。

TakeVideo: 调用MediaStore.ACTION_VIDEO_CAPTURE 拍摄视频,保存到给定的Uri地址,返回一张缩略图。

PickContact: 从通讯录APP获取联系人

GetContent: 提示用选择一条内容,返回一个通过ContentResolver#openInputStream(Uri)访问原生数据的Uri地址(content://形式) 。默认情况下,它增加了Intent#CATEGORY_OPENABLE, 返回可以表示流的内容。

CreateDocument: 提示用户选择一个文档,返回一个(file:/http:/content:)开头的Uri。

OpenMultipleDocuments: 提示用户选择文档(可以选择多个),分别返回它们的Uri,以List的形式。

OpenDocumentTree: 提示用户选择一个目录,并返回用户选择的作为一个Uri返回,应用程序可以完全管理返回目录中的文档。

 具体可参考:https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts

比如说,打开联系人列表选择一个联系人的ActivityResultContracts的实现为:

/**
     * An {@link ActivityResultContract} to request the user to pick a contact from the contacts
     * app.
     * <p>
     * The result is a {@code content:} {@link Uri}.
     *
     * @see ContactsContract
     */
    public static final class PickContact extends ActivityResultContract<Void, Uri> {

        @NonNull
        @Override
        public Intent createIntent(@NonNull Context context, @Nullable Void input) {
            return new Intent(Intent.ACTION_PICK).setType(ContactsContract.Contacts.CONTENT_TYPE);
        }

        @Nullable
        @Override
        public Uri parseResult(int resultCode, @Nullable Intent intent) {
            if (intent == null || resultCode != Activity.RESULT_OK) return null;
            return intent.getData();
        }
    }

附上一个实例:

private val aLauncher =
        registerForActivityResult(ActivityResultContracts.PickContact()) { uri: Uri? ->
            // Handle the returned Uri
            //crimesuspect启动activity的注册laucher
            val queryFields = arrayOf(ContactsContract.Contacts.DISPLAY_NAME)
            val cursor =
                uri?.let {
                    requireActivity().contentResolver.query(
                        it,
                        queryFields,
                        null,
                        null,
                        null
                    )
                }
            cursor?.use {
                if (it.count > 0) {
                    it.moveToFirst()
                    val suspect = it.getString(0)
                    crime.suspect = suspect
                    crimeDetailViewModel.saveCrime(crime)
                    binding.crimeSuspect.text = suspect
                }
            }
        }设置监听器的时候:binding.crimeSuspect.setOnClickListener {    aLauncher.launch()}

4.自定义协定

除了上面的预定义协定,我们还可以根据实际的业务定义自己的协定,当然实现也很简单,只需要继承ActivityResultContract<I, O>抽象类即可,ActivityResultContract类有两个抽象方法,

//创建Intent,在Activity#startActivityForResult中使用I input是定义的范型
public abstract Intent createIntent(Context context, I input)
//解析Activity#onActivityResult中的返回结果,返回范型O
public abstract O parseResult(int resultCode, Intent intent)

下面实现一个例子:

// 定义协定
class MyActivityResultContract : ActivityResultContract<Int, String>() {
   override fun createIntent(context: Context, input: Int?): Intent {
       return Intent(context, ActivityB::class.java).apply {
           putExtra("projectId", input)
       }
   }

   override fun parseResult(resultCode: Int, intent: Intent?): String? {
       if (intent == null || !intent.hasExtra("projectName")) {
           return null
       }
       return intent.getStringExtra("projectName").orEmpty()
   }
}

//注册协定
private val getProject = registerForActivityResult(MyActivityResultContract()) {
      //it 是返回值
}
//启动页面
getProject.launch(23)

5.非Activity、Fragment页面接收协定结果

一般的onActivityResult只能在Activity类处理返回结果,处理业务逻辑,增加了代码的耦合性,同时有很多场景希望在自己的类里面接收结果,处理逻辑,比如在Adapter里面响应点击事件,处理返回结果,下面介绍如何在业务类实现打开新页面,处理返回结果。

页面的启动管理是通过ActivityResultRegistry实现的,Activity里面已经定义好了ActivityResultRegistry方便我们使用,在自己的类里面只需要传递过来就可以处理跳转逻辑了。

class MyLifecycleObserver(private val registry : ActivityResultRegistry)
       : DefaultLifecycleObserver {
   lateinit var getContent : ActivityResultLauncher<String>

   override fun onCreate(owner: LifecycleOwner) {
       getContent = registry.register("key", owner, GetContent()) { uri ->
           // Handle the returned Uri
       }
   }

   fun selectImage() {
       getContent.launch("image/*")
   }
}

class MyFragment : Fragment() {
   lateinit var observer : MyLifecycleObserver

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

       observer = MyLifecycleObserver(requireActivity().activityResultRegistry)
       lifecycle.addObserver(observer)
   }

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
       val selectButton = view.findViewById<Button>(R.id.select_button)

       selectButton.setOnClickListener {
           // Open the activity to select an image
           observer.selectImage()
       }
   }
}

使用 ActivityResultRegistry API 时,强烈建议您使用可接受 LifecycleOwner 作为参数的 API,因为 LifecycleOwner 会在 Lifecycle 被销毁时自动移除已注册的启动器。不过,如果 LifecycleOwner 不存在,每个 ActivityResultLauncher 类都允许您手动调用 unregister() 作为替代。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值