Android registerForActivityResult 详解

文章目录

    • 一、背景
    • 二、基本用法
    • 三、核心概念
    • 四、注册时机
    • [五、常用 Contract](#五、常用 Contract)
      • [5.1 StartActivityForResult](#5.1 StartActivityForResult)
      • [5.2 RequestPermission / RequestMultiplePermissions](#5.2 RequestPermission / RequestMultiplePermissions)
      • [5.3 GetContent / GetMultipleContents](#5.3 GetContent / GetMultipleContents)
      • [5.4 TakePicture / CaptureVideo](#5.4 TakePicture / CaptureVideo)
      • [5.4 CreateDocument / OpenDocument](#5.4 CreateDocument / OpenDocument)
      • [5.5 内置 Contract 汇总](#5.5 内置 Contract 汇总)
    • [六、自定义 Contract](#六、自定义 Contract)
    • 七、新旧对比

一、背景

在 Android 开发中,从当前 Activity 发起一个操作并在完成后获取返回结果是常见的需求,典型场景包括:

  • 跳转到另一个 Activity,等它关闭后获取回传数据
  • 向用户申请运行时权限
  • 从相册或文件管理器中选择文件
  • 调用系统相机拍照

在 AndroidX Activity 1.2.0 之前,这些场景依赖多套不同的 API:Activity 跳转用 startActivityForResult / onActivityResult,权限申请用 requestPermissions / onRequestPermissionsResult。这些 API 存在一些共性问题:

  1. 代码分散:发起请求和接收结果的逻辑分布在不同的方法中,不利于阅读和维护。
  2. requestCode 管理繁琐:需要手动维护整型常量,多模块协作时容易产生冲突。
  3. 回调膨胀:所有返回结果汇聚到同一个回调方法中,业务扩展后分支逻辑不断膨胀。
  4. API 风格不统一:Activity 跳转和权限申请使用完全不同的回调机制。

AndroidX 引入了 registerForActivityResult 来解决这些问题,将所有"发起---返回"模式统一为一套声明式 API。


二、基本用法

registerForActivityResult 的调用形式为:

java 复制代码
ActivityResultLauncher<I> launcher = registerForActivityResult(
    ActivityResultContract<I, O> contract,
    ActivityResultCallback<O> callback
);

// 触发操作
launcher.launch(input);

与旧 API 最直观的区别是:不再需要 requestCode,也不再需要重写任何回调方法。发起和接收结果在同一个代码块中完成声明。

示例:启动 Activity 并获取回传数据

java 复制代码
ActivityResultLauncher<Intent> launcher = registerForActivityResult(
    new ActivityResultContracts.StartActivityForResult(),
    result -> {
        if (result.getResultCode() == RESULT_OK && result.getData() != null) {
            String value = result.getData().getStringExtra("key");
        }
    }
);

launcher.launch(new Intent(this, SecondActivity.class));

示例:申请单个权限

java 复制代码
ActivityResultLauncher<String> launcher = registerForActivityResult(
    new ActivityResultContracts.RequestPermission(),
    granted -> {
        if (granted) {
            // 授权成功
        } else {
            // 授权被拒绝
        }
    }
);

launcher.launch(Manifest.permission.CAMERA);

可以看到,Activity 跳转和权限申请现在使用完全相同的 API 风格,代码结构一致。


三、核心概念

registerForActivityResult 围绕三个核心概念构建:

概念 类型 职责
Contract ActivityResultContract<I, O> 定义操作的类型:输入 I 是什么,输出 O 是什么,以及如何生成启动 Intent、如何解析返回结果
Callback ActivityResultCallback<O> 结果返回后的处理逻辑,参数 O 的类型由 Contract 决定
Launcher ActivityResultLauncher<I> 通过 registerForActivityResult 获得的启动器,调用 launch(I) 触发操作

执行流程

复制代码
launch(输入 I)
    ↓
Contract.createIntent(Context, I) → 生成 Intent → 打开目标组件
    ↓
目标组件关闭
    ↓
Contract.parseResult(int, Intent) → 将返回数据解析为类型 O
    ↓
Callback.onActivityResult(O) → 执行业务逻辑

四、注册时机

registerForActivityResult 必须在 Activity 生命周期进入 STARTED 状态之前调用。官方推荐在 onCreate 中进行注册。

java 复制代码
//  正确
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    launcher = registerForActivityResult(...);
}

//  错误
private void onButtonClick() {
    launcher = registerForActivityResult(...); // 此时生命周期已过 STARTED
}

原因 :当 Activity 因系统内存回收被销毁后重建时,只会重新执行 onCreate。如果在 onCreate 中注册,重建时会自动重新注册,之前在途的请求结果也不会丢失。若在事件回调中注册,重建后注册缺失,结果将无法兑现。


五、常用 Contract

AndroidX 内置了多种 Contract,覆盖常见场景。

5.1 StartActivityForResult

启动一个 Activity 并接收返回结果。最常用的 Contract。

java 复制代码
ActivityResultLauncher<Intent> launcher = registerForActivityResult(
    new ActivityResultContracts.StartActivityForResult(),
    result -> {
        if (result.getResultCode() == RESULT_OK && result.getData() != null) {
            String data = result.getData().getStringExtra("extra_key");
        }
    }
);
  • 输入:Intent
  • 输出:ActivityResult(封装了 resultCodedata

5.2 RequestPermission / RequestMultiplePermissions

申请单个或多个运行时权限。

java 复制代码
// 单个权限
ActivityResultLauncher<String> singlePermissionLauncher = registerForActivityResult(
    new ActivityResultContracts.RequestPermission(),
    granted -> { /* granted 为 Boolean */ }
);

// 多个权限
ActivityResultLauncher<String[]> multiPermissionLauncher = registerForActivityResult(
    new ActivityResultContracts.RequestMultiplePermissions(),
    result -> { /* result 为 Map<String, Boolean> */ }
);
  • RequestPermission:输入 String(权限名),输出 Boolean
  • RequestMultiplePermissions:输入 String[](权限名数组),输出 Map<String, Boolean>

5.3 GetContent / GetMultipleContents

从系统文件选择器中选择文件。

java 复制代码
// 单选
ActivityResultLauncher<String> launcher = registerForActivityResult(
    new ActivityResultContracts.GetContent(),
    uri -> { /* uri 为 Uri? */ }
);
launcher.launch("image/*");    // MIME 类型
launcher.launch("video/*");
launcher.launch("*/*");        // 任意文件

// 多选
ActivityResultLauncher<String> multiLauncher = registerForActivityResult(
    new ActivityResultContracts.GetMultipleContents(),
    uris -> { /* uris 为 List<Uri> */ }
);

5.4 TakePicture / CaptureVideo

调用系统相机拍照或录像。

java 复制代码
// 拍照
ActivityResultLauncher<Uri> takePictureLauncher = registerForActivityResult(
    new ActivityResultContracts.TakePicture(),
    success -> { /* success 为 Boolean */ }
);

// 录像
ActivityResultLauncher<Uri> captureVideoLauncher = registerForActivityResult(
    new ActivityResultContracts.CaptureVideo(),
    success -> { /* success 为 Boolean */ }
);

// 需要先准备输出文件的 Uri
Uri outputUri = /* 由 FileProvider 生成 */;
takePictureLauncher.launch(outputUri);

5.4 CreateDocument / OpenDocument

创建或打开文档。

java 复制代码
// 创建新文件
ActivityResultLauncher<String> createLauncher = registerForActivityResult(
    new ActivityResultContracts.CreateDocument("text/plain"),
    uri -> { /* uri 为 Uri? */ }
);
createLauncher.launch("文件名.txt");

// 打开已有文件
ActivityResultLauncher<String[]> openLauncher = registerForActivityResult(
    new ActivityResultContracts.OpenDocument(),
    uri -> { /* uri 为 Uri? */ }
);
openLauncher.launch(new String[]{"image/*", "application/pdf"});

5.5 内置 Contract 汇总

Contract 输入 输出 用途
RequestPermission String Boolean 单个权限申请
RequestMultiplePermissions String[] Map<String, Boolean> 多个权限申请
StartActivityForResult Intent ActivityResult 通用 Activity 启动
StartIntentSenderForResult IntentSenderRequest ActivityResult 启动 IntentSender
GetContent String(MIME) Uri? 选择单个文件
GetMultipleContents String(MIME) List<Uri> 选择多个文件
TakePicture Uri Boolean 拍照并保存
TakePicturePreview Bitmap? 拍照获取缩略图
CaptureVideo Uri Boolean 录像并保存
CreateDocument String(MIME) Uri? 创建文件
OpenDocument String[](MIME) Uri? 打开文件
OpenDocumentTree Uri? Uri? 选择目录
PickContact Uri? 选择联系人

六、自定义 Contract

当内置 Contract 无法满足需求时,可以通过继承 ActivityResultContract<I, O> 实现自定义 Contract。需要覆写两个方法:

方法 调用时机 作用
createIntent(Context, I) launch() 调用时 根据输入 I 生成启动目标组件的 Intent
parseResult(int, Intent) 目标组件关闭时 将 resultCode 和 Intent 解析为输出 O

示例:封装 Activity 跳转

将启动某个 Activity 并传递字符串的逻辑封装为 Contract,调用方无需直接构造 Intent:

java 复制代码
class OpenTargetActivityContract extends ActivityResultContract<String, ActivityResult> {

    private final Class<? extends Activity> targetClass;

    public OpenTargetActivityContract(Class<? extends Activity> targetClass) {
        this.targetClass = targetClass;
    }

    @NonNull
    @Override
    public Intent createIntent(@NonNull Context context, String input) {
        Intent intent = new Intent(context, targetClass);
        intent.putExtra("payload", input);
        return intent;
    }

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

// 使用
ActivityResultLauncher<String> launcher = registerForActivityResult(
    new OpenTargetActivityContract(TargetActivity.class),
    result -> {
        if (result.getResultCode() == RESULT_OK && result.getData() != null) {
            String back = result.getData().getStringExtra("result");
        }
    }
);

launcher.launch("要传递的内容");

示例:多选图片

系统默认的 GetMultipleContents 已支持多选,这里通过自定义 Contract 来演示实现思路:

java 复制代码
class PickMultipleImagesContract extends ActivityResultContract<Void, List<Uri>> {

    @NonNull
    @Override
    public Intent createIntent(@NonNull Context context, Void input) {
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType("image/*");
        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
        return intent;
    }

    @NonNull
    @Override
    public List<Uri> parseResult(int resultCode, @Nullable Intent data) {
        if (resultCode == RESULT_OK && data != null) {
            List<Uri> uris = new ArrayList<>();
            ClipData clipData = data.getClipData();
            if (clipData != null) {
                for (int i = 0; i < clipData.getItemCount(); i++) {
                    uris.add(clipData.getItemAt(i).getUri());
                }
            } else if (data.getData() != null) {
                uris.add(data.getData());
            }
            return uris;
        }
        return Collections.emptyList();
    }
}

七、新旧对比

对比维度 旧 API registerForActivityResult
Activity 回传 startActivityForResult + onActivityResult StartActivityForResult Contract
权限申请 requestPermissions + onRequestPermissionsResult RequestPermission Contract
文件选择 手动构造 Intent + onActivityResult GetContent / OpenDocument Contract
代码组织 发起和回调分离在两个方法 在同一位置声明
requestCode 手动管理整型常量
类型安全 回调参数无编译期约束 泛型保证类型匹配
可扩展性 需覆写 Activity/Fragment 回调方法 实现自定义 Contract

相关推荐
小孔龙3 小时前
VSync 与同步屏障:doFrame() 的优先调度
android·性能优化
kfzjw0083 小时前
Android 的一帧完整渲染流程:从 VSYNC 到 Screen 的全链路解密
android
wWYy.4 小时前
Mysql:覆盖索引
android·数据库·mysql
Kapaseker4 小时前
如果我要从 List 中获取一段元素
android·kotlin
三少爷的鞋4 小时前
一次朋友圈发布流程,理解 Kotlin 协程为什么重新定义异步代码组织方式
android
FungLeo5 小时前
Flutter/Android Release 包连不上网?AndroidManifest INTERNET 权限排查实录
android·flutter
wWYy.7 小时前
Mysql:一行数据是怎么存储的?
android·数据库·mysql
漏刻有时14 小时前
PHP GeoJSON转PNG地图渲染程序开发笔记、源码解读、问题复盘与整改方案
android·笔记·php
-SOLO-15 小时前
解决VMware 显示比例被重置的问题
android