ITmob-Ly
发布于 2023-01-02 / 472 阅读
0

onActivityResult 已弃用,使用 Androidx API 的 ActivityResultRegistry 替换

androidx ActivityResultRegistry

1. 介绍

启动另一个 Activity(无论是在自己的应用内还是从另一个应用启动),不必只是单向操作。还可以启动另一个活动并接收返回的结果。

虽然之前我们一直使用 startActivityForResult()onActivityResult() API 启动另一个 Activity 并回调,而且在所有 API 级别的 Activity 类上都可用,但官方现在强烈建议使用 androidx 的 Activity 和 Fragment 中引入的 Activity Result API - ActivityResultRegistry

2. 为什么废弃 startActivityForResult

startActivityForResultonActivityResult 有什么缺点:

  1. 从哪里发出某个请求必须发送唯一的请求代码,以防使用重复。有时会导致错误的结果
  2. 如果重新创建组件,则会丢失结果
  3. onActivityResult 回调不适用于 Fragment

为什么新的 ActivityResultRegistry 方式更好?

  1. 可以从 Fragment 中启动回调结果的 Activity,并以 Fragment 形式接收结果,而无需通过 Activity
  2. 现在不再使用易出错的 int 类型来区分请求,而是使用强类型类。
  3. 可以对单独的请求进行单独的回调,从而消除了回调中切换情况的需要。

3. 如何使用 ActivityResultRegistry

  1. 声明 ActivityResultLauncher

    lateinit var launcher : ActivityResultLauncher
    
  2. 注册 ActivityResultLauncher

    launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                // TODO use the result
    }
    

    注意:
    必须在创建完成 (onCreate 结束前) Fragment 或 Activity 之前调用 registerForActivityResult 在 Fragment 或 Activity 的生命周期达到 “已创建” 之前,无法启动另一个 Activity。

  3. 使用 ActivityResultLauncher

    launcher.launch(Intent(context, AnotherActivity::class.java)
        .putExtra(AnotherActivity.PARAM_NAME, ExtraData))
    

上面的介绍用法使用的是 StartActivityFroResult 作为协定,不需要自定义协定时可以用它来处理结果。

注意:
由于在调用 launch() 与触发 onActivityResult() 回调的两个时间点之间,您的进程和 Activity 可能会被销毁,因此,处理结果所需的任何其他状态都必须与这些 API 分开保存和恢复。

3.1 一些常用的内置协定 ActivityResultContracts

  • StartActivityForResult
  • RequestPermission
  • TakePicture
  • TakeVideo
  • GetContent
  • OpenDocument

具体用法参见官方文档,或 ActivityResultContracts 源码文件中继承 ActivitResultContracts 的子类。

3.2 自定义协定 ActivityResultContracts

除了内置的 ActivityResultContracts 外,还支持自定义协定。

// 定义一个自定义的协定,接受 String 类型的参数,并返回一个 String? 类型的结果
class CustomContract : ActivityResultContract<String, String?>() {

    override fun createIntent(context: Context, input: String): Intent {
        val intent = Intent(context, AnotherActivity::class.java)
        intent.putExtra(EXTRA_DATA, input)
        return intent
    }

    override fun parseResult(resultCode: Int, intent: Intent?): String? {
        return when (resultCode) {
            // TODO 处理打开的另一个 Activity 返回的 intent 携带的数据,并返回 String? 类型的结果
        }
    }

    companion object {
        const val EXTRA_DATA = "extra_data"
    }
}
// 自定义协定的用法
class MainActivity : ComponentActivity() {

    val resultLauncher = registerForActivityResult(CustomContract()) { result ->
        // Handle the returned result
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTextDemoTheme {
                LoginItmob {
                    resultLauncher.launch("input-data itmob.cn")
                }
            }
    }
}

@Composable
private fun LoginItmob(
    launchLogin: () -> Unit,
) {
    Button(
        onClick = launchLogin,
    ) {
        Text(text = "Login ITmob.cn")
    }
}

4. 示例

4.1 ActivityA 调用 ActivityB 并获取结果

class MainActivity : ComponentActivity() {
    // result launcher
    private val resultLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) {
        if (it.resultCode == Activity.RESULT_OK) {
            // Handle the returned result
        }
    }
}

// 调用 ActivityB
getResult.launch(Intent(context, ActivityB::class.java))

4.2 FragmentA 调用 ActivityB 并获取结果

Fragment 调用 Activity 并获取结果,是旧实现方式的一个问题,没有可用的明确实现,但新 API 可以跨 Activity 和 Fragment 提供一致的实现。

因此,只需要将示例 a 中的代码添加到我们的 Fragment 即可

4.3 启动外部组件,如启动相机获取图像

// 注册 result launcher
private val getImage = registerForActivityResult(
    ActivityResultContracts.TakePicture()
) { result ->
    // TODO 如果图像已保存到给定的 Uri 中,则返回 true。
}

// 调用相机拍照,并将其保存到给定的content-Uri 中
getImage.launch(uri)

4.4 在单独的类中接收 Activity 的结果

androidx 的 ComponentActivityFragment 类实现了 ActivityResultCaller 接口以允许使用 registerForActivityResult API,但也可以在单独的类中接收 Activity 的结果,即使该类不直接使用 ActivityResultRegistry 实现 ActivityResultCaller

例如,可能想要实现一个 LifecycleObserver 来处理注册协定以及启动 ResultLauncher:

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

5. 注意:

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

上面内容是文档中的描述,接受 LifecycleOwner 作为参数的 API 是什么意思呢?

就是使用 ActivityResultRegistryregister 方法注册结果回调时,强烈建议提供 LifecycleOwner 参数,这样在 Lifecycle 销毁时会自动移除注册的 ResultLauncher。

// 正如 ComponentActivity 的源码中,
// registerForActivityResult 方法其实也是调用的 register 方法,并将 LifecycleOwner 作为参数
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
        @NonNull final ActivityResultContract<I, O> contract,
        @NonNull final ActivityResultRegistry registry,
        @NonNull final ActivityResultCallback<O> callback) {
    return registry.register(
            "activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);
}