文章目录
-
- 一、背景
- 二、基本用法
-
- [示例:启动 Activity 并获取回传数据](#示例:启动 Activity 并获取回传数据)
- 示例:申请单个权限
- 三、核心概念
- 四、注册时机
- [五、常用 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)
-
- [示例:封装 Activity 跳转](#示例:封装 Activity 跳转)
- 示例:多选图片
- 七、新旧对比
一、背景
在 Android 开发中,从当前 Activity 发起一个操作并在完成后获取返回结果是常见的需求,典型场景包括:
- 跳转到另一个 Activity,等它关闭后获取回传数据
- 向用户申请运行时权限
- 从相册或文件管理器中选择文件
- 调用系统相机拍照
在 AndroidX Activity 1.2.0 之前,这些场景依赖多套不同的 API:Activity 跳转用 startActivityForResult / onActivityResult,权限申请用 requestPermissions / onRequestPermissionsResult。这些 API 存在一些共性问题:
- 代码分散:发起请求和接收结果的逻辑分布在不同的方法中,不利于阅读和维护。
- requestCode 管理繁琐:需要手动维护整型常量,多模块协作时容易产生冲突。
- 回调膨胀:所有返回结果汇聚到同一个回调方法中,业务扩展后分支逻辑不断膨胀。
- 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(封装了resultCode和data)
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 |