安卓 onActivityResult 废弃,registerForActivityResult 使用详解 ...

打印 上一主题 下一主题

主题 564|帖子 564|积分 1692

  安卓的兼容性是出了名的低,原因就在于它经常喜欢出一个版本就换一个 API。终于,连 Activity 之间数据回传的方法 onActivityResult 也废弃了。安卓官方给出的解决方案是使用 registerForActivityResult 来取代 onActivityResult。
  registerForActivityResult 的使用流程比原先的 onActivityResult 要复杂许多,但明确了之后,发现这种新的方式确实更优雅。
  为了能让读者明确,这里先回首原来 onActivityResult 的使用方式,然后再来对比讲解 registerForActivityResult。
onActivityResult 存在的问题

  为了便于阐明,这里假设 活动 A 调用了 活动 B,然后活动 B 生命结束,将效果回传至活动 A。
  在这个过程中,A 需要在调用时向 B 传递一个 请求码,如许在 B 返回时就会自动携带谁人请求码。同时 B 需要提供一个 返回码,来代表 B 中返回的具体数据 Intent 作一个分类。


  • 活动 A
  1. Intent intent = new Intent(AActivity.this, BActivity.class);
  2. startActivityForResult(intent, requestCode);
复制代码
  1. @Override
  2. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  3.     super.onActivityResult(requestCode, resultCode, data);
  4.     if (requestCode == SOME_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
  5.         if (data != null) {
  6.             String value = data.getStringExtra("key");
  7.             // 在这里处理传递过来的信息
  8.         }
  9.     }
  10. }
复制代码


  • 活动 B
  1. Intent resultIntent = new Intent();
  2. resultIntent.putExtra("key", value);
  3. setResult(Activity.RESULT_OK, resultIntent);
复制代码
  可以看出,在这个过程中,请求码 才是须要的参数,因为活动 A 有可能在不同环境下启动不同的活动,所以需要标志究竟是哪个活动返回到 A 的。而 返回码 只是一种安卓额外提供的对回传数据的分类,这个参数现实上不是须要的,因为回传数据内部本身就可以自行分类。另外,此值固定设置为 int 类型也非常不合理。
  不仅云云,onActivityResult 直接作为 Activity 的重载方法,耦合度过大。onActivityResult 会直接接受全部活动的回传业务,这是非常不合适的。正确的计划方案应该是让每一个返回至活动 A 的活动单独在一个方法中处理回传业务,如许不同的活动之间就可以非侵入解耦,而不是将它们集中在一起同一管理。
  可以看出,onActivityResult 的计划实在确实是有许多问题的,只不过这个 API 过于古老,所以广泛用了很久。
registerForActivityResult 有哪些改进

  没有任何开发者喜欢官方一出一个新版本,就照官方的指示更换一次 API。这是没有技术含量的事变。如果一个新计划没有重大突破,它也没有替代老古董的须要。registerForActivityResult 相比于 onActivityResult 做了许多计划上的改进。只管使用流程变得更复杂,但现实上好用许多。
  registerForActivityResult 使用了 责任链模式。责任链模式在许多通信框架中都有广泛使用,如 Netty 等。安卓 Activity 的回传过程如果有更复杂的业务需求,其中就涉及对数据的编码与解码。registerForActivityResult 就支持对回传数据的解码及解码之后的业务处理。这一点,它和 onActivityResult 不同,onActivityResult 是把返回码写死为 int 类型,而 registerForActivityResult 虽然也是把返回码写死为 int 类型,但它由于提供了一个 编解码器,因此支持将回传数据转化为任意的类型。
  此外,registerForActivityResult 会直接与要启动的 Activity 相绑定,这意味着,对 registerForActivityResult 来说,它不需要提供 请求码 来标志不同的 Activity。因为 registerForActivityResult 并不是 Activtity 重载方法,它支持多实例,因此可以让不同活动的回调代码相互隔离,而不是像 onActivityResult 集中在一起同一管理。
registerForActivityResult 实战

  纸上得来终觉浅,没有实战的讲解没有任何意义。这里联合具体代码来详细先容 registerForActivityResult 的使用。
