Jetpack:使用 ActivityResult 处理 Activity 之间的数据通信

简介: 本文先介绍ActivityResult的基本使用,最后会通过源码来探讨背后的原理。在Android中,我们如果想在Activity之间双向传递数据,需要使用startActivityForResult启动,然后在onActivityResult中处理返回,另外申请权限也是类似的步骤。但是这样的处理方式会让我们的代码变得非常复杂,并且也无法保证在 Activity 发送或接收数据时参数的类型安全。

前言


本文先介绍ActivityResult的基本使用,最后会通过源码来探讨背后的原理。

在Android中,我们如果想在Activity之间双向传递数据,需要使用startActivityForResult启动,然后在onActivityResult中处理返回,另外申请权限也是类似的步骤。

但是这样的处理方式会让我们的代码变得非常复杂,并且也无法保证在 Activity 发送或接收数据时参数的类型安全。

ActivityResult是Jetpack提供的一个功能,可以简化Activity直接的数据传递(包括权限申请)。它通过提供类型安全的 contract (协定) 来简化处理来自 Activity 的数据。这些协定为一些常见操作 (比如: 拍照或请求权限) 定义了预期的输入和输出类型,除此之外您还能够自定义协定来满足不同场景的需求。

ActivityResult API 提供了一些组件用于注册 Activity 的处理结果、发起请求以及在系统返回结果后立即进行相应处理。您也可以在启动 Activity 的地方使用一个独立的类接收返回结果,这样依然能够保证类型安全。


ActivityResult使用


