美文网首页
【转】过时的OnActivityResult替代品-regist

【转】过时的OnActivityResult替代品-regist

作者: a_simple_coder | 来源:发表于2021-08-05 12:18 被阅读0次

    时隔多年 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中:
    其中有一个名为ComponentActivityActivity用于分发事件,

    如果你查看androidx.fragment:fragment:version就会发现:
    FragmentActivity已经依赖了androidx.activity.ComponentActivity而不是androidx.core.app.ComponentActivity

    Activity是实现新回调的一个基类,不论是activity或者fragment调用registerForActivityResult的时候,最终都是在这个类中去处理的,

    image.png

    具体新加的与回调相关的类如上图所示,其中有几个暂时不相关的类,但不影响观看

    ActivityResultContract

    该类为基类,需要两个泛型,一个为出参,一个为入参。
    在使用过程中一般只需要关注实现createIntentparseResult这两个方法即可,顾名思义createIntent提供跳转需要的intent,而parseResult提供返回的数据

    ActivityResultContracts

    该类为ActivityResultContract的实现类,其中有几个已经实现好了的ActivityResultContract,这里挑几个介绍一下

    StartActivityForResult : 通用的Contract,不做任何转换,Intent作为输入,ActivityResult作为输出,用于跳转Activity,其中涉及到了ActivityResult

    StartIntentSenderForResult :google支付,这里不多做解释

    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);
                }
            };
    }
    

    这个时候回到ComponentActivitymActivityResultRegistry看下源码就知道是怎么一回事了,
    该方法先判断了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,这个时候调用ActivityResultCallbackonActivityResult,这样就能拿到对应的数据和状态

    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推动的隐私和权限处理,如果能推广开来,则androidios的交集则越来越多,整个android生态环境也会逐渐好起来,

    作者使用android的时候最烦的是各种app在根目录乱拉的毛病,而android11也强制开启了文件沙盒,这表明了欠的债迟早是要还的~~

    原文: https://7449.github.io/2020/05/01/android_new_activity_result.html

    相关文章

      网友评论

          本文标题:【转】过时的OnActivityResult替代品-regist

          本文链接:https://www.haomeiwen.com/subject/cejmvltx.html