registerForActivityResult 自定义使用


  • registerForActivityResult 是 Activtity 的一个方法,它可以天生一个 ActivityResultLauncher<Intent> 对象,该对象的 launch 方法可以启动一次 Activtity 调用流程。该对象可以启动一个 Activtity 并与之绑定,如许,将这个启动的 Activtity 回传时,接收回传数据的代码就无需使用 请求码 了。
    可以向 launch 提供一个实参来体现本次 Activtity 调用流程的输入。这个输入可以是任意类型,也可以为 null。
    1. // 这里的 this 指向的是一个 Activity
    2. ActivityResultLauncher<Intent> activityLauncher = this.registerForActivityResult(...);
    3. activityLauncher.launch(input);
    复制代码
  • 然后,当需要接收处理回传数据时,可以在 registerForActivityResult(...) 中提供回调来处理。
    其中,registerForActivityResult 接受两个参数,第一个就是笔者前面提到的 编解码器,第二个就是解码之后的业务处理。
    (但是,contract 在英语的意思并不是解码与解码。虽然这里就现实使用来说,意译成 编解码器 会更加望文生义一些,但直译成 协议 可能更符合翻译界的 信达雅 规则。因此,本文后面将使用 协议 一词,但现实上这里的 contract 是一种 编解码器。)
    1. @NonNull
    2. @Override
    3. public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
    4.         @NonNull ActivityResultContract<I, O> contract,
    5.         @NonNull ActivityResultCallback<O> callback) {
    6.     return registerForActivityResult(contract, mActivityResultRegistry, callback);
    7. }
    复制代码
    可以看出,上面的形参 contract 就是 协议,它有两个泛型形参,第一个代表输入,第二个代表输出。输入就是前面向 launch 方法提供的输入。而输出则会向形参 callback 传递。
    而形参 callback 就是解码之后的业务处理,它有一个泛型形参,代表从 contract 解码之后的可操作数据。
  • 自定义一个 协议 ActivityResultContract<I, O>,它有两个抽象方法,其中,方法 createIntent 会在启动一个新 Activity 之前被调用,它代表对输入数据的编码。因此,它的返回值被固定为 Intent,代表需要启动的 Activity。
    方法 parseResult 则会在启动的谁人 Activity 结束,返回到原 Activity 时调用,它代表对输出数据的解码。因此,它也有一个为 Intent 类型的形参。而另一个形参 resultCode,则是启动的谁人 Activity 在结束前设置的,这一点与原先使用 onActivityResult 时是一样的。
    可以看出,整个过程不需要 请求码,因为 ActivityResultLauncher 已经与启动的 Activity 举行了绑定,所以无需使用 请求码 将不同的 Activity 分开。
    1. abstract class ActivityResultContract<I, O> {
    2.     abstract fun createIntent(context: Context, input: I): Intent
    3.     abstract fun parseResult(resultCode: Int, intent: Intent?): O
    4.    
    5.     // ...省略其它内容...
    6. }
    复制代码
    下面假设前面向 launch 方法提供的输入为 Integer 类型,而输出设置为 String 类型。这里,先用方法 createIntent 启动了一个新 Activity,然后在方法 parseResult 中对回传数据举行解码处理。
    1. import android.content.Context;
    2. import android.content.Intent;
    3. import androidx.activity.result.contract.ActivityResultContract;
    4. public class ActivityResultHandler extends ActivityResultContract<Integer, String> {
    5.    
    6.     @NonNull
    7.     @Override
    8.     public Intent createIntent(Context context, Integer input) {
    9.         // 设置要启动的 Activity
    10.         Intent intent = new Intent(context, BActivity.class);
    11.         intent.putExtra("INPUT_VALUE", input);
    12.         return intent;
    13.     }
    14.    
    15.     @NonNull
    16.     @Override
    17.     public String parseResult(int resultCode, Intent intent) {
    18.         // 处理回传数据解码业务
    19.         if (resultCode == RESULT_OK && intent != null) {
    20.             return intent.getStringExtra("MESSAGE");
    21.         }
    22.         return null;
    23.     }
    24. }
    复制代码
  • 自定义解码之后的业务处理。它有一个名称看起来很认识的方法 onActivityResult,它的形参正是上面 协议 中输出的内容。
    1. public interface ActivityResultCallback<O> {
    2.     void onActivityResult(@SuppressLint("UnknownNullness") O result);
    3. }
    复制代码
  • 如许,被启动的谁人 Activity 就可以使用 setResult 传递回传数据了。这个步调与原先使用 onActivityResult 时是一样的。
    1. Intent data = new Intent();
    2. data.putExtra("MESSAGE", "...回传数据...");
    3. setResult(RESULT_OK, data);
    4. finish();
    复制代码
  • 上面的代码也可以组合起来成一次函数调用。
    1. this.registerForActivityResult(
    2.                 new ActivityResultContract<Integer, String>() {
    3.                     @NonNull
    4.                     @Override
    5.                     public Intent createIntent(Context context, Integer input) {
    6.                         // 设置要启动的 Activity
    7.                         Intent intent = new Intent(context, BActivity.class);
    8.                         intent.putExtra("INPUT_VALUE", input);
    9.                         return intent;
    10.                     }
    11.                     @NonNull
    12.                     @Override
    13.                     public String parseResult(int resultCode, Intent intent) {
    14.                         // 处理回传数据解码业务
    15.                         if (resultCode == RESULT_OK && intent != null) {
    16.                             return intent.getStringExtra("MESSAGE");
    17.                         }
    18.                         return null;
    19.                     }
    20.                 },
    21.                 result -> {
    22.                     // 处理回传数据业务
    23.                 })
    24.         .launch(0); // 传入输入参数 input 值
    复制代码
