文章目录
- [Android registerForActivityResult详解](#Android registerForActivityResult详解)
-
- [1. 引入:旧的onActivityResult有什么问题?](#1. 引入:旧的onActivityResult有什么问题?)
-
- [1.1 传统写法:startActivityForResult + onActivityResult](#1.1 传统写法:startActivityForResult + onActivityResult)
- [1.2 这种写法有什么痛点?](#1.2 这种写法有什么痛点?)
- [2. ActivityResult API是什么?](#2. ActivityResult API是什么?)
-
- [2.1 两大核心概念](#2.1 两大核心概念)
- [2.2 基本使用](#2.2 基本使用)
- [3. 内置的ActivityResultContracts](#3. 内置的ActivityResultContracts)
- [4. 为什么说它更好?](#4. 为什么说它更好?)
-
- [4.1 更清晰的工程边界](#4.1 更清晰的工程边界)
- [4.2 类型更明确](#4.2 类型更明确)
- [4.3 生命周期感知](#4.3 生命周期感知)
- [5. 关键规则:必须在初始化时注册](#5. 关键规则:必须在初始化时注册)
-
- [5.1 为什么不能在点击时注册?](#5.1 为什么不能在点击时注册?)
- [5.2 正确写法](#5.2 正确写法)
- [6. 在单独类中接收结果](#6. 在单独类中接收结果)
- [7. 自定义Contract](#7. 自定义Contract)
- [8. 总结对比](#8. 总结对比)
- [9. 一句话总结](#9. 一句话总结)
Android registerForActivityResult详解
1. 引入:旧的onActivityResult有什么问题?
在Android开发中,页面A打开页面B并拿回结果,是一个非常常见的需求。比如:
- 从相册选一张图片回来显示
- 打开编辑资料页面,拿回修改后的用户名
- 申请权限后决定是否继续执行
1.1 传统写法:startActivityForResult + onActivityResult
java
// 旧写法
private static final int REQ_EDIT_PROFILE = 1001;
private void openEditProfile() {
Intent intent = new Intent(this, EditProfileActivity.class);
startActivityForResult(intent, REQ_EDIT_PROFILE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQ_EDIT_PROFILE && resultCode == RESULT_OK) {
String name = data.getStringExtra("name");
// 处理返回的数据
}
}
1.2 这种写法有什么痛点?
| 痛点 | 说明 |
|---|---|
| 集中式分发 | 所有结果都挤在一个onActivityResult里,用requestCode区分,业务多了代码臃肿 |
| 类型不安全 | requestCode是int常量,容易冲突;结果数据要手动解析,可能空指针 |
| 职责分散 | 发起请求在一个地方,处理结果在另一个地方,靠整数关联,维护困难 |
| 缺乏生命周期感知 | 屏幕旋转等配置变化时,结果可能丢失 |
在项目变大以后会越来越难维护:回调集中在Activity里,多个业务共用同一个onActivityResult(),requestCode容易冲突,结果类型也不够清晰。
2. ActivityResult API是什么?
ActivityResult API是AndroidX中替代onActivityResult的现代解决方案。它把"发起动作"和"接收结果"绑定在一起,并且与生命周期协作。
2.1 两大核心概念
| 概念 | 类 | 作用 |
|---|---|---|
| 协定 | ActivityResultContract<I, O> |
定义输入类型和输出类型 |
| 启动器 | ActivityResultLauncher |
负责发起动作并接收结果 |
其中I是输入类型,O是结果类型。比如打开一个Activity并拿回ActivityResult,输入是Intent,输出是ActivityResult;申请单个权限,输入是权限字符串,输出是布尔值。
2.2 基本使用
java
// 1. 注册:在Activity/Fragment初始化时注册
private final ActivityResultLauncher<Intent> editProfileLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
String name = data.getStringExtra("name");
// 处理返回的数据
}
}
);
// 2. 启动:点击按钮时调用launch
private void openEditProfile() {
Intent intent = new Intent(this, EditProfileActivity.class);
editProfileLauncher.launch(intent);
}
相比旧写法,结果处理直接和launcher绑定,不再需要全局requestCode。你看到editProfileLauncher,就能知道它发起什么动作、在哪里处理结果。
3. 内置的ActivityResultContracts
Android提供了大量内置的Contract,覆盖了常见场景:
| Contract | 输入类型 | 输出类型 | 用途 |
|---|---|---|---|
StartActivityForResult |
Intent | ActivityResult | 启动Activity获取结果 |
TakePicture |
Uri | Boolean | 拍照并保存到指定Uri |
TakePicturePreview |
Void | Bitmap? | 拍照获取缩略图 |
GetContent |
String | Uri? | 选择媒体内容(图片/视频) |
GetMultipleContents |
String | List | 选择多个媒体内容 |
RequestPermission |
String | Boolean | 申请单个权限 |
RequestMultiplePermissions |
String\[\] | Map<String, Boolean> | 申请多个权限 |
OpenDocument |
String\[\] | Uri? | 打开文档 |
java
// 打开相册选图
private final ActivityResultLauncher<String> getContent =
registerForActivityResult(new ActivityResultContracts.GetContent(),
uri -> {
if (uri != null) {
// 显示选中的图片
imageView.setImageURI(uri);
}
}
);
// 申请单个权限
private final ActivityResultLauncher<String> requestPermission =
registerForActivityResult(new ActivityResultContracts.RequestPermission(),
granted -> {
if (granted) {
// 权限已授予
}
}
);
4. 为什么说它更好?
4.1 更清晰的工程边界
不同业务可以拥有自己的launcher,不再把所有结果塞进同一个onActivityResult里。业务之间的耦合降低,删除或迁移某个功能时也更容易。
4.2 类型更明确
权限申请返回Boolean,多权限申请返回Map<String, Boolean>,选择内容返回Uri?。输入输出类型由Contract定义,不再需要手动从Intent解析。
4.3 生命周期感知
registerForActivityResult() 在注册时内部会使用 LifecycleOwner,当 Lifecycle 被销毁时会自动移除已注册的启动器,避免内存泄漏。
5. 关键规则:必须在初始化时注册
5.1 为什么不能在点击时注册?
java
// 错误写法:在点击事件里注册
button.setOnClickListener(v -> {
// 这样会导致结果回调丢失!
ActivityResultLauncher<Intent> launcher =
registerForActivityResult(...);
launcher.launch(intent);
});
原因 :registerForActivityResult() 内部会生成一个递增的key来标识回调。每次调用顺序必须保持一致,否则屏幕旋转重建Activity时,结果无法正确回调。
You must always call registerForActivityResult() in the same order for each creation of your fragment or activity to ensure that the inflight results are delivered to the correct callback.
5.2 正确写法
java
// 正确:在成员变量或onCreate中注册
public class MainActivity extends AppCompatActivity {
// 方式1:成员变量直接注册(推荐)
private final ActivityResultLauncher<Intent> launcher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> { /* 处理结果 */ }
);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 方式2:在onCreate中注册也可以
// 点击时只调用launch()
button.setOnClickListener(v -> {
launcher.launch(new Intent(this, TargetActivity.class));
});
}
}
registerForActivityResult() is safe to call before your fragment or activity is created, allowing it to be used directly when declaring member variables for the returned ActivityResultLauncher instances.
6. 在单独类中接收结果
如果希望把结果接收逻辑移到单独的类中(比如ViewModel或LifecycleObserver),可以直接使用ActivityResultRegistry。
java
public class MyLifecycleObserver implements DefaultLifecycleObserver {
private final ActivityResultRegistry registry;
private ActivityResultLauncher<String> getContent;
public MyLifecycleObserver(ActivityResultRegistry registry) {
this.registry = registry;
}
@Override
public void onCreate(@NonNull LifecycleOwner owner) {
getContent = registry.register("key", owner, new ActivityResultContracts.GetContent(),
uri -> {
// 处理选图结果
});
}
public void selectImage() {
getContent.launch("image/*");
}
}
// 在Fragment中使用
public class MyFragment extends Fragment {
private MyLifecycleObserver observer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
observer = new MyLifecycleObserver(requireActivity().getActivityResultRegistry());
getLifecycle().addObserver(observer);
}
}
使用ActivityResultRegistry API时,强烈推荐使用接受LifecycleOwner作为参数的API,因为LifecycleOwner会在Lifecycle被销毁时自动移除已注册的启动器。
7. 自定义Contract
如果内置的Contract不能满足需求,可以自定义:
java
// 自定义一个获取用户名的Contract
public class GetUsernameContract extends ActivityResultContract<Void, String> {
@Override
public Intent createIntent(Context context, Void input) {
return new Intent(context, GetUsernameActivity.class);
}
@Override
public String parseResult(int resultCode, Intent intent) {
if (resultCode == Activity.RESULT_OK && intent != null) {
return intent.getStringExtra("username");
}
return null;
}
}
// 使用
private final ActivityResultLauncher<Void> getUsername =
registerForActivityResult(new GetUsernameContract(),
username -> {
if (username != null) {
textView.setText("欢迎:" + username);
}
}
);
8. 总结对比
| 维度 | 传统方式 | ActivityResult API |
|---|---|---|
| 注册方式 | requestCode常量 | Contract + Callback |
| 结果处理 | 集中式onActivityResult | 分散式,各业务独立 |
| 类型安全 | 手动解析Intent | Contract定义输入输出类型 |
| 生命周期感知 | 需要手动处理 | 自动管理 |
| 代码组织 | 职责分散 | 发起和结果绑定 |
| 学习曲线 | 简单 | 需要理解Contract概念 |
9. 一句话总结
registerForActivityResult 就是把"启动Activity"和"接收返回结果"绑定在一起,通过Contract定义输入输出类型,通过Launcher发起请求,通过Callback接收结果------逻辑更清晰,类型更安全,而且知道你在哪个Lifecycle阶段,不会给已经销毁的界面发结果,避免内存泄漏。