方法看着有点懵?没关系,找两个个具体实现类看看就知道了。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()
,是因为 ComponentActivity
和 Fragment
都实现了 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
用于处理协定的注册和启动器的启动,代码示例如下:
调用处:
④ 亿点小细节:配置改变引起Activity重建的处理
在 ActivityResultRegistry
中还发现了介个:
好家伙,连配置更改导致重建的场景也考虑到了吗?
保存了:key(requestCode)相关的数据、处理结果、Random随机数实例。
requestCode和Result得以保留,Activity重建后,再把它们分发给新注册的Callback,避免了数据的丢失。
⑤ 亿点补充:测试Activity结果调用
默认情况下,registerForActivityResult()
会自动使用Activity提供的 ActivityResultRegistry
,而它还提供了一个重载,支持传入自己的 ActivityResultRegistry
实例。能干嘛?拦截结果调用进行测试啊,不会另外启动另一个Activity。代码示例如下:
0x3、关于封装
Activity Results API 了解得七七八八了,接下来可以放心地用到项目中了,虽然它的API已经很简单易用了。但对于 喜欢偷懒到极致的开发仔 来说还是不够的,可以利用Kotlin相关的语法特性,封装下再少写一些代码。
捋下API使用链条:
registerForActivityResult()
→ActivityResultLauncher
,需在ON_START或之前注册,在OnCreate()时再初始化会报错,还得传入一个**ActivityResultContract
** 实例,最后跟一个ActivityResultCallback
回调。- 调用
ActivityResultLauncher#launch()
才触发页面跳转,需要传入一个输入(如Intent)实例。
最简单的封装就是写几个 扩展方法,从ActivityResultLauncher生成和launch()调用处入手:
// 扩展
fun ComponentActivity.registerActResult(callback: ActivityResultCallback<ActivityResult>) =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
callback.onActivityResult(it) }
fun Fragment.registerActResult(callback: ActivityResultCallback<ActivityResult>) =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
callback.onActivityResult(it) }
fun Intent.launch(launcher: ActivityResultLauncher<Intent>) {
launcher.launch(this)
}
// 注册处
private val mLauncher = registerActResult {
shortToast("收到测试数据:${it.data?.getStringExtra("value")}")
}
// 调用处
Intent(this, SecondActivity::class.java).launch(mLauncher)
还可以在优化下,比如改成基于 ActivityResultCaller
进行扩展,然后把常用的一些跳转,如权限、打开相机、录像等写成一个个扩展函数,用的时候直接调用即可。懒得自己写或者想找参考的可以看看 → ActivityResult.kt
如果想代码写得更少更优雅,可以折腾得更复杂些,比如结合生命周期回调,各种简化调用的扩展,甚至弄成DSL调用等,具体可以参考这些:
- iDeMonnnnnn/DeMon-ARA
- TxcA/ManageStartActivity
- Flywith24/Flywith24-ActivityResultRequest
怎么封装看自己,觉得适合就行,笔者就懒得整那么复杂了~
0x4、小结
借着重构BaseFragment的机缘巧合,过了波Activity Results API的用法,阅读源码了解到背后的实现原理,小试了一下封装。心里有底了,赶紧在重构项目的时候安排上!!!
有问题或者建议欢迎在评论区提出,肝文不易,如果本文有帮到你的话,可以给个三连,谢谢~
参考文献:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数HarmonyOS鸿蒙开发工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年HarmonyOS鸿蒙开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上HarmonyOS鸿蒙开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上HarmonyOS鸿蒙开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
[外链图片转存中…(img-cEqE6vLk-1712868783590)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!