Activity新的回调方式

在我们的日常开发中,从一个Activity打开另一个Activity并接收其回调结果是一个很普遍的场景,这其中包括打开其他应用的Activity,通常我们的做法都是通过activity.startActivityForResult()方法来实现。这其中除了我们应用内业务相关页面的跳转之外,还有很多像打开相册、调起相机等和系统Activity交互的场景,这些场景每次都要很麻烦的维护很多代码,即使是原生页面跳转也要维护requestCode和bundle数据的key值等常量,代码臃肿难以维护。Jetpack出现后,现在在处理这种非单向操作的场景时有了一种更优雅的方式,我们来看看它是怎么帮我们封装的。

首先,我们需要添加两个依赖(androidx.*中的)

api 'androidx.fragment:fragment:1.3.0-alpha06'
api 'androidx.activity:activity:1.2.0-alpha06'

然后我们顺着Activity的继承关系找下去会看到ComponentActivity的继承关系:

image

其中的ActivityResultCaller是注册回调的关键接口,ActivityResultRegistryOwner是用来获取Activity的ActivityResultRegistry对象的,通常用于Fragment或者其他非Activity类使用。

ActivityResultCaller接口中有两个方法:

@NonNull
 ActivityResultLauncher registerForActivityResult(
        @NonNull ActivityResultContract contract,
        @NonNull ActivityResultCallback callback);
@NonNull
 ActivityResultLauncher registerForActivityResult(
        @NonNull ActivityResultContract contract,
        @NonNull ActivityResultRegistry registry,
        @NonNull ActivityResultCallback callback);

这两个方法的差别就是三参数的那个多了个ActivityResultRegistry类型的参数,如果在Activity中调用则不需要这个参数,因为ComponentActivity中帮我们维护了一个ActivityResultRegistry对象了,如果在自定义组件或者其它类中可以使用三参数方法并调用activity的getActivityResultRegistry()方法传入该参数。

现在我们知道了每一个ComponentActivity的子类都可以使用这两个方法注册ActivityResult回调,我们从注册开始顺着看看是怎么实现的。

img

我们这里在Activity中注册,所以使用两个参数的注册方法。第一个参数就是Contract契约,是一个ActivityResultContract类型的对象,里面有两个关键方法,createIntent()和parseResult(),createIntent()就是来创建用于请求打开新Activity的intent的;其中的第二个参数是泛型,可以自定义,parseResult()用于最后根据resultCode和data创建ActivityResult对象传给ActivityResultCallback的onActivityResult()方法内使用,后面会看到。

第二个参数就是ActivityResultCallback对象了,重写onActivityResult方法就可以处理回调了,这就相当于之前我们在Activity中重写的onActivityResult方法。

image-20200703141056335

进到registerForActivityResult方法里面可以看到两个参数的registerForActivityResult也是调用了三参数的重载方法,ActivityResultRegistry对象用的是ComponentActivity内部定义的mActivityResultRegistry,三参数的方法最终返回的是mActivityResultRegistry的register方法的调用,这个方法有四个参数,后面两个就是ActivityResultContract对象和ActivityResultCallback对象,第一个参数是key值,稍后会提到,第二个参数是LifecycleOwner对象,这里就是Activity本身(因为ComponentActivity实现了它),现在我们进到ActivityResultRegistry类的register里面看看:

image-20200703141309938

我们会看到首先通过registerKey方法获取requestCode:

image-20200703141558444

这里可以看到requestCode是通过当前类里面的一个AtomicInteger对象产生的,所以每次产生的都会是最新且唯一的(这里注意getAndIncrement()方法返回的是增加前的值),这就不需要我们自己去维护requestCode了,然后把它保存在map里,这里有两个map,一个是mKeyToRc,它保存的是key->requestCode,另一个是mRcToKey,它保存的是requestCode-key,后面会看到他们的用武之地。

接着有一个mKeyToCallback的map以传进来的key值为键,保存了一个组合了callback和contract的CallbackAndContract对象,接下来根据lifecycleOwner获取到Activity的生命周期对象。

