【Jetpack】ActivityResult介绍及原理分析

news2025/1/19 8:14:43

​​在这里插入图片描述

前言

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

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

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

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

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

ActivityResult

使用ActivityResult需要先添加依赖:
在这里插入图片描述

然后先看看最简单的使用方式,比如打开系统文件管理器选择一个图片,代码如下:

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // 处理返回的 Uri
}

getContent.launch("image/*") //过滤图片

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

  • registerForActivityResult:是ComponentActivity的一个函数(注意这里的ComponentActivity是androidx.activity.ComponentActivity而不是androidx.core.app.ComponentActivity,androidx.core中的对应类目前还不支持这项功能。)这个函数的定义如下:
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
            @NonNull ActivityResultContract<I, O> contract,
            @NonNull ActivityResultCallback<O> callback)

可以看到这个函数接收两个参数,分别是ActivityResultContract和回调ActivityResultCallback,ActivityResultContract是封装启动所需要的各项参数(组成Intent,后面会细说)。函数返回ActivityResultLauncher,可以看到后面通过他的launch函数就可以启动activity。

  • GetContent:ActivityResultContracts.GetContent类是一个继承ActivityResultContract的具体实现类,封装了调用系统文件管理器的功能。Jetpack提供了一些常用的ActivityResultContract,比如选取图片,拍照等等,如果我们需要拉起自己的Activity,就需要自定义一个ActivityResultContract。

  • 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(见下面)即可。

ActivityResultContracts子类

在Jetpack提供的已封装好的ActivityResultContract有(都是ActivityResultContracts的子类):

  1. StartActivityForResult

如下:

public static final class StartActivityForResult extends ActivityResultContract<Intent, ActivityResult>

最简单的,相当于传统方式的startActivityForResult,只不过将onActivityResult的几个参数封装成一个ActivityResult,如下:

public ActivityResult(int resultCode, @Nullable Intent data)

  1. StartIntentSenderForResult

相当于Activity.startIntentSender(IntentSender, Intent, int, int, int),与PendingIntent配合使用

  1. RequestMultiplePermissions

用于批量申请权限,如下:

public static final class RequestMultiplePermissions extends ActivityResultContract<String[], java.util.Map<String, Boolean>>

以Map形式返回每个权限的情况。

  1. RequestPermission

申请单个权限,如下:

public static final class RequestPermission extends ActivityResultContract<String, Boolean>

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

  1. TakePicturePreview

拉起拍照预览,如下:

public static class TakePicturePreview extends ActivityResultContract<Void, Bitmap>

直接返回bitmap数据。(跟传统方式一样,这个bitmap只是一个图片预览,因为intent中不能传输过大的数据)

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

  1. TakePicture

拉起拍照,如下:

public static class TakePicture extends ActivityResultContract<Uri, Boolean>

输入图片要保存的位置uri

  1. TakeVideo

录制视频,如下:

public static class TakeVideo extends ActivityResultContract<Uri, Bitmap>

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

  1. PickContact

选取联系人,如下:

public static final class PickContact extends ActivityResultContract<Void, Uri>

  1. GetContent

获取单个文件,如下:

public static class GetContent extends ActivityResultContract<String, Uri>

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

  1. GetMultipleContents

文件多选,如下:

public static class GetMultipleContents extends ActivityResultContract<String, List>

同上

  1. OpenDocument

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

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

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

  1. OpenMultipleDocuments

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

  1. OpenDocumentTree

打开文档tree,对应Intent.ACTION_OPEN_DOCUMENT_TREE

  1. 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>() {
            ...
        };
    }

首先可以看到这个函数的调用是有时机限制的,需要在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对象,而且有时机的限制,所以还不是特别灵活(尤其在权限管理这块)。

推荐阅读:《Android 12新功能:启动画面SplashScreen》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/414886.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vulnhub项目:Breakout

靶机地址&#xff1a;Empire: Breakout ~ VulnHub 渗透过程&#xff1a; 查询kali ip&#xff1a;192.168.56.104&#xff0c;靶机ip&#xff1a;192.168.56.131 探测靶机开放端口&#xff0c;利用nmap 靶机开放了80、139、445、10000、20000端口&#xff0c;先对80端口进行访…

【DevOps】GitOps多环境管理 - 别用多分支!

前言 在上一篇文章中【DevOps】GitOps多环境管理(上) - 别用多分支&#xff01;&#xff0c;我们介绍了在探索GitOps实践过程中会遇到的一些痛点&#xff0c;其中之一就是难以做到跨环境的版本发布&#xff0c;或者说怎么处理多个集群的部署。 在上一篇文章中&#xff0c;我们…

记一次内存泄漏排查

记一次内存泄漏排查 文章目录记一次内存泄漏排查背景问题排查问题处理背景 最近某项目的服务突然告警&#xff0c;cpu超85%&#xff0c;随后就是服务宕机。交付重启服务后恢复正常但是随后不久又开始告警&#xff0c;特别是白天&#xff0c;严重影响客户业务进行。 问题排查 …

【分享贴】如何衡量和提高项目成功?

“如何衡量项目成功&#xff1f;” 无论是对于项目经理还是组织来说都希望项目能够成功&#xff0c;但是怎样才算是项目成功了呢&#xff1f; 世界项目管理大师哈罗德科兹纳认为&#xff1a;“传统项目或运营项目成功的衡量标准是时间、成本和范围&#xff1b;创新项目成功的衡…

函数调用、

1、函数调用 重载了函数调用运算符&#xff08;&#xff09;的类 实例化的对象 就叫做函数对象 函数对象 &#xff08;&#xff09;触发 重载函数调用运算符 执行 》类似函数调用 &#xff08;仿函数&#xff09; #include <iostream> using namespace std; class Pr…

