ActivityResultLauncher使用,新方式来请求权限和startActivityForResult等等
文章目录
当前基于
androidx.activity
的
1.4.0
版本
一、这篇文章讲的是什么的?
1. 替换startActivityForResult和现在的权限回调处理
2. 注册必须在Activity#onStart()? 不,我们完全可以在用的时候才创建并使用
先说结论:
-
使用
registerForActivityResult
注册得到ActivityResultLauncher
,在Activity
必须在onStart
及之前执行,在fragment
必须在onCreate
及以前执行。 -
ActivityResultLauncher
的注册实际上是通过ActivityResultRegistry
注册获取的。 -
ActivityResultRegistry
有2个register
方法,其中一个并不需要LifecycleOwner
,所以通过这个新的注册方法,我们可以不遵守:必须在Activity
必须在onStart
及之前执行注册,和在fragment
必须在onCreate
及以前执行注册。 -
Fragment
在使用register
的是时候需要注意Activity是不是为空,否则会异常
1. 了解ActivityResultContract
首先我们需要了解有哪些自带的ActivityResultContract
StartActivityForResult
使用Intent在activity(fragment)间通信,返回ActivityResultStartIntentSenderForResult
使用IntentSenderRequest. builder构造,可以返回带有activity的ActivityResultRequestMultiplePermissions
获取多个动态权限,返回Map<String,boolean>数组RequestPermission
获取单个动态权限,成功后返回trueTakePicturePreview
调用相机,拍照后返回bitmap图片信息TakePicture
调用相机,拍照后将图片保存到开发者指定的Uri,返回trueTakeVideo
拍摄视频,将拍摄内容保存到开发者指定的Uri,返回缩略图CaptureVideo
拍摄视频,将内容保存到开发者指定的Uri后,返回truePickContact
弹出手机联系人列表,用户选中其中一个后,返回UriGetContent
访问原始数据,返回Uri,例如相册中的单个图片GetMultipleContents
功能如上,返回Uri数组,例如相册中的多个图片OpenDocument
访问文件数据,打开文件夹,返回单个文件的UriOpenMultipleDocuments
功能如上,返回多个文件的UriOpenDocumentTree
文件目录CreateDocument
创建文件
当然也可以自己定义,不过上面已经基本满足我们的需求了。
eg:
class UserActivityResultContract : ActivityResultContract<String, UserInfo?>() {
override fun createIntent(context: Context, input: String): Intent {
val intent = Intent(context, UserInfoActivity::class.java)
intent.putExtra("url", input)
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): UserInfo? {
if (resultCode == Activity.RESULT_OK) {
if (intent?.hasExtra("userInfo") == true) {
if (intent.getSerializableExtra("userInfo") != null) {
return intent.getSerializableExtra("userInfo") as UserInfo
}
}
}
return null
}
}
2. 如何使用
大家一般是在Fragment
或者Activity
中使用,需要在onCreate中使用registerForActivityResult
注册一下得到ActivityResultLauncher
,
需要注意的是,通过源码androidx.activity.ComponentActivity#registerForActivityResult
:
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.");
}
在Activity
必须在onStart
及之前执行。
androidx.fragment.app.Fragment#registerForActivityResult
if (mState > CREATED) {
throw new IllegalStateException("Fragment " + this + " is attempting to "
+ "registerForActivityResult after being created. Fragments must call "
+ "registerForActivityResult() before they are created (i.e. initialization, "
+ "onAttach(), or onCreate()).");
}
在fragment
必须在onCreate
及以前执行。
案例:
直接在在Fragment
或者Activity
中创建ActivityResultLauncher
private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
//结果
}
然后在要调用的地方
permissionLauncher.launch(arrayOf(
Manifest.permission.WRITE_CALENDAR,
Manifest.permission.READ_CALENDAR
))
总结:
使用
registerForActivityResult
注册得到ActivityResultLauncher
,在Activity
必须在onStart
及之前执行,在fragment
必须在onCreate
及以前执行。
二、注册ActivityResultLauncher
必须在Activity#onStart()之前?
答案:当然不是
通过源码我可以知道,实际上注册获取ActivityResultLauncher
是通过ActivityResultRegistry
注册,通过查看源码然后我发现还有一个注册方法是不需要LifecycleOwner
。
这样我们就可以在任意一个地方只要有Activity的上下文(context)就可以注册获取ActivityResultLauncher
。
首先通过context
(需要注意这里的context,要判断是否ComponentActivity
)
androidx.activity.ComponentActivity#getActivityResultRegistry
获取ActivityResultRegistry
然后通过下面的方法进行注册:
于是添加了下面的扩展方法,进行注册获取ActivityResultLauncher
:
Activity:
fun <I, O> ComponentActivity.register(
@NonNull key: String,
@NonNull contract: ActivityResultContract<I, O>,
@NonNull callback: ActivityResultCallback<O>
): ActivityResultLauncher<I> {
return activityResultRegistry.register(key, contract, callback).also {
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Event) {
if (event == Event.ON_DESTROY) {
it.unregister()
}
}
})
}
}
fragment:
fun <I, O> Fragment.register(
@NonNull key: String,
@NonNull contract: ActivityResultContract<I, O>,
@NonNull callback: ActivityResultCallback<O>
): ActivityResultLauncher<I> {
return requireActivity().activityResultRegistry.register(key, contract, callback).also {
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Event) {
if (event == Event.ON_DESTROY){
it.unregister()
}
}
})
}
}
使用案例:
fun ComponentActivity.startActivityResult2(
@NonNull key: String = "startActivityResult" + mNextLocalRequestCode.getAndIncrement(),
@NonNull intent: Intent,
@NonNull callback: ActivityResultCallback<ActivityResult>
) {
register(key,ActivityResultContracts.StartActivityForResult(), callback).launch(intent)
}
在ComponentActivity
需要的地方进行直接调用就可以了
startActivityResult2(intent = Intent(this,TestActivity::class.java)){
it.resultCode.toString().logI()
}
三、总结:
-
使用
registerForActivityResult
注册得到ActivityResultLauncher
,在Activity
必须在onStart
及之前执行,在fragment
必须在onCreate
及以前执行。 -
ActivityResultLauncher
的注册实际上是通过ActivityResultRegistry
注册获取的。 -
ActivityResultRegistry
有2个注册方法,其中一个并不需要LifecycleOwner
,所以通过这个新的注册方法我们可以不遵守:在Activity
必须在onStart
及之前执行,在fragment
必须在onCreate
及以前执行。 -
Fragment
在使用register
的是时候需要注意Activity是不是为空,否则会异常
四、附上【请求权限封装】和案例
配合上面的扩展函数,进行封装
fun ComponentActivity.startRequestPermissionsLauncher(
@NonNull key: String = "startRequestMultiplePermissions" + mNextLocalRequestCode.getAndIncrement(),
@NonNull callback: ActivityResultCallback<Map<String, Boolean>>
): ActivityResultLauncher<Array<String>> {
return register(key, ActivityResultContracts.RequestMultiplePermissions(), callback)
}
fun ComponentActivity.startRequestPermissionLauncher(
@NonNull key: String = "startRequestPermission" + mNextLocalRequestCode.getAndIncrement(),
@NonNull callback: ActivityResultCallback<Boolean>
): ActivityResultLauncher<String> {
return register(key, ActivityResultContracts.RequestPermission(), callback)
}
fun ComponentActivity.startRequestPermissions(
@NonNull key: String = "startRequestMultiplePermissions" + mNextLocalRequestCode.getAndIncrement(),
@NonNull permissions: Array<String>,
@NonNull callback: ActivityResultCallback<Map<String, Boolean>>
) {
return startRequestPermissionsLauncher(key, callback).launch(permissions)
}
fun ComponentActivity.startRequestPermission(
@NonNull key: String = "startRequestPermission" + mNextLocalRequestCode.getAndIncrement(),
@NonNull permission: String,
@NonNull callback: ActivityResultCallback<Boolean>
) {
return startRequestPermissionLauncher(key, callback).launch(permission)
}
Fragment的封装差不多,这里就不做描述,下面是具体调用方法:
mContent.testEvent.setOnClickListener {
//调用方法获取日历的权限
startRequestPermissions(permissions = arrayOf(
Manifest.permission.WRITE_CALENDAR,
Manifest.permission.READ_CALENDAR
)){
if (it.filter { !it.value }.isEmpty()){
// 获取权限成功
}
}
}
还可以封装更多,比如选择图片,选择文件,拍照等等
更多案例Demo:AndroidX-KTX
1.选择联系人
pickContact {
if (it != null) {
Toast.makeText(this, it.toString(), Toast.LENGTH_LONG).show()
}
}
2.选择多个文件,比如多个图片
selectMultipleFile {
if (it != null) {
Toast.makeText(this, it.toString(), Toast.LENGTH_LONG).show()
}
}
3.拍摄视频
takeVideo {
if (it != null) {
Toast.makeText(this, it.toString(), Toast.LENGTH_LONG).show()
}
}
4.申请权限
startRequestPermission(permission = READ_EXTERNAL_STORAGE) {
if (it != null) {
Toast.makeText(this, it.toString(), Toast.LENGTH_LONG).show()
}
}
5.选择文件
selectFile {
if (it != null) {
Toast.makeText(this, it.toString(), Toast.LENGTH_LONG).show()
}
}
6.拍摄图片
takePicture {
if (it != null) {
Toast.makeText(this, it.toString(), Toast.LENGTH_LONG).show()
}
}