使用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/*") //过滤图片
复制代码


这里涉及几个重要的类和函数:

(1)registerForActivityResult:是ComponentActivity的一个函数,注意这里的ComponentActivity是androidx.activity.ComponentActivity而不是androidx.core.app.ComponentActivity,androidx.core中的对应类(截止1.3.0)还不支持这项功能。


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>
复制代码


最简单的,相当于传统方式的startActivityForResult,只不过将onActivityResult的几个参数封装成一个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>
复制代码


通过这两个来申请权限就可以很方便的进行后续处理。


(5)TakePicturePreview


拉起拍照预览


public static class TakePicturePreview extends ActivityResultContract<Void, Bitmap>
复制代码


直接返回bitmap数据。(跟传统方式一样,这个bitmap只是一个图片预览,因为intent

中不能传输过大的数据)

注意虽然输入是Void,但是执行ActivityResultLauncher的lanch函数是还需要传入一个null才行。


(6)TakePicture


拉起拍照


public static class TakePicture extends ActivityResultContract<Uri, Boolean>
复制代码


输入图片要保存的位置uri


(7)TakeVideo


录制视频


public static class TakeVideo extends ActivityResultContract<Uri, Bitmap>
复制代码


输入视频要保存的位置uri,返回视频的缩略图。


(8)PickContact


选取联系人


public static final class PickContact extends ActivityResultContract<Void, Uri>
复制代码


(9)GetContent


获取单个文件

public static class GetContent extends ActivityResultContract<String, Uri>
复制代码

输入过滤类型,返回文件uri


(10)GetMultipleContents


文件多选


public static class GetMultipleContents extends ActivityResultContract<String, List<Uri>>
复制代码

同上


(11)OpenDocument


打开单个文档(拉起的是系统文档管理器)


@TargetApi(19)
    public static class OpenDocument extends ActivityResultContract<String[], Uri>
复制代码


对应Intent.ACTION_OPEN_DOCUMENT,输入的是类型过滤(如image/*),输出uri


(12)OpenMultipleDocuments

打开多个文档,与上面类似


(13)OpenDocumentTree

打开文档tree,对应Intent.ACTION_OPEN_DOCUMENT_TREE


(14)CreateDocument

新建一个文档,对应Intent.ACTION_CREATE_DOCUMENT

可以看到Android已经将常用的功能都封装了,基本可以满足我们的开发使用。


原理


那么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对象,而且有时机的限制,所以还不是特别灵活(尤其在权限管理这块)。



目录
相关文章
|
存储 缓存 自然语言处理
Jetpack 系列(3)—— 为什么 Activity 都重建了 ViewModel 还存在?
Jetpack 系列(3)—— 为什么 Activity 都重建了 ViewModel 还存在?
174 0
Jetpack 系列(3)—— 为什么 Activity 都重建了 ViewModel 还存在?
|
API Android开发 Kotlin
【Jetpack】学穿:Activity Results API(下)
【Jetpack】学穿:Activity Results API
328 0
|
API 开发者
【Jetpack】学穿:Activity Results API(中)
【Jetpack】学穿:Activity Results API
204 0
|
API 开发者
【Jetpack】学穿:Activity Results API(上)
【Jetpack】学穿:Activity Results API
187 0
|
Java API Android开发
【JetPack】数据绑定 DataBinding 简介 ( 使用要求 | Gradle 版本 | 定义数据类 | 定义数据绑定布局 | Activity 数据绑定 | 绑定类生成规则 )(一)
【JetPack】数据绑定 DataBinding 简介 ( 使用要求 | Gradle 版本 | 定义数据类 | 定义数据绑定布局 | Activity 数据绑定 | 绑定类生成规则 )(一)
288 0
【JetPack】数据绑定 DataBinding 简介 ( 使用要求 | Gradle 版本 | 定义数据类 | 定义数据绑定布局 | Activity 数据绑定 | 绑定类生成规则 )(一)
【JetPack】视图绑定 ( ViewBinding ) 各种应用 ( 视图绑定两种方式 | Activity 布局 | 对话框布局 | 自定义组件布局 | RecyclerView 列表布局 )
【JetPack】视图绑定 ( ViewBinding ) 各种应用 ( 视图绑定两种方式 | Activity 布局 | 对话框布局 | 自定义组件布局 | RecyclerView 列表布局 )
525 0
【JetPack】视图绑定 ( ViewBinding ) 各种应用 ( 视图绑定两种方式 | Activity 布局 | 对话框布局 | 自定义组件布局 | RecyclerView 列表布局 )
|
Java Android开发
【JetPack】为现有 Android 项目配置视图绑定 ( ViewBinding ) 模块 ( 视图绑定不影响传统布局操作 | 视图绑定类关联 Activity | 视图绑定类本质 )(一)
【JetPack】为现有 Android 项目配置视图绑定 ( ViewBinding ) 模块 ( 视图绑定不影响传统布局操作 | 视图绑定类关联 Activity | 视图绑定类本质 )(一)
193 0
【JetPack】为现有 Android 项目配置视图绑定 ( ViewBinding ) 模块 ( 视图绑定不影响传统布局操作 | 视图绑定类关联 Activity | 视图绑定类本质 )(一)
【JetPack】数据绑定 DataBinding 简介 ( 使用要求 | Gradle 版本 | 定义数据类 | 定义数据绑定布局 | Activity 数据绑定 | 绑定类生成规则 )(二)
【JetPack】数据绑定 DataBinding 简介 ( 使用要求 | Gradle 版本 | 定义数据类 | 定义数据绑定布局 | Activity 数据绑定 | 绑定类生成规则 )(二)
95 0
|
Android开发
【JetPack】为现有 Android 项目配置视图绑定 ( ViewBinding ) 模块 ( 视图绑定不影响传统布局操作 | 视图绑定类关联 Activity | 视图绑定类本质 )(二)
【JetPack】为现有 Android 项目配置视图绑定 ( ViewBinding ) 模块 ( 视图绑定不影响传统布局操作 | 视图绑定类关联 Activity | 视图绑定类本质 )(二)
167 0
|
30天前
|
存储 安全 Android开发
构建高效的Android应用:Kotlin与Jetpack的结合
【5月更文挑战第31天】 在移动开发的世界中,Android 平台因其开放性和广泛的用户基础而备受开发者青睐。随着技术的进步和用户需求的不断升级,开发一个高效、流畅且易于维护的 Android 应用变得愈发重要。本文将探讨如何通过结合现代编程语言 Kotlin 和 Android Jetpack 组件来提升 Android 应用的性能和可维护性。我们将深入分析 Kotlin 语言的优势,探索 Jetpack 组件的核心功能,并通过实例演示如何在实际项目中应用这些技术。