Jetpack Activity Result API 优雅的实现页面传值

官方文档

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)
 }

4. 其它的协定

上面使用的ActivityResultContracts.StartActivityForResult是一个通用的协定,可以原封不动的通过Intent启动页面,ActivityResult接收返回值,系统还定义了好多通用的协定方便使用,下面一一简单介绍

  • StartActivityForResult 这是一个通用协定,它可接受任何 Intent 作为输入内容并返回 ActivityResult,让您能够在回调中提取 resultCodeIntent
  • RequestMultiplePermissions 用于请求一组权限
  • RequestPermission 用于请求一个权限
  • TakePicturePreview 调用MediaStore.ACTION_IMAGE_CAPTURE拍照,返回 Bitmap,可以继承它重写createIntent方法传递额外的参数
  • TakePicture 调用MediaStore.ACTION_IMAGE_CAPTURE拍照,返回Uri
  • TakeVideo 调用MediaStore.ACTION_VIDEO_CAPTURE 拍摄视频,返回Uri
  • PickContact 从通讯录APP获取联系人
  • GetContent 通过Intent.ACTION_GET_CONTENT选择一条内容,默认添加了Intent.CATEGORY_OPENABLE接收流内容,返回Uri
  • GetMultipleContents 同上,选择多条内容,需要api 18以上
  • OpenDocument 选择一个文档
  • OpenMultipleDocuments 选择一个、多个文档
  • OpenDocumentTree 打开文档树选择文档
  • CreateDocument 提示用户选择一个路径,创建新文档
    大部分系统间的交互都已经定义好了,我们可以直接使用,比如GetContent,如下:
private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
       // Handle the Intent
   }
// 选择图片
getContent.launch("image/*")
// 选择视频
getContent.launch("video/*")

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() 作为替代。

推荐阅读更多精彩内容