registerForActivityResult 开箱即用

  虽然上面的流程对于笔者这种技术栈较广的老将来说,明确起来不是很有难度,但对有些新手来说,却有点不太友好。为此,安卓官方在 ActivityResultContracts 下提供了一些开箱即用的 协议,它使得 registerForActivityResult 用起来就像原先的 onActivityResult 一样。
StartActivityForResult

  StartActivityForResult 是最平凡的 协议,它使得 registerForActivityResult 用起来就像原先的 onActivityResult 一样。
  1. ActivityResultLauncher<Intent> activityLauncher = this.registerForActivityResult(
  2.         new ActivityResultContracts.StartActivityForResult(),
  3.         result -> {
  4.             // 处理回传数据业务
  5.         });
  6. Intent intent = new Intent(AActivity.this, BActivity.class);
  7. activityLauncher.launch(intent);
复制代码
GetContent

  GetContent 可以用于与安卓系统自带应用之间的交互。例如,下面的代码演示了打开系统相册并选择图片的方法。
  1. ActivityResultLauncher<Intent> activityLauncher = this.registerForActivityResult(
  2.         new ActivityResultContracts.GetContent(),
  3.         uri -> {
  4.             // 处理图片回传
  5.         });
  6. activityLauncher.launch("image/*");
复制代码
跋文

注意事项



  • 当 Activity 返回时,registerForActivityResult 的回调 callback 会先于 Activity 的方法 onResume 被调用。这与原先被废弃的 onActivityResult 是一样的。
  • registerForActivityResult 需要在生命周期方法 onCreate 及之前调用。也就是说,registerForActivityResult 方法只能在类字段和 onCreate 方法中使用。不能随意调用 registerForActivityResult 来设置 Activity 在返回之后的举动,否则会导致如下报错。
    1. java.lang.IllegalStateException: LifecycleOwner XXX is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.
    2.         at androidx.activity.result.ActivityResultRegistry.register(ActivityResultRegistry.java:123)
    3.         at androidx.activity.ComponentActivity.registerForActivityResult(ComponentActivity.java:833)
    4.         at androidx.activity.ComponentActivity.registerForActivityResult(ComponentActivity.java:842)
    复制代码
附录



  • 安卓官方 registerForActivityResult 使用阐明:https://developer.android.google.cn/training/basics/intents/result?hl=zh-cn

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

去皮卡多

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表