赞
踩
转载请注明出处:https://blog.csdn.net/hx7013/article/details/120916287
Activity Result API
已经出来有一段时间了,但是还是有很多朋友对这个API感到使用不便或疑惑,今天尽量用一篇简短的文章简述下registerForActivityResult
的使用方法。
可以明显的看到,在androidx.activity1.2.0-alpha04
时开始,Android中这位你调用过无数次的startActivityForResult
和onActivityResult
,已经被官方标记为弃用了,继而推出了名为Activity Result API
的组件。
弃用原因也许是onActivityResult
里需要处理的各种判断、嵌套,也许是既要处理requestCode
也要处理resultCode
这种高耦合难以维护的Id判断模式。但其原因已不重要了,因为既然Android里已提供了更好的方案并把startActivityForResult
标记为了弃用,那么我们就应该开始了解一下位于 ComponentActivity
或 Fragment
中的registerForActivityResult
了。
这里先做一个简单的对比,来了解下registerForActivityResult
的简单及清爽
- // startActivityForResult
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- startActivityForResult(Intent(this, SecondActivity::class.java), REQUEST_CODE)
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
-
- when (requestCode) {
- REQUEST_CODE -> {
- val code = resultCode
- val data = data
- }
- }
- }
-
- companion object {
- private const val REQUEST_CODE = 1024
- }
-
- // registerForActivityResult
- private lateinit var resultLauncher: ActivityResultLauncher<Intent>
-
- private val launcherCallback = ActivityResultCallback<ActivityResult> { result ->
- val code = result.resultCode
- val data = result.data
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- resultLauncher = registerForActivityResult(
- ActivityResultContracts.StartActivityForResult(),
- launcherCallback)
-
- resultLauncher.launch(Intent(this,SecondActivity::class.java))
- }
代码一看完,是不是第一感觉不对啊,怎么感觉比之前还复杂了,其实这里是为了让你更直观的了解到这个registerForActivityResult
到底是什么东西,所以对载体、定义协定、回调3个类分别定义写出来。其实大部分情况我们像以下代码其实这么写就可以了
- private val launcherActivity = registerForActivityResult(
- ActivityResultContracts.StartActivityForResult()) {
- val code = it.resultCode
- val data = it.data
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- launcherActivity.launch(Intent(this, SecondActivity::class.java))
- }
是不是瞬间清爽了许多,但是…你还是觉得比使用startActivityForResult
更复杂?其实不然,因为上面代码的需求是一个单一的回调,所以看着似乎startActivityForResult
更便于维护和使用。但倘若编写一个稍复杂的页面,需要同时请求相册、需要在其它Activity选择数据并回调、需要判断权限等等时,继续使用startActivityForResult
,会导致onActivityResult
里掺杂各种嵌套及判断,导致代码难以维护。而使用registerForActivityResult()
可以多次调用以注册多个 ActivityResultLauncher
实例,用来处理不同的Activity结果,让代码更便于维护。
优势了解到了,但既然需要使用新的功能,那么我们就必须要先了解以下,刚说到的ActivityResultLauncher
、ActivityResultContract
、ActivityResultCallback
到底是些什么东西
registerForActivityResult
返回该对象,这时并不会立即启动另一个Activity。ActivityResultContracts.StartActivityForResult()
。当然这里你也可以通过继承ActivityResultContract
实现自己的定义。对于这3个类,其实只需重点了解ActivityResultContract
,就能很轻松的理解并使用好Activity Result API
了。
同时,引用一个官方文档的警告 ↓
注意:虽然在 fragment 或 activity 创建完毕之前可安全地调用
registerForActivityResult()
,但在 fragment 或 activity 的Lifecycle
变为CREATED
状态之前,您无法启动ActivityResultLauncher
。
刚才的例子中,其实已经简单的使用到Android提供的一个默认协定ActivityResultContracts.StartActivityForResult()
来启动了一个Activity并获得想要的返回值。除了StartActivityForResult()
,Android还提供了以下的默认协定以便于开发者的使用
ActivityResultContracts.* | 说明 | 参数 | 回调 |
---|---|---|---|
StartActivityForResult | 可以理解为startActivityForResult | Intent | ActivityResult(code,data) |
TakePicture | 通过MediaStore.ACTION_IMAGE_CAPTURE 拍照并保存 | 保存文件的Uri | 是否保存成功 |
TakePicturePreview | 通过MediaStore.ACTION_IMAGE_CAPTURE 拍照 | null(Void) | 图片的Bitmap |
CaptureVideo | 通过MediaStore.ACTION_VIDEO_CAPTURE 拍摄视频并保存(androidx.activity 1.3.0-alpha08后提供,androidx.appcompat好像还没提供该类) | 保存文件的Uri | 是否保存成功。 |
RequestPermission | 请求单个权限 | Manifest.permission.* | 用户是否授予该权限 |
RequestMultiplePermissions | 请求多个权限 | Array<Manifest.permission.*> | 回调为map, key为请求的权限,value为用户是否授予该权限 |
CreateDocument | 通过Intent.ACTION_CREATE_DOCUMENT 创建一个文件 | 默认文件名 | 选择目录后返回该文件的Uri |
GetContent | 通过Intent.ACTION_GET_CONTENT 获取一个文件(这个方法可以通过android.content.ContentResolver.openInputStream 获取到文件的原始数据) | MIME类型 | 文件Uri |
GetMultipleContents | 通过Intent.ACTION_GET_CONTENT 及Intent.EXTRA_ALLOW_MULTIPLE 获取一个或多个文件(这个方法可以通过android.content.ContentResolver.openInputStream 获取到文件的原始数据) | MIME类型 | 文件List |
OpenDocument | 通过Intent.ACTION_OPEN_DOCUMENT 选择文件 | MIME类型 | 文件Uri |
OpenDocumentTree | 通过Intent.ACTION_OPEN_DOCUMENT_TREE 选择一个目录,返回一个Uri并得到该目录下全部文档的管理权 | 目录初始位置Uri | 选择目录Uri |
OpenMultipleDocuments | 通过Intent.ACTION_OPEN_DOCUMENT 及Intent.EXTRA_ALLOW_MULTIPLE 获取一个或多个文件 | MIME类型 | 文件List |
PickContact | 通过Intent.ACTION_PICK 从系统通讯录中获取联系人 | null(Void) | 联系人Uri |
StartIntentSenderForResult | 构建IntentSender 或PendingIntent | 使用IntentSenderRequest.Builder 构建 | ActivityResult(code,data) |
通过MediaStore.ACTION_VIDEO_CAPTURE 拍摄视频并保存(弃用了,官方解释是缩略图的Bitmap的返回不稳定,替换为上面的CaptureVideo 即可) | 保存文件的Uri | 视频缩略图Bitmap |
以上全部ActivityResultContracts可在GitHub查看完整示例源码
OK,到此是不是慢慢开始感觉到Activity Result API
的便捷了。
虽然Android提供的默认协定ActivityResultContracts
已经很丰富了,但是为了自己应用内Activity类型安全的传递或是解耦,有时我们需要自己创建一个ActivityResultContract。其实查看ActivityResultContracts
任意一个类的源码,发现自己实现ActivityResultContract
并不复杂,只需继承ActivityResultContract
,即可实现类型安全的Activity启动与数据返回。
- class ContractActivity : AppCompatActivity() {
-
- ......
-
- /**
- * 继承[ActivityResultContract]
- *
- * 泛型第一个类型为传入到 [ContractActivity] 的参数类型
- * 第二个类型为 [ContractActivity] 返回给启动Activity的返回值类型
- */
- class Contract : ActivityResultContract<String, String>() {
- /**
- * 创建启动Intent
- * @param context [Context]
- * @param input 当前类的第一个泛型参, 这里自己实现传递过程
- */
- override fun createIntent(context: Context, input: String): Intent =
- Intent(context, ContractActivity::class.java).apply {
- putExtra(EXTRA_NAME, "$input - ${System.currentTimeMillis()}")
- }
-
- /**
- * 解析结果
- * @param resultCode [Activity.setResult] 的 resultCode
- * @param intent [Activity.setResult] 的 intent
- * 其实这里类似 [Activity.onActivityResult] 处理过程,只是由于返回是明确的,所以少了requestCode
- */
- override fun parseResult(resultCode: Int, intent: Intent?): String {
- return if (resultCode == Activity.RESULT_OK && intent != null) {
- "${intent.getStringExtra(EXTRA_RESULT)} - ${System.currentTimeMillis()}"
- } else {
- "error"
- }
- }
-
- companion object {
- /** EXTRA */
- const val EXTRA_NAME = "extra_name"
- const val EXTRA_RESULT = "extra_result"
- }
- }
- }
定义好一个Contract后,在使用Activity Result API
启动ContractActivity
时,只需调用
- val launcherContractActivity = registerForActivityResult(ContractActivity.Contract()) { result: String -> }
-
- launcherContractActivity.launch("hi, Activity Result API!")
怎么样,是不是瞬间觉得特别方便?而且这种方式让启动Activity解耦得很彻底,启动方能明确的知道该传什么值给被启动的Activity,也能明确的知道被启动Activity会返回什么数据。
以上全部ActivityResultContracts可在GitHub查看完整示例源码
虽然Activity Result API
非常强大与便捷,但在国内各厂商深度定制系统的情况下,权限申请操作一般我们还是会使用到第三方框架,拍照、视频录制大部分情况使用系统界面操作肯定也不适用。所以Activity Result API
里,我的刚需似乎只是一个startActivityForResult
那么简单, 那有更便捷的方法吗?
首先,尝试一个直接在按钮点击时创建一个ActivityResultLauncher
并启动
- viewBinding.btnStartActivityForResult.setOnClickListener {
- registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result->
- val code = result.resultCode
- val data = result.data
- }.launch(Intent(this@ActivityResultContractsActivity, SecondActivity::class.java))
- }
看着似乎很方便,又不需要提前注册,即用即回调。但是很不幸,这样使用你会收获一个IllegalStateException
,异常里也说得很明白LifecycleOwner xxx is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.
,必须在STARTED
前调用registerForActivityResult
。
看来我们必须每次都必须通过registerForActivityResult
预定一个ActivityResultLauncher
,在需要的时候再去launch
,这…似乎还是比较麻烦。那么来看看一个ManageStartActivity
小框架吧。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。