目录

前言

ActivityResult使用

ActivityResultContract

原理

总结


前言

本文先介绍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@Overridepublic Intent createIntent(@NonNull Context context, @NonNull String input) {return new Intent(Intent.ACTION_GET_CONTENT).addCategory(Intent.CATEGORY_OPENABLE).setType(input);}@Nullable@Overridepublic final SynchronousResult<Uri> getSynchronousResult(@NonNull Context context,@NonNull String input) {return null;}@Nullable@Overridepublic 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 StartActivityForResultextends 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 RequestMultiplePermissionsextends 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@Overridepublic 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@Overridepublic final <I, O> ActivityResultLauncher<I> registerForActivityResult(@NonNull ActivityResultContract<I, O> contract,@NonNull ActivityResultCallback<O> callback) {return registerForActivityResult(contract, mActivityResultRegistry, callback);}

最终调用ActivityResultRegistry(mActivityResultRegistry)的register函数:

@NonNullpublic 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() {@Overridepublic 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>() {@Overridepublic void launch(I input, @Nullable ActivityOptionsCompat options) {onLaunch(requestCode, contract, input, options);}@Overridepublic void unregister() {ActivityResultRegistry.this.unregister(key);}@NonNull@Overridepublic ActivityResultContract<I, ?> getContract() {return contract;}};}

首先可以看到这个函数的调用是有时机限制的,需要在Activity的start生命周期之前(包含start)才可以,否则会抛出异常。

往下可以看到是通过lifecycle这个功能实现的,为启动的context(如activity)添加一个Observer,在Observer中发现是在onStart这个事件里处理的返回。但是实际上返回是在onActivityResult函数中,这里就需要关注mPendingResults,在ActivityResultRegistry中的doDispatch函数中为它赋予了数据,而doDispatch则被dispatchResult函数调用。那么在那里执行了dispatchResult?

    @MainThreadpublic 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 resultmParsedPendingResults.remove(key);// And add these pending results in their placemPendingResults.putParcelable(key, new ActivityResult(resultCode, data));}}

答案是在ComponentActivity中,ComponentActivity中持有一个ActivityResultRegistry的对象,即上面提到的mActivityResultRegistry。在ComponentActivity的onActivityResult和onRequestPermissionsResult中都会调用dispatchResult函数。这样就实现了结果(包括申请权限)的回调。

@CallSuper@Override@Deprecatedprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) {super.onActivityResult(requestCode, resultCode, data);}}@CallSuper@Override@Deprecatedpublic 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:使用 ActivityResult 处理 Activity 之间的数据通信相关推荐

  1. Android 使用 ActivityResult 处理 Activity 之间的数据通信及调起拍照实例

    在 Android 中,我们如果想在 Activity 之间双向传递数据,需要使用 startActivityForResult 启动,然后在 onActivityResult 中处理返回,另外申请权 ...

  2. android 不同activity之间传递数据

    1> 不同activity之间传递数据: Intent intent=new Intent(); intent.setClass(activity1.this,activity2.class); ...

  3. 从Android中Activity之间的通信说开来

    引言 最近两个星期在研究android的应用开发,学习了android应用开发的基础知识,基本控件,基本布局,基本动画效果,数据存储,http访问internet等等基础知识. android中有一个 ...

  4. android activity之间传值

    当对Android有一些了解后,不难发现,Android程序UI框架接近于Web页面的概念.每一个用于呈现页面的组件,Activity,都是彼此独立的,它们通过系统核心来调度整合,彼此之间的通过Int ...

  5. 不同Activity之间传递数据--Bundle对象和startActivityForResult方法的实现

    首先,由于Activity是Android四大组件之一,如果一个应用程序中包含不止一个Activity,则需要在AndroidManifest.xml文件中进行声明. 例如进行如下的声明(程序中包含两 ...

  6. 【Android 应用开发】Activity生命周期 与 Activity 之间的通信

    一. Activity生命周期 上图 1. Activity状态 激活状态 : Activity出于前台 , 栈顶位置; 暂停状态 : 失去了焦点 , 但是用户仍然可以看到 , 比如弹出一个对话框 , ...

  7. ​Android中如何使用Intent在Activity之间传递对象[使用Serializable或者Parcelable]

    Android中如何使用Intent在Activity之间传递对象[使用Serializable或者Parcelable] 在Android中的不同Activity之间传递对象,我们可以考虑采用Bun ...

  8. 大叔也说Xamarin~Android篇~Activity之间传递数组

    大叔也说Xamarin~Android篇~Activity之间传递数组 原文:大叔也说Xamarin~Android篇~Activity之间传递数组 我们在开发应用程序时,不可能只使用一个Layout ...

  9. android不同Activity之间的数据共享

    方法1: 通过不同Activity之间的消息传递机制实现数据共享 Activity1: Intent intent=new intent(Activity1.this,Activity2.clas); ...

最新文章

  1. 3des密钥生成 java_使用keytool生成3DES密钥
  2. 特斯拉撤诉和解,小鹏汽车沉冤得雪:警惕自动驾驶领域的“美国陷阱”
  3. 《暗时间》的笔记-我在南大的七年
  4. Redis 是单线程凭什么能支撑高并发
  5. t oracle删除吗,Oracle 11g 手工建库与删库
  6. Joi验证模块的使用
  7. mtk android 设置默认铃声,[转载]MTK修改铃声资源
  8. Bitmap对图像的处理
  9. JS基础 - - if 练习二
  10. 2007年测试员的工具选择排名
  11. JSF使用HTML5的custom attribute
  12. 转载-MATLAB中将数据写入TXT文本文档中
  13. Systrace的工作原理
  14. Eclipse使用技巧--设置编辑器背景护眼色和设置字体
  15. 生活中的逻辑谬误06.德克萨斯神枪手
  16. java读文件写文件
  17. 文件系统以及硬盘分区概念
  18. Atcoder ABC162 D - RGB Triplets
  19. 【爬虫进阶】常见的反爬手段和解决方法(建议收藏)
  20. 计算机的ip地址和用户名和密码是什么原因,电脑的ip地址账户和密码忘记怎么办...

热门文章

  1. 面试和人生目标(转)
  2. win8 - 学习资源
  3. 自定义的Sort对象
  4. Ext.Net中CheckboxSelectionModel的动态隐藏(显示),一定条件下的隐藏(不让选择),获得多选栏位的信息,及后台控件的动态创建...
  5. 在ASP.Net中两种利用CSS实现多界面的方法(转)
  6. Chisel 学习笔记(四)
  7. 第三章JavaScript 内置对象
  8. BZOJ 1011: [HNOI2008]遥远的行星
  9. CSDN总结的面试中的十大可视化工具
  10. dmalloc用法快速入门