为什么要丢掉 onActivityResult ?

如何启动一个新的 Activity,并获取返回值?

你的答案肯定是startActivityForResult和onActivityResult。没错,一直以来,在某些场景下,例如启动系统相机拍照,返回当前页面后获取照片数据,我们并没有其他选择,只能在 onActivityResult 中进行处理。

在最新的Activity 1.2.0-alpha02和Fragment 1.3.0-alpha02中,Google 提供了新的Activity Result API, 让我们可以更加优雅的处理 onActivityResult 。在介绍新 API 之前,我们不妨思考一下,为什么 Google 要丢掉 onActivityResult?

减少样板代码,解耦,更易测试。

举个最简单的场景,MainActivity跳转到SecondActivity,SecondActivity中按钮触发返回并传值回来。SecondActivity中的代码很简单:

class SecondActivity : AppCompatActivity(R.layout.activity_second){ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) back.setOnClickListener { setResult(Activity.RESULT_OK, Intent().putExtra("value","I am back !")) finish() } } } 复制代码

现在支持直接在AppCompatActivity()构造函数中传入 layoutId 了,无需另外setContentView()。

回到MainActivity中,按照传统的写法,是这样的:

class MainActivity : AppCompatActivity(R.layout.activity_main) { private val REQUEST_CODE = 1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) jump.setOnClickListener { jump() } } private fun jump() { startActivityForResult(Intent(this, SecondActivity::class.java), REQUEST_CODE) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE) { toast(data?.getStringExtra("value") ?: "") } }API } 复制代码
定义一个 REQUEST_CODE ,同一页面有多个时,保证不重复调用 startActivityForResult在 onActivityResult 中接收回调,并判断 requestCode,resultCode

上面的逻辑中不乏重复的样板代码,且大多都耦合在视图控制器(Activity/Fragment)中,也就造成了不易测试。细品一下,的确不是那么的合理。

可能一直以来我们也只有这一个选择,所以也很少看到有人抱怨 onActivityResult。精益求精的 Google 工程师为我们改进了这一问题。

下面来看看如何使用最新的 Activity Result API 。

Activity Result API

private val startActivity = prepareCall(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult? -> toast(result?.data?.getStringExtra("value") ?: "") } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) jump.setOnClickListener { jump() } } private fun jump() { startActivity.launch(Intent(this,SecondActivity::class.java)) } 复制代码

恩,就是这么简单。主要就两个方法,prepareCall()和launch()。拆解开来逐一分析。

public <I, O> ActivityResultLauncher<I> prepareCall( @NonNull ActivityResultContract<I, O> contract, @NonNull ActivityResultCallback<O> callback) { return prepareCall(contraguanxict, mActivityResultRegistry, callback); } 复制代码

prepare()方法接收两个参数,ActivityResultContract和ActivityResultCallback,返回值是ActivityResultLauncher。这几个名字取得都很好,见名知意。

ActivityResultContract

ActivityResultContract可以理解为一种协议,它是一个抽象类,提供了两个能力,createIntent和parseResult。这两个能力放到启动 Activity 中就很好理解了,createIntent 负责为 startActivityForResult 提供 Intent ,parseResult 负责处理 onActivityResult 中获取的结果。

上面的例子中,prepare() 方法传入的协议实现类是StartActivityForResult。它是ActivityResultContracts类中的静态内部类。除了StartActivityForResult之外,官方还默认提供了RequestPermissions,Dial,RequestPermission,TakePicture,它们都是ActivityResultContract的实现类。

所以,除了可以简化startActivityForResult,权限请求,拨打电话,拍照,都可以通过 Activity Result API 得到了简化。除了使用官方默认提供的这些之外,我们还可以自己实现 ActivityResultContract,在后面的代码中会进行演示。

ActivityResultCallback

public interface ActivityResultCallback<O> { /** * Called when result is available */ void onActivityResult(@SuppressLint("UnknownNullness") O result); } 复制代码

这个就比较简单了。当回调结果可用时,通过该接口通知。需要注意的一点是,由于 prepare() 方法的泛型限制,这里的返回值 result 一定是类型安全的。下表是系统内置协议和其返回值类型的对应关系。

Github

协议类型

返回值类型

StartActivityForResult ActivityResult

TakePicture Bitmap

Dial Boolean

RequestPermission Boolean

RequestPermissions Map<String,Boolean>

ActivityResultLauncher

prepare()方法的返回值。

prepare()方法其实会调用ActivityResultRegistry.registerActivityResultCallback()方法,具体的源码这里就不分析了,后面会单独写一篇源码解析。大致流程就是,自动生成 requestCode,注册回调并存储起来,绑定生命周期,当收到Lifecycle.Event.ON_DESTROY事件时,自动解绑注册。

代替startActivityForResult()的就是ActivityResultLauncher.launch()方法,最后会调用到ActivityResultRegistry.invoke()方法,如下所示:

