【转】过时的OnActivityResult替代品-regist
时隔多年 google
终于开始对 onActivityResult
下手了,在 FragmentV1.3.0-alpha03
版本中新加入了prepareCall
方法,
该方法是startActivityForResult
的替代品
作者使用的版本是1.3.0-alpha04,google
已经在该版本中把
startActivityForResult()
onActivityResult()
requestPermissions()
onRequestPermissionsResult()
标记为过时,并把prepareCall
重命名为registerForActivityResult
该方法目前在alpha
版本中,所以api
可能会随着版本变化而改变,本文以1.3.0-alpha04
为基础
本文是针对新版本的registerForActivityResult
,所以不会再讲之前的onActivityResult
的使用方法,并且代码以kotlin
为主
功能
该功能新加的类基本都在androidx.activity:activity:version
中:
其中有一个名为ComponentActivity
的Activity
用于分发事件,
如果你查看androidx.fragment:fragment:version
就会发现:
FragmentActivity
已经依赖了androidx.activity.ComponentActivity
而不是androidx.core.app.ComponentActivity
。
该Activity
是实现新回调的一个基类,不论是activity
或者fragment
调用registerForActivityResult
的时候,最终都是在这个类中去处理的,
具体新加的与回调相关的类如上图所示,其中有几个暂时不相关的类,但不影响观看
ActivityResultContract
该类为基类,需要两个泛型,一个为出参,一个为入参。
在使用过程中一般只需要关注实现createIntent
和parseResult
这两个方法即可,顾名思义createIntent
提供跳转需要的intent
,而parseResult
提供返回的数据
ActivityResultContracts
该类为ActivityResultContract
的实现类,其中有几个已经实现好了的ActivityResultContract
,这里挑几个介绍一下
StartActivityForResult
: 通用的Contract
,不做任何转换,Intent作为输入,ActivityResult作为输出,用于跳转Activity,其中涉及到了ActivityResult
StartIntentSenderForResult
:
RequestMultiplePermissions
: 多个权限请求
RequestPermission
: 用于请求单个权限
TakePicturePreview
: 调用MediaStore.ACTION_IMAGE_CAPTURE
拍照,返回值为Bitmap图片
TakePicture
: 调用MediaStore.ACTION_IMAGE_CAPTURE
拍照,并将图片保存到给定的Uri地址,返回true
表示保存成功。
TakeVideo
: 调用MediaStore.ACTION_VIDEO_CAPTURE
拍摄视频,保存到给定的Uri地址,返回一张缩略图。
PickContact
: 从通讯录APP获取联系人
GetContent
: 获取各种文件的Uri,提示用选择一条内容,返回一个通过ContentResolver#openInputStream(Uri)访问原生数据的Uri地址(content://形式) 。默认情况下,它增加了Intent#CATEGORY_OPENABLE, 返回可以表示流的内容。
GetMultipleContents
: 获取多个各种文件的Uri
OpenDocument
: 打开文件
OpenMultipleDocuments
: 打开多个文件,提示用户选择文档(可以选择多个),分别返回它们的Uri,以List的形式。
OpenDocumentTree
: 打开文件夹,提示用户选择一个目录,并返回用户选择的作为一个Uri返回,应用程序可以完全管理返回目录中的文档。
CreateDocument
: 创建文件,提示用户选择一个文档,返回一个(file:/http:/content:)开头的Uri。
ActivityResult
StartActivityForResult
需要的一个扩展类,提供了resultCode
和返回的Intent
ActivityResultCallback
接口,替代onActivityResult
ActivityResultCaller
接口,用于注册ActivityResult
,返回一个ActivityResultLauncher
ActivityResultLauncher
用于启动相应的Intent
ActivityResultRegistry
事件分发的提供者,具体可看ComponentActivity
中的mActivityResultRegistry
ActivityResultRegistryOwner
接口,获取一个ActivityResultRegistry
ComponentActivity
Activity
,实现了ActivityResultRegistry
Activity中使用
class MainActivity : AppCompatActivity(R.layout.activity_main) {
private val startActivityLauncher: ActivityResultLauncher<Intent> =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
//
} else if (it.resultCode == Activity.RESULT_CANCELED) {
//
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivityLauncher.launch(Intent(this, TestActivity::class.java))
}
}
Fragment中使用
class Fragment : Fragment() {
private val startActivityLauncher: ActivityResultLauncher<Intent> =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
//
} else if (it.resultCode == Activity.RESULT_CANCELED) {
//
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivityLauncher.launch(Intent(requireContext(), TestActivity::class.java))
}
}
源码解析
先来看一下在Activity
中启动launcher
之后是怎么实现这种功能的
调用ComponentActivity
中的registerForActivityResult
,该方法返回一个ActivityResultLauncher
用于启动Intent
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);
}
由于没有自定义ActivityResultRegistry
,所以这里使用的是ComponentActivity
中的mActivityResultRegistry
,调用了ActivityResultRegistry
中的register
register
则使用了lifecycleOwner
实现了自动解绑功能,然后返回了一个ActivityResultLauncher
,并调用了invoke
方法
public final <I, O> ActivityResultLauncher<I> register() {
// 注册一个requestCode并保存回调,这里的回调就是在onActivityResult拦截的时候取的
final int requestCode = registerKey(key);
mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));
// 这里最终返回的是我们需要的ActivityResultLauncher,并调用了ComponentActivity中mActivityResultRegistry的invoke
return new ActivityResultLauncher<I>() {
@Override
public void launch(I input, @Nullable ActivityOptionsCompat options) {
invoke(requestCode, contract, input, options);
}
@Override
public void unregister() {
ActivityResultRegistry.this.unregister(key);
}
};
}
这个时候回到ComponentActivity
的mActivityResultRegistry
看下源码就知道是怎么一回事了,
该方法先判断了action
是否为权限或者支付,最后调用的startActivityForResult
private ActivityResultRegistry mActivityResultRegistry = new ActivityResultRegistry() {
@Override
public <I, O> void invoke() {
ComponentActivity activity = ComponentActivity.this;
Intent intent = contract.createIntent(activity, input);
if (ACTION_REQUEST_PERMISSIONS.equals(intent.getAction())) {
// requestPermissions path 权限处理
} else if (ACTION_INTENT_SENDER_REQUEST.equals(intent.getAction())) {
//google支付
} else {
// 启动activity最终调用的是这里
ActivityCompat.startActivityForResult(activity, intent, requestCode, options != null ? options.toBundle() : null);
}
}
};
具体流程大概是这个样子,再来看下是如何拦截onActivityResult
的
ActivityResultRegistry
拦截了onActivityResult
,onRequestPermissionsResult
同理,dispatchResult
方法判断了是否需要拦截,根据在调用register
的时候保存的registerkey
来判断
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
//处理onActivityResult如果不是launcher启动的则还是走onActivityResult
//不过这里已经标记为过时
if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
然后拿出mKeyToCallback
中保存对应requestCode
的回调ActivityResultCallback
,这个时候调用ActivityResultCallback
的onActivityResult
,这样就能拿到对应的数据和状态
public final boolean dispatchResult(int requestCode, int resultCode, @Nullable Intent data) {
String key = mRcToKey.get(requestCode);
if (key == null) {
//使用的startActivityForResult
return false;
}
doDispatch(key, resultCode, data, mKeyToCallback.get(key));
return true;
}
//这里就比较简单了,获取回调之后返回parseResult生成的类数据
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;
//parseResult返回的什么这里就获取的什么
callback.onActivityResult(contract.parseResult(resultCode, data));
} else {
mPendingResults.putParcelable(key, new ActivityResult(resultCode, data));
}
}
fragment
也是先拿到mActivityResultRegistry
然后还是走的Activity
流程
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultCallback<O> callback) {
return prepareCallInternal(contract, new Function<Void, ActivityResultRegistry>() {
@Override
public ActivityResultRegistry apply(Void input) {
//返回了ComponentActivity的ActivityResultRegistry
if (mHost instanceof ActivityResultRegistryOwner) {
return ((ActivityResultRegistryOwner) mHost).getActivityResultRegistry();
}
return requireActivity().getActivityResultRegistry();
}
}, callback);
}
自定义ActivityResultContract
原因:在Album
框架替换api
的过程时,拍照需要处理,自带的不能满足功能,所以需要自定义一个
源码如下:
class CameraUri(val type: ScanType, val uri: Uri)
class CameraResultContract : ActivityResultContract<CameraUri, Int>() {
override fun createIntent(context: Context, input: CameraUri): Intent =
Intent(if (input.type == ScanType.VIDEO) MediaStore.ACTION_VIDEO_CAPTURE else MediaStore.ACTION_IMAGE_CAPTURE)
.putExtra(MediaStore.EXTRA_OUTPUT, input.uri)
override fun parseResult(resultCode: Int, intent: Intent?): Int = resultCode
}
自定义兼容了视频还是图片,因为uri是需要特别处理所以这里只返回了resultCode
,至于uri更新则由其他类自行管理
总结
大体流程走下来感觉还是很不错的,各种回调也比较好处理,Activity
之间的回调处理起来也比较容易点
jetpack
更新比较快,各种新功能也层出不穷,类似于ViewPager2
,MergeAdapter
这种方便的功能越来越多,针对Kotlin
也支持了各种xxx-ktx
,
再加上miui12
推动的隐私和权限处理,如果能推广开来,则android
和ios
的交集则越来越多,整个android
生态环境也会逐渐好起来,
作者使用android
的时候最烦的是各种app
在根目录乱拉的毛病,而android11
也强制开启了文件沙盒,这表明了欠的债迟早是要还的~~
原文: https://7449.github.io/2020/05/01/android_new_activity_result.html