接下来的mPendingResults部分的代码主要是为了处理Activity非主动销毁时的恢复,后面会提到,值得注意的是,这里会使用正在进行注册的新callback来处理之前未正常执行完成的result。这里判断如果存在尚未正常执行完的回调且Activity此时生命周期已是STARTED之后(onStart()方法执行完),则先执行未完成的回调;若是生命周期在STARTED之前则给其生命周期添加监听,STARTED之后自动执行回调。

然后统一添加销毁时的注销工作的监听,最后会返回一个ActivityResultLauncher对象,我们就用这个对象来启动指定的Activity。

launcher.launch("image/*")

因为我们之前使用的contract是ActivityResultContracts.GetContent(),所以这里我们传入一个String作为intent的type。

image-20200703142447801

我们看到ActivityResultLauncher类里有两个launch方法,只有一个是需要重写的,因为ActivityOptionsCompat参数是可选的,ActivityOptionsCompat就是一个封装了Activity之间转换动画的类(还有一点其他设置),这里没传就是null。

我们上面说到这个launcher实际上就是那个register方法返回的匿名对象,所以这里的launch方法就是调用的那个匿名对象的launch方法,回过头去看,匿名对象的launch方法实际上是调用的ActivityResultRegistry类的onLaunch方法,并且把生成的requestCode、contract、传入的“iamge/*”、还有一个ActivityOptionsCompat对象带过去,所以到了这里实际上调用的就是ComponentActivity里的mActivityResultRegistry的onLaunch方法:

image-20200703143800799

onLaunch()方法是ActivityResultRegistry类里唯一的abstract方法。我们看看它都做了些什么:

首先通过contract的getSynchronousResult()方法拿到一个ActivityResultContract.SynchronousResult(这个类中只有一个泛型对象)对象,如果这个对象不为空则直接在主线程调用dispatchResult()方法结束launch,ActivityResultContract的getSynchronousResult()方法有默认实现(默认返回null),是一个可选方法,RequestPermission和RequestMultiplePermissions里会用得到,主要是用它处理一些不需要启动Activity就能知道预期结果的场景。dipatchResult方法很简单,不做说明了,看下图:

image-20200703150421901

接下来就是正常情况下最后启动Activity的流程了,可以看到出了两个特殊情况需要特别处理之外,其他的启动方式最后都是调用了我们熟悉的ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle)方法,ActivityResultContracts里面已经给我们封装好了常用的启动系统Activity的场景,如果我们需要启动自己的Activity,我们也可以使用ActivityResultContracts中的StartActivityForResult来实现。

到现在为止,我们可以很明显地感受到这种方式使我们启动Activity变得简洁优雅,但还有最后一个问题,最终的回调是如何触发的呢?前面我们看到了Immediate result path的回调,现在我们来看一下启动Activity之后返回的回调是怎么触发的。

官方文档中并没有回调触发的新方法,所以我们猜测应该还是finish()这个老朋友的任务:

image-20200703153317825

通过注释我们知道最终会回调到调用者的onActivityResult()方法:

image-20200703153556965

注释告诉我们已经不提倡在Activity里重写这个方法来接收回调了,提倡使用我们新介绍的这种方式。可以看到在这个统一的回调方法里,会首先调用mActivityResultRegistry的dispatchResult方法,如果返回false则继续走传统的回调方式:

image-20200703153928351

这里通过requestCode拿到一开始注册的key,再通过key拿到CallbackAndContract对象,最终执行doDispatch()方法:

image-20200703154224412

这里的resultCode和data都是上一个Activity调用setResult()方法设置的。可以看到,在这里调用了contract的parseResult方法拿到ActivityResult对象,回调到前面注册时callback的onActivityResult方法中,else分支的意义在于如果出现异常没取到回调对象(有可能是注册的时候传入的callback参数是null,虽然registerForActivityResult的callback参数加了注解明确表示不能为空,但是因为Java不像kotlin那样传入null会报错,所以这里还是要特殊容错处理)则会放到表示未处理的mPendingResults中,等到有新的注册行为时会通过新注册的callback对象来处理,前面已经提过。

至此,一个完整的新式启动回调流程就完成了。
不仅如此,这种方式内部还会调用onSaveInstance和onRestoreInstance方法进行key和回调等实例的保存和恢复,因此在那些回调尚未完成就由系统杀死的情况下,在返回重新创建实例后会自动回复之前的回调实例完成回调。

你可能感兴趣的:(Activity新的回调方式)