【Jetpack】学穿:Activity Results API_requestmultiplepermissions(1)

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

0x1、引言

🥰 不是什么新玩意了,恰逢最近拆公司项目的BaseFragment时看到介个:

下划线?Deprecated?点开源码看下啥原因,有啥替代方案:

注释说这种写法out了,可在 ActivityResultContract 中传入一个 RequestMultiplePermissions 对象,并在回调中处理结果。除此之外,诸如 startActivityForResult()onActivityResult() 等都过时了。

搜了一波官方文档《获取 activity 的结果 》,没找到具体原因,笔者猜测官方旨在帮助开发者:减少样板代码、解耦

以前跳转新页面回传数据,得经历这三步:

  • 定义REQUEST_CODE,同一个页面有多个数据时,避免重复;
  • 调用 startActivityForResult(Intent, REQUEST_CODE)
  • 重写 onActivityResult(),判断requestCode和resultCode,拿到值后执行后续逻辑;

简易代码示例如下

新页面,一个简单的setResult,然后finish():

而使用 Activity Result API,你只需定义一个函数,然后launch()一下:

跳转页面不用动,可以看到:移除了onActivityResult()的重写,少写了一个REQUEST_CODE。看着好像是简洁了一些,对了网上一堆旧文章说要依赖activity-ktx和fragment-ktx,笔者使用1.4.0的activity和fragment包,发现已经内置这些东西了,可以不依赖,技术迭代飞快,建议读者使用时以官方文档为准。

0x2、原理浅探

Activity Results API的使用非常简单 (侧面说明封装得好),它由三个要素组成:启动器 + 协定 + 结果回调

① ActivityResultLauncher → 启动器

registerForActivityResult() 的返回值,用于:承载启动对象与返回对象

② ActivityResultContract → 协定/契约

第一个入参,协定的是:所需的输入类型结果的输出类型

方法看着有点懵?没关系,找两个个具体实现类看看就知道了。ActivityResultContracts 给我们提供了一些 常用的协定,拿来即用:

罗列下各自的作用 (直接看代码实现也能猜到干嘛的~):

  • StartActivityForResult() → 通用协定,不做任何转换,Intent作为输入,ActivityResult作为输出;
  • StartIntentSenderForResult() → 内部Intent请求;
  • RequestMultiplePermissions() → 请求一组权限;
  • RequestPermission() → 请求单个权限;
  • TakePicturePreview() → 调用MediaStore.ACTION_IMAGE_CAPTURE拍照,返回Bitmap图片;
  • TakePicture() → 调用MediaStore.ACTION_IMAGE_CAPTURE拍照,并将图片保存在给定Uri,返回true表示保存成功;
  • TakeVideo() → 调用MediaStore.ACTION_VIDEO_CAPTURE录制,并将视频保存在给定Uri,返回true表示保存成功;
  • PickContact() → 调用通讯录APP获取联系人;
  • GetContent() → 提示选择一条内容,返回一个通过ContentResolver#openInputStream(Uri)访问原生数据的Uri地址(content://形式) 。默认情况下,它增加了 Intent#CATEGORY_OPENABLE, 返回可以表示流的内容;
  • GetMultipleContents() → 提示选择多条内容;
  • OpenDocument() → 提示选择文档,返回Uri;
  • OpenMultipleDocuments() → 提示用户选择多个文档,以List形式,返回他们的Uri;
  • OpenDocumentTree() → 提示用户选择目录,返回Uri;
  • CreateDocument() → 提示用户选择创建新文档的路径,返回已创建项目的Uri。

附:调用文件选择器,获取指定类型的文件,可在launch()方法里使用mimetype指定调用文件类型,文件mimetype对照表可参见:media-types

挑两个协议看看具体代码实现,先是 StartActivityForResult

返回类型ActivityResult实现了Parcelable序列化接口,定义了需要用到的两个字段:mResultCode 和 mData。

接着是 TakePicturePreview

