Jetpack: 使用 ActivityResult 处理 Activity 之间的数据通信 | 开发者说·DTalk
The following article is from BennuCTech Author BennuC
本文原作者: BennuC,原文发布于: BennuCTech。
前言
本文先介绍 ActivityResult 的基本使用,最后会通过源码来探讨背后的原理。
在 Android 中,我们如果想在 Activity 之间双向传递数据,需要使用 startActivityForResult 启动,然后在 onActivityResult 中处理返回,另外申请权限也是类似的步骤。
但是这样的处理方式会让我们的代码变得非常复杂,并且也无法保证在 Activity 发送或接收数据时参数的类型安全。
ActivityResult 是 Jetpack 提供的一个功能,可以简化 Activity 直接的数据传递 (包括权限申请)。它通过提供类型安全的 contract (协定) 来简化处理来自 Activity 的数据。这些协定为一些常见操作 (比如: 拍照或请求权限) 定义了预期的输入和输出类型,除此之外您还能够自定义协定来满足不同场景的需求。
ActivityResult API 提供了一些组件用于注册 Activity 的处理结果、发起请求以及在系统返回结果后立即进行相应处理。您也可以在启动 Activity 的地方使用一个独立的类接收返回结果,这样依然能够保证类型安全。
ActivityResult 使用
dependencies {
// 在 https://developer.android.google.cn/jetpack/androidx/releases/activity 获得最新版本号
def activity_version = "1.2.0"
// 在 https://developer.android.google.cn/jetpack/androidx/releases/fragment 获得最新版本号
def fragment_version = "1.3.0"
implementation "androidx.activity:activity:$activity_version"
implementation "androidx.fragment:fragment:$fragment_version”
}
然后先看看最简单的使用方式,比如打开系统文件管理器选择一个图片,代码如下:
val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
// 处理返回的 Uri
}
getContent.launch("image/*") //过滤图片
这里涉及几个重要的类和函数:
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull ActivityResultContract<I, O> contract,
@NonNull ActivityResultCallback<O> callback)
可以看到这个函数接收两个参数,分别是 ActivityResultContract 和回调 ActivityResultCallback,ActivityResultContract 是封装启动所需要的各项参数 (组成 Intent,后面会细说)。函数返回 ActivityResultLauncher,可以看到后面通过他的 launch 函数就可以启动 activity。
(2) GetContent: ActivityResultContracts.GetContent 类是一个继承 ActivityResultContract 的具体实现类,封装了调用系统文件管理器的功能。Jetpack 提供了一些常用的 ActivityResultContract,比如选取图片,拍照等等,如果我们需要拉起自己的 Activity,就需要自定义一个 ActivityResultContract。
(3) launch: ActivityResultLauncher 的函数,启动 activity,代替了之前的 startActivity。
ActivityResultContract
下面我们来看看 GetContent 是如何实现的,代码如下:
public static class GetContent extends ActivityResultContract<String, Uri> {
@CallSuper
@NonNull
@Override
public Intent createIntent(@NonNull Context context, @NonNull String input) {
return new Intent(Intent.ACTION_GET_CONTENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType(input);
}
@Nullable
@Override
public final SynchronousResult<Uri> getSynchronousResult(@NonNull Context context,
@NonNull String input) {
return null;
}
@Nullable
@Override
public final Uri parseResult(int resultCode, @Nullable Intent intent) {
if (intent == null || resultCode != Activity.RESULT_OK) return null;
return intent.getData();
}
}
createIntent 就是用于将传入的参数封装成 intent,用于启动 activity,GetContent 的该函数就是封装一个打开系统文件的 Intent; parseResult 则是将返回的 intent 进行解析,整理成我们需要的格式返回,GetContent 中我们只需要返回的文件 uri 即可。
上面我们提到的回调 ActivityResultCallback,它的参数就是 parseResult 的返回值。所以如果我们自己的页面间通信,则自定义 ActivityResultContract 即可,与 GetContent 类似,根据自己的需求实现这两个函数即可,当然还可以直接使用 jetpack 提供的 StartActivityForResult (见下面) 即可。
在 Jetpack 提供的已封装好的 ActivityResultContract 有 (都是 ActivityResultContracts 的子类):
(1) StartActivityForResult
public static final class StartActivityForResult
extends ActivityResultContract<Intent, ActivityResult>
public ActivityResult(int resultCode, @Nullable Intent data)
(2) StartIntentSenderForResult
相当于 Activity.startIntentSender(IntentSender, Intent, int, int, int),与 PendingIntent 配合使用。
(3) RequestMultiplePermissions
用于批量申请权限。
public static final class RequestMultiplePermissions
extends ActivityResultContract<String[], java.util.Map<String, Boolean>>
以 Map 形式返回每个权限的情况。
(4) RequestPermission
申请单个权限
public static final class RequestPermission extends ActivityResultContract<String, Boolean>
public static class TakePicturePreview extends ActivityResultContract<Void, Bitmap>
public static class TakePicture extends ActivityResultContract<Uri, Boolean>
public static class TakeVideo extends ActivityResultContract<Uri, Bitmap>
public static final class PickContact extends ActivityResultContract<Void, Uri>
public static class GetContent extends ActivityResultContract<String, Uri>
public static class GetMultipleContents extends ActivityResultContract<String, List<Uri>>
同上
(11) OpenDocument
打开单个文档 (拉起的是系统文档管理器)
@TargetApi(19)
public static class OpenDocument extends ActivityResultContract<String[], Uri>
原理
那么 ActivityResult 的原理是什么,为什么可以这样实现?
launch 应该很好理解,就是通过 ActivityResultContract 的 createIntent 得到的 intent 去启动即可。
那么怎么实现 result 的回调的?
先看 registerForActivityResult 源码:
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultRegistry registry,
@NonNull final ActivityResultCallback<O> callback) {
return registry.register(
"activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);
}
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull ActivityResultContract<I, O> contract,
@NonNull ActivityResultCallback<O> callback) {
return registerForActivityResult(contract, mActivityResultRegistry, callback);
}
最终调用 ActivityResultRegistry (mActivityResultRegistry) 的 register 函数:
@NonNull
public final <I, O> ActivityResultLauncher<I> register(
@NonNull final String key,
@NonNull final LifecycleOwner lifecycleOwner,
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultCallback<O> callback) {
Lifecycle lifecycle = lifecycleOwner.getLifecycle();
if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
throw new IllegalStateException("LifecycleOwner " + lifecycleOwner + " is "
+ "attempting to register while current state is "
+ lifecycle.getCurrentState() + ". LifecycleOwners must call register before "
+ "they are STARTED.");
}
final int requestCode = registerKey(key);
LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key);
if (lifecycleContainer == null) {
lifecycleContainer = new LifecycleContainer(lifecycle);
}
LifecycleEventObserver observer = new LifecycleEventObserver() {
@Override
public void onStateChanged(
@NonNull LifecycleOwner lifecycleOwner,
@NonNull Lifecycle.Event event) {
if (Lifecycle.Event.ON_START.equals(event)) {
mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));
if (mParsedPendingResults.containsKey(key)) {
@SuppressWarnings("unchecked")
final O parsedPendingResult = (O) mParsedPendingResults.get(key);
mParsedPendingResults.remove(key);
callback.onActivityResult(parsedPendingResult);
}
final ActivityResult pendingResult = mPendingResults.getParcelable(key);
if (pendingResult != null) {
mPendingResults.remove(key);
callback.onActivityResult(contract.parseResult(
pendingResult.getResultCode(),
pendingResult.getData()));
}
} else if (Lifecycle.Event.ON_STOP.equals(event)) {
mKeyToCallback.remove(key);
} else if (Lifecycle.Event.ON_DESTROY.equals(event)) {
unregister(key);
}
}
};
lifecycleContainer.addObserver(observer);
mKeyToLifecycleContainers.put(key, lifecycleContainer);
return new ActivityResultLauncher<I>() {
@Override
public void launch(I input, @Nullable ActivityOptionsCompat options) {
onLaunch(requestCode, contract, input, options);
}
@Override
public void unregister() {
ActivityResultRegistry.this.unregister(key);
}
@NonNull
@Override
public ActivityResultContract<I, ?> getContract() {
return contract;
}
};
}
首先可以看到这个函数的调用是有时机限制的,需要在 Activity 的 start 生命周期之前 (包含 start) 才可以,否则会抛出异常。
往下可以看到是通过 lifecycle 这个功能实现的,为启动的 context (如 activity) 添加一个 Observer,在 Observer 中发现是在 onStart 这个事件里处理的返回。但是实际上返回是在 onActivityResult 函数中,这里就需要关注 mPendingResults,在 ActivityResultRegistry 中的 doDispatch 函数中为它赋予了数据,而 doDispatch 则被 dispatchResult 函数调用。那么在那里执行了 dispatchResult?
@MainThread
public final boolean dispatchResult(int requestCode, int resultCode, @Nullable Intent data) {
String key = mRcToKey.get(requestCode);
if (key == null) {
return false;
}
doDispatch(key, resultCode, data, mKeyToCallback.get(key));
return true;
}
private <O> void doDispatch(String key, int resultCode, @Nullable Intent data,
@Nullable CallbackAndContract<O> callbackAndContract) {
if (callbackAndContract != null && callbackAndContract.mCallback != null) {
ActivityResultCallback<O> callback = callbackAndContract.mCallback;
ActivityResultContract<?, O> contract = callbackAndContract.mContract;
callback.onActivityResult(contract.parseResult(resultCode, data));
} else {
// Remove any parsed pending result
mParsedPendingResults.remove(key);
// And add these pending results in their place
mPendingResults.putParcelable(key, new ActivityResult(resultCode, data));
}
}
答案是在 ComponentActivity 中,ComponentActivity 中持有一个 ActivityResultRegistry 的对象,即上面提到的 mActivityResultRegistry。在 ComponentActivity 的 onActivityResult 和 onRequestPermissionsResult 中都会调用 dispatchResult 函数。这样就实现了结果 (包括申请权限) 的回调。
@CallSuper
@Override
@Deprecated
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
@CallSuper
@Override
@Deprecated
public void onRequestPermissionsResult(
int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
if (!mActivityResultRegistry.dispatchResult(requestCode, Activity.RESULT_OK, new Intent()
.putExtra(EXTRA_PERMISSIONS, permissions)
.putExtra(EXTRA_PERMISSION_GRANT_RESULTS, grantResults))) {
if (Build.VERSION.SDK_INT >= 23) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
总结
通过上面的介绍可以看到 ActivityResult 其实是对之前 startActivityForResult 模式的一次封装,在简化使用的同时增加了安全性。但是我们需要提前注册回调,并生成 ActivityResultLauncher 对象,而且这一步需要 ComponentActivity 对象,而且有时机的限制,所以还不是特别灵活 (尤其在权限管理这块)。
长按右侧二维码
查看更多开发者精彩分享
"开发者说·DTalk" 面向