文章目录
- 一、背景
- 二、基本用法
- 示例:启动 Activity 并获取回传数据
- 示例:申请单个权限
- 三、核心概念
- 执行流程
- 四、注册时机
- 五、常用 Contract
- 5.1 StartActivityForResult
- 5.2 RequestPermission / RequestMultiplePermissions
- 5.3 GetContent / GetMultipleContents
- 5.4 TakePicture / CaptureVideo
- 5.4 CreateDocument / OpenDocument
- 5.5 内置 Contract 汇总
- 六、自定义 Contract
- 示例:封装 Activity 跳转
- 示例:多选图片
- 七、新旧对比
一、背景
在 Android 开发中,从当前 Activity 发起一个操作并在完成后获取返回结果是常见的需求,典型场景包括:
- 跳转到另一个 Activity,等它关闭后获取回传数据
- 向用户申请运行时权限
- 从相册或文件管理器中选择文件
- 调用系统相机拍照
在 AndroidX Activity 1.2.0 之前,这些场景依赖多套不同的 API:Activity 跳转用startActivityForResult/onActivityResult,权限申请用requestPermissions/onRequestPermissionsResult。这些 API 存在一些共性问题:
- 代码分散:发起请求和接收结果的逻辑分布在不同的方法中,不利于阅读和维护。
- requestCode 管理繁琐:需要手动维护整型常量,多模块协作时容易产生冲突。
- 回调膨胀:所有返回结果汇聚到同一个回调方法中,业务扩展后分支逻辑不断膨胀。
- API 风格不统一:Activity 跳转和权限申请使用完全不同的回调机制。
AndroidX 引入了registerForActivityResult来解决这些问题,将所有"发起—返回"模式统一为一套声明式 API。
二、基本用法
registerForActivityResult的调用形式为:
ActivityResultLauncher<I>launcher=registerForActivityResult(ActivityResultContract<I,O>contract,ActivityResultCallback<O>callback);// 触发操作launcher.launch(input);与旧 API 最直观的区别是:不再需要requestCode,也不再需要重写任何回调方法。发起和接收结果在同一个代码块中完成声明。
示例:启动 Activity 并获取回传数据
ActivityResultLauncher<Intent>launcher=registerForActivityResult(newActivityResultContracts.StartActivityForResult(),result->{if(result.getResultCode()==RESULT_OK&&result.getData()!=null){Stringvalue=result.getData().getStringExtra("key");}});launcher.launch(newIntent(this,SecondActivity.class));示例:申请单个权限
ActivityResultLauncher<String>launcher=registerForActivityResult(newActivityResultContracts.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中进行注册。
// 正确@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);launcher=registerForActivityResult(...);}// 错误privatevoidonButtonClick(){launcher=registerForActivityResult(...);// 此时生命周期已过 STARTED}原因:当 Activity 因系统内存回收被销毁后重建时,只会重新执行onCreate。如果在onCreate中注册,重建时会自动重新注册,之前在途的请求结果也不会丢失。若在事件回调中注册,重建后注册缺失,结果将无法兑现。
五、常用 Contract
AndroidX 内置了多种 Contract,覆盖常见场景。
5.1 StartActivityForResult
启动一个 Activity 并接收返回结果。最常用的 Contract。
ActivityResultLauncher<Intent>launcher=registerForActivityResult(newActivityResultContracts.StartActivityForResult(),result->{if(result.getResultCode()==RESULT_OK&&result.getData()!=null){Stringdata=result.getData().getStringExtra("extra_key");}});- 输入:
Intent - 输出:
ActivityResult(封装了resultCode和data)
5.2 RequestPermission / RequestMultiplePermissions
申请单个或多个运行时权限。
// 单个权限ActivityResultLauncher<String>singlePermissionLauncher=registerForActivityResult(newActivityResultContracts.RequestPermission(),granted->{/* granted 为 Boolean */});// 多个权限ActivityResultLauncher<String[]>multiPermissionLauncher=registerForActivityResult(newActivityResultContracts.RequestMultiplePermissions(),result->{/* result 为 Map<String, Boolean> */});RequestPermission:输入String(权限名),输出Boolean。RequestMultiplePermissions:输入String[](权限名数组),输出Map<String, Boolean>。
5.3 GetContent / GetMultipleContents
从系统文件选择器中选择文件。
// 单选ActivityResultLauncher<String>launcher=registerForActivityResult(newActivityResultContracts.GetContent(),uri->{/* uri 为 Uri? */});launcher.launch("image/*");// MIME 类型launcher.launch("video/*");launcher.launch("*/*");// 任意文件// 多选ActivityResultLauncher<String>multiLauncher=registerForActivityResult(newActivityResultContracts.GetMultipleContents(),uris->{/* uris 为 List<Uri> */});5.4 TakePicture / CaptureVideo
调用系统相机拍照或录像。
// 拍照ActivityResultLauncher<Uri>takePictureLauncher=registerForActivityResult(newActivityResultContracts.TakePicture(),success->{/* success 为 Boolean */});// 录像ActivityResultLauncher<Uri>captureVideoLauncher=registerForActivityResult(newActivityResultContracts.CaptureVideo(),success->{/* success 为 Boolean */});// 需要先准备输出文件的 UriUrioutputUri=/* 由 FileProvider 生成 */;takePictureLauncher.launch(outputUri);5.4 CreateDocument / OpenDocument
创建或打开文档。
// 创建新文件ActivityResultLauncher<String>createLauncher=registerForActivityResult(newActivityResultContracts.CreateDocument("text/plain"),uri->{/* uri 为 Uri? */});createLauncher.launch("文件名.txt");// 打开已有文件ActivityResultLauncher<String[]>openLauncher=registerForActivityResult(newActivityResultContracts.OpenDocument(),uri->{/* uri 为 Uri? */});openLauncher.launch(newString[]{"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:
classOpenTargetActivityContractextendsActivityResultContract<String,ActivityResult>{privatefinalClass<?extendsActivity>targetClass;publicOpenTargetActivityContract(Class<?extendsActivity>targetClass){this.targetClass=targetClass;}@NonNull@OverridepublicIntentcreateIntent(@NonNullContextcontext,Stringinput){Intentintent=newIntent(context,targetClass);intent.putExtra("payload",input);returnintent;}@NonNull@OverridepublicActivityResultparseResult(intresultCode,@NullableIntentdata){returnnewActivityResult(resultCode,data);}}// 使用ActivityResultLauncher<String>launcher=registerForActivityResult(newOpenTargetActivityContract(TargetActivity.class),result->{if(result.getResultCode()==RESULT_OK&&result.getData()!=null){Stringback=result.getData().getStringExtra("result");}});launcher.launch("要传递的内容");示例:多选图片
系统默认的GetMultipleContents已支持多选,这里通过自定义 Contract 来演示实现思路:
classPickMultipleImagesContractextendsActivityResultContract<Void,List<Uri>>{@NonNull@OverridepublicIntentcreateIntent(@NonNullContextcontext,Voidinput){Intentintent=newIntent(Intent.ACTION_PICK);intent.setType("image/*");intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true);returnintent;}@NonNull@OverridepublicList<Uri>parseResult(intresultCode,@NullableIntentdata){if(resultCode==RESULT_OK&&data!=null){List<Uri>uris=newArrayList<>();ClipDataclipData=data.getClipData();if(clipData!=null){for(inti=0;i<clipData.getItemCount();i++){uris.add(clipData.getItemAt(i).getUri());}}elseif(data.getData()!=null){uris.add(data.getData());}returnuris;}returnCollections.emptyList();}}七、新旧对比
| 对比维度 | 旧 API | registerForActivityResult |
|---|---|---|
| Activity 回传 | startActivityForResult+onActivityResult | StartActivityForResultContract |
| 权限申请 | requestPermissions+onRequestPermissionsResult | RequestPermissionContract |
| 文件选择 | 手动构造 Intent +onActivityResult | GetContent/OpenDocumentContract |
| 代码组织 | 发起和回调分离在两个方法 | 在同一位置声明 |
| requestCode | 手动管理整型常量 | 无 |
| 类型安全 | 回调参数无编译期约束 | 泛型保证类型匹配 |
| 可扩展性 | 需覆写 Activity/Fragment 回调方法 | 实现自定义 Contract |