所以 ActivityResultContract 中的函数意义分别是:

  • createIntent(context: Context, input: I): Intent → 创建用于 startActivityForResult 的intent对象;
  • parseResult (resultCode: Int, intent: Intent?): O → 对 onActivityResult 的结果进行转换;
  • getSynchronousResult() → 可选,处理一些不需要启动Activity就能知道预期结果的场景,如RequestPermission会用到;

了解函数意义后,如果你觉得内置协定满足不了你,完全可以自定义一波,官方示例如下:

class PickRingtone : ActivityResultContract<Int, Uri?>() {
override fun createIntent(context: Context, ringtoneType: Int) =
Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType)
}

override fun parseResult(resultCode: Int, result: Intent?) : Uri? {
if (resultCode != Activity.RESULT_OK) {
return null
}
return result?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
}
}

③ ActivityResultCallback → 结果回调

第二个入参,见名知意:启动Activity并返回当前Activity时的 结果回调

就定义了一个回调方法,Activity Result API 又是 模版方法模式 封装的思想体现,开发仔按需注入 协定类型结果回调 即可,无需关注底层细节。巴适得很!

杰哥当然不会止步于调别人写好的API,接着再探一波更深层次的原理,弄清楚整条调用链路。


0x2、原理再探

在Activity、Fragment中可以直接使用 registerForActivityResult(),是因为 ComponentActivityFragment 都实现了 ActivityResultCaller 接口。

① Activity

先跟下 ComponentActivity#registerForActivityResult()

第一个参数构造了一个key,规则:activity_rq# + 一个自增的AtomicInteger值,怪不得不用另外定义一个REQUEST_CODE,就能进行区分。

继续跟 ActivityResultRegistry#register()

看着好像挺复杂,其实不然,核心就是:

添加了一个观察者,当生命周期组件(传入的第2个参数) 状态切换到 ON_START 时执行回调

然后下半段返回了一个 ActivityResultLauncher 实例:

跟下 onLaunch() 发现是一个抽象方法,具体实现在 ComponentActivity 中:

到此,基本的调用链条就浮出水面了:

  • ComponentActivity 内部初始化了一个 ActivityResultRegistry 实例,并重写了 onLaunch()
  • ② 开发者调用 registerForActivityResult() 最终调用 ActivityResultRegistry.register(),在此添加了一个观察者,当生命周期状态切换到ON_START时,执行协定 Contract.parseResult() 生成输出内容,并把结果作为参数传入回调 callback.onActivityResult() 中。
  • ③ 注意!②是要生命周期发生改变才会触发的,开发者要调用 ActivityResultLauncher.launch() 才会发起跳转,其中回调了 onLaunch() 方法,在此调用了协定 Contract.createIntent() 返回一个和 startActivityForResult() 搭配使用的 Intent 实例。
  • ④ 跳转目标Activity后返回此页面,生命周期发生改变,然后回调②中的相关代码。

描述起来好像有点拗口,不过你自己照着跟下源码就清楚了,接着跟下Fragment~

② Fragment

同样跟下 registerForActivityResult()

最终调用 prepareCallInternal()

思路也很简单,想办法拿到 宿主Activity中的ActivityResultRegistry实例,调它的 register() 拿到返回的 ActivityResultLauncher实例引用。最后返回 新的ActivityResultLauncher 实例,在launch()中调用前面那个Activity的 ActivityResultLauncher实例引用 的launch()方法。TM调的是Activity的launch(),这一手 委托代理 玩挺6啊。

对了,这有个小细节,生命周期组件传入的是 Fragment.this,所以不用担心Fragment销毁没解绑导致的内存泄露问题。

③ 非Activity/Fragment 接收Activity结果

实现一个 LifecycleObserver 用于处理协定的注册和启动器的启动,代码示例如下:

调用处:

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

中…(img-OIIf8krA-1715189752447)]
[外链图片转存中…(img-SG5uBQ00-1715189752447)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值