@Override public <I, O> void invoke( final int requestCode, @NonNull ActivityResultContract<I, O> contract, I input) { Intent intent = contract.createIntent(input); if (ACTION_REQUEST_PERMISSIONS.equals(intent.getAction())) { // handle request permissions } else { ComponentActivity.this.startActivityForResult(intent, requestCode); } } 复制代码

中间那一块处理 request permissions 的我给掐掉了。这样看起来看清晰。本来准备单独水一篇QQ号出售平台源码解析的,这马上核心源码都讲完了。

前面展示过了startActivityForResult(),再来展示一下权限请求。

private val requestPermission = prepareCall(ActivityResultContracts.RequestPermission()){ result -> toast("request permission $result") } requestPermission.launch(Manifest.permission.READ_PHONE_STATE) 复制代码

拨打电话,拍照就不在这里展示了。所有的示例代码都已经上传到了我的 Github 。

如何自定义返回值 ?

前面提到的都是系统预置的协议,返回值也都是固定的。那么,如何返回自定义类型的值呢?其实也很简单,自定义ActivityResultContract就可以了。

我们以TakePicture为例,默认的返回值是Bitmap,现在我们让它返回Drawable。

private class TakePicDrawable : ActivityResultContract<Void,Drawable>(){ override fun createIntent(input: Void?): Intent { return Intent(MediaStore.ACTION_IMAGE_CAPTURE) } override fun parseResult(resultCode: Int, intent: Intent?): Drawable? { if (resultCode != Activity.RESULT_OK || intent == null) return null val bitmap = intent.getParcelableExtra<Bitmap>("data") return BitmapDrawable(bitmap) } } 复制代码

使用:

private val takePictureCustom = prepareCall(TakePicDrawable()) { result -> toast("take picture : $result") } pictureCustomBt.setOnClickListener { takePictureCustom()} 复制代码

这样就可以调用系统相机拍照并在结果回调中拿到 Drawable 对象了。

说好的解耦呢 ?

有时候我们可能会在结果回调中进行一些复杂的处理操作,无论是之前的onActivityResult()还是上面的写法,都是直接耦合在视图控制器中的。通过新的 Activity Result API,我们还可以单独的类中处理结果回调,真正做到单一职责。

其实 Activity Result API 的核心操作都是通过ActivityResultRegistry来完成的,ComponentActivity 中包含了一个ActivityResultRegistry对象 :

@NonNull public ActivityResultRegistry getActivityResultRegistry() { return mActivityResultRegistry; } 复制代码

现在要脱离 Activity 完成操作,就需要外部提供一个ActivityResultRegistry对象来进行结果回调的注册工作。同时,我们一般通过实现LifecycleObserver接口,绑定一个LifecycleOwner来进行自动解绑注册。完整代码如下:

class TakePhotoObserver( private val registry: ActivityResultRegistry, private val func: (Bitmap) -> Unit ) : DefaultLifecycleObserver { private lateinit var takePhotoLauncher: ActivityResultLauncher<Void?> override fun onCreate(owner: LifecycleOwner) { takePhotoLauncher = registry.registerActivityResultCallback( "key", ActivityResultContracts.TakePicture() ) { bitmap -> func(bitmap) } } fun takePicture(){ takePhotoLauncher() } } 复制代码

再玩点花出来 ?

在 Github 上看到了一些花式写法,和大家分享一下。

class TakePhotoLiveData(private val registry: ActivityResultRegistry) : LiveData<Bitmap>() { private lateinit var takePhotoLauncher : ActivityResultLauncher<Intent> override fun onActive() { super.onActive() registry.registerActivityResultCallback("key", ActivityResultContracts.TakePicture()){ result -> value = result } } override fun onInactive() { super.onInactive() takePhotoLauncher.dispose() } } 复制代码

通过绑定 LiveData 自动注册和解绑。

最后

不知道你如何看待最新的 Activity Result API ,欢迎在评论区留下你的意见。


更多相关文章

  1. 代码审查常见问题,建议收藏
  2. 趣学Spring:一文搞懂Aware、异步编程、计划任务
  3. “百行代码”实现简单的Python分布式爬虫
  4. Docker社区核心成员Doug Davis分享为社区贡献代码的技巧
  5. 用MongoDB Change Streams 在BigQuery中复制数据
  6. Spring Boot 2.3 新特性分层JAR
  7. 实力解剖一枚挖矿脚本,风骚操作亮瞎双眼
  8. 一文读懂 TS 中 Object, object, {} 类型之间的区别
  9. Netfliter状态跟踪之动态协议的实现浅析(tftp实现)

随机推荐

  1. Android 默认全面屏适配方案
  2. Android manifest属性
  3. 最新Android ADT, SDK, SDK_tool等官方下
  4. 基本控件学习以( RadioGroup和RadioButton
  5. Android抽屉式按钮实现
  6. android sdk更新失败解决办法
  7. 【30篇突击 android】源码统计二
  8. Android ProgressDialog的使用
  9. 《Android程序运行过程,Android》
  10. Android资料