时隔多年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的时候

最终都是在这个类中去处理的,

_config.yml

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

ActivityResultContract

该类为基类,需要两个泛型,一个为出参,一个为入参,在使用过程中一般只需要关注实现createIntentparseResult这两个方法即可,顾名思义

createIntent提供跳转需要的intent,而parseResult提供返回的数据

ActivityResultContracts

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

StartActivityForResult : 用于跳转Activity,其中涉及到了ActivityResult,代码比较简单,有兴趣的可以看看

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

RequestMultiplePermissions : 多个权限请求

RequestPermission : 单个权限请求

TakePicturePreview : 拍照预览

TakePicture : 拍照

TakeVideo : 摄像

PickContact : 选择联系人

GetContent : 获取各种文件的Uri

GetMultipleContents : 获取多个各种文件的Uri

OpenDocument : 打开文件

OpenMultipleDocuments : 打开多个文件

OpenDocumentTree : 打开文件夹

CreateDocument : 创建文件

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也强制开启了文件沙盒,这表明了欠的债迟早是要还的~~