【hello Linux】Linux第一个小程序 - 进度条

目录 先来区分两个标识符&#xff1a;回车和换行 1. 倒计时 2. 进度条 Linux&#x1f337; 下面来编写Linux系统下的第一个小程序 - 进度条 先来区分两个标识符&#xff1a;回车和换行 \r 和 \n \r 回车 &#xff1a;代表回到本行的开头&#xff1b; \n 换行 &#xff1a;代表…

【Linux】vscode的使用 | 进程间通信(简单概括)

文章目录1.vscode的下载2. vscode的使用1. 连接远端2. 在vscode创建文件并运行程序切换到命令行3. 安装常见插件3. 进程间通信1. 简单举例2.管道原理为什么把读写都打开&#xff0c;只打开读或者写不可以吗&#xff1f;3. 通过父子进程理解管道1. 创建匿名管道系统调用为什么可…

不用996,不用007,赚的还比我多?我直接好家伙

今天打开手机就看见信息99&#xff0c;哟吼&#xff0c;还挺热闹——感情都在上班摸鱼呢。 好奇心让我点了第一条未读信息&#xff0c;好家伙&#xff0c;直接让我手机闪退出APP了&#xff01; 嗨&#xff0c;我这暴脾气&#xff0c;直接手动滑到了第一条&#xff01;但是我没…

CentOS7-部署Tomcat并运行Jpress

1. 简述静态网页和动态网页的区别。 2. 简述 Webl.0 和 Web2.0 的区别。 3. 安装tomcat8&#xff0c;配置服务启动脚本&#xff0c;部署jpress应用。1、简述静态网页和动态网页的区别 静态网页&#xff1a; 请求响应信息&#xff0c;发给客户端进行处理&#xff0c;由浏览器进…

009:Mapbox GL点击click某位置,显示坐标信息

第009个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中点击某位置,显示坐标信息 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共81行)相关API参考:专栏目标示例效果 配置方式 1)查看基础设置:htt…

一行代码就能完成的事情,为什么要写两行?

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 三元运算符 用三元运算符代替简单的if else if (age < 18) {me 小姐姐; } else {me …

MIMO-OFDM无线通信技术(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 本代码为MIMO-OFDM无线通信技术及MATLAB实现。分为十章&#xff0c;供大家学习。 &#x1f4da;2 运行结果 主函数部分代码&a…

移动端架构师都需要具备怎样的技术栈和能力

移动端架构师都需要具备怎样的技术栈和能力 主要的能力 技术深度 技术广度 动手能力: 比如说造轮子的能力, UI(高级自定义UI, 通用UI组件库), LibrarySDK(通用基础库, 项目框架/架构) 经验丰富 辅助的能力 领导力 沟通能力 洞察与前瞻 赋能业务 技术栈 T字形技术栈语言语言高…

TPS79650DCQR低压差线性稳压器、TPS54040ADGQR一款42V,0.5A降压稳压器数据手册资料

TPS79650DCQR低压差&#xff08;LDO&#xff09;低功耗线性稳压器具有高电源抑制比&#xff08;PSRR&#xff09;、超低噪声、快速启动&#xff0c;以及出色的线路和负载瞬态响应&#xff0c;采用小外形、33 VSON、SOT223-6和TO-263封装。该系列的每个器件在输出端都有一个小型…

14届蓝桥杯Python总结

在比赛的时候大家头脑注意力都高度集中&#xff0c;比较紧绷&#xff0c; 我是不喜欢太紧绷的神经的&#xff0c;这时候电脑就夸得一下关机重启了&#xff0c;我当时真的想说关的好&#xff0c;休息一会&#xff08;哈哈哈&#xff09; 重启后我就继续做题&#xff0c;虽然出了…

AcWing 第一讲 打卡例题习题题目

AcWing 第一讲 打卡&例题&习题题目 1. AcWing 1. A B #include<iostream>using namespace std;int main(){int a,b;cin >> a >> b;cout << ab << endl;return 0; }2. AcWing 608. 差 #include<iostream>using namespace std;int…

他98年的,我真的玩不过他...

现在的小年轻真的卷得过分了。前段时间我们公司来了个98年的&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 最近和他聊了一次天&#xff0c;原来这位小老弟家里条…

ChatGPT做爬虫的第一步

做爬虫就是搞数据, 专业的人做专业的事, ChatGPT阅虫无数, 搞个小爬虫, So Easy! 我知道可以百度到下载图片的代码, 但是我不想百度了, 一切交给ChatGPT 你只需要问他: 怎么用Nodejs下载图片? 其中&#xff0c;url是图片的地址&#xff0c;filePath是保存图片的本地路径。使…

安装stable-diffusion时遇到卡的情况

安装过程中&#xff0c;假如出现Installing gfpgan并且卡了很久不动&#xff0c;见下图&#xff1a; 遇到这个情况&#xff0c;怎么解决呢&#xff1f; 去TencentARC/GFPGAN官网&#xff0c;点击绿色的“Code”按钮&#xff0c;然后点击“Download ZIP”按钮&#xff1a; 将下…

MySQL--数据类型--0409

目录 1.数值类型 1.2 tinyint类型 2.bit类型 3.小数类型 3.1 float 3.2 decimal 4.字符类型 4.1 char 4.2 varchar 4.3 char 和 varchar 比较 5. 日期类型 6. enum 和 set 6.2 set和enum的查找 6.2.1 enum的查找 6.2.2 set的查找 1.数值类型 1.2 tinyint类型 只…