「Android」从其他Activity获取结果:registerForActivityResult()
简介
- Activity Result APIs 可以取代 startActivityForResult 方法,去启动Activity以获取结果
- Activity Result APIs 可以取代 requestPermissions 方法,去请求运行时权限
背景
启动一个 activity(无论是本应用中的 activity 还是其他应用中的 activity)不一定是单向操作,也可以启动另一个 activity 并接收返回的结果。
常见的场景是调用系统相机、调用相册获取照片、调用通讯录、获取部分特殊权限等,传统方式通常是通过 Intent 携带数据,然后使用 startActivityForResult 方法来启动下一个 Activity,然后通过 onActivityResult 来接收返回的数据。
传统方式的问题在于:
- 在启动 activity 以获取结果时,可能会出现进程和 activity 因内存不足而被销毁的情况
- onActivityResult 回调方法嵌套耦合严重,逻辑混乱导致难以维护
为启动Activity获取的结果注册结果回调
基于上述存在问题,Activity Result APIs 将启动 Activity 的结果回调,与启动 Activity 的逻辑进行了分离。
位于 ComponentActivity 或 Fragment 中时,Activity Result API 提供了 registerForActivityResult() API,用于注册结果回调。
registerForActivityResult() 接受 ActivityResultContract 和 ActivityResultCallback 作为参数,并返回 ActivityResultLauncher,用来启动另一个 activity,其中:
- ActivityResultContract 定义生成结果所需的输入类型以及结果的输出类型。这些 API 可为拍照和请求权限等基本 intent 操作提供默认协定,同时还可以创建自定义协定。
- ActivityResultCallback 是单一方法接口,带有 onActivityResult() 方法,可接受 ActivityResultContract 中定义的输出类型的对象:
ActivityResultLauncher mGetContent = registerForActivityResult(new GetContent(),
new ActivityResultCallback() {
@Override
public void onActivityResult(Uri uri) {
// Handle the returned Uri
}
});
启动Activity以获取结果
registerForActivityResult() 仅为启动 Activity 获取的结果注册结果回调,但它本身不会并启动另一个 activity 并发出结果请求。启动 Activity 的操作由 registerForActivityResult() 返回的 ActivityResultLauncher 的实例对象负责。
如果存在输入参数,ActivityResultLauncher 的实例对象会根据输入参数去匹配 ActivityResultContract 的类型。调用ActivityResultLauncher 的实例对象的 launch() 方法,会启动 Activity 并获取结果。当用户完成后续 activity 并返回时,系统将执行 ActivityResultCallback 中的 onActivityResult() 方法。
ActivityResultLauncher mGetContent = registerForActivityResult(new GetContent(),
new ActivityResultCallback() {
@Override
public void onActivityResult(Uri uri) {
// Handle the returned Uri
}
});
@Override
public void onCreate(@Nullable savedInstanceState: Bundle) {
// ...
Button selectButton = findViewById(R.id.select_button);
selectButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
// Pass in the mime type you'd like to allow the user to select
// as the input
mGetContent.launch("image/*");
}
});
}
注意:由于在调用 launch() 与触发 onActivityResult() 回调的两个时间点之间,进程和 activity 可能会被销毁,因此,处理结果所需的任何其他状态,都必须与这些 API 分开保存和恢复。
预定义Contract
在 Activity Result APIs 中提供了一系列预定义 Contract 供开发者去分别启动Activity以获取结果和请求运行时权限:
- StartActivityForResult:通用的Contract,不做任何转换,Intent作为输入,ActivityResult作为输出,这也是最常用的一个协定。
- RequestMultiplePermissions:用于请求一组权限
- RequestPermission:用于请求单个权限
- TakePicturePreview:调用MediaStore.ACTION_IMAGE_CAPTURE拍照,返回值为Bitmap图片
- TakePicture:调用MediaStore.ACTION_IMAGE_CAPTURE拍照,并将图片保存到给定的Uri地址,返回true表示保存成功。
- TakeVideo:调用MediaStore.ACTION_VIDEO_CAPTURE 拍摄视频,保存到给定的Uri地址,返回一张缩略图。
- PickContact:从通讯录APP获取联系人
- GetContent:提示用选择一条内容,返回一个通过ContentResolver#openInputStream(Uri)访问原生数据的Uri地址(content://形式) 。默认情况下,它增加了 Intent#CATEGORY_OPENABLE, 返回可以表示流的内容。
- CreateDocument:提示用户选择一个文档,返回一个(file:/http:/content:)开头的Uri。
- OpenMultipleDocuments:提示用户选择文档(可以选择多个),分别返回它们的Uri,以List的形式。
- OpenDocumentTree:提示用户选择一个目录,并返回用户选择的作为一个Uri返回,应用程序可以完全管理返回目录中的文档。
使用以上预定义 Contract 进行开发的经典例子:
StartActivityForResult
ActivityResultLauncher activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() {
@Override
public void onActivityResult(ActivityResult result) {
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
activityResultLauncher.launch(intent);
}
RequestMultiplePermissions
ActivityResultLauncher activityResultLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback
新旧对比:onActivityResult & Activity Result APIs
旧:
public void openSomeActivityForResult() {
Intent intent = new Intent(this, SomeActivity.class);
startActivityForResult(intent, 123);
}
@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK && requestCode == 123) {
doSomeOperations();
}
}
新:
// You can do the assignment inside onAttach or onCreate, i.e, before the activity is displayed
ActivityResultLauncher someActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Intent data = result.getData();
doSomeOperations();
}
}
});
public void openSomeActivityForResult() {
Intent intent = new Intent(this, SomeActivity.class);
someActivityResultLauncher.launch(intent);
}
参考
https://segmentfault.com/a/11...
https://developer.android.com...