是时候丢掉 onActivityResult 了 !

简介: 是时候丢掉 onActivityResult 了 !

为什么要丢掉 onActivityResult ?


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

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


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

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


举个最简单的场景,MainActivity 跳转到 SecondActivitySecondActivity 中按钮触发返回并传值回来。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() 方法接收两个参数,ActivityResultContractActivityResultCallback ,返回值是 ActivityResultLauncher 。这几个名字取得都很好,见名知意。


ActivityResultContract

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


上面的例子中,prepare() 方法传入的协议实现类是 StartActivityForResult 。它是 ActivityResultContracts 类中的静态内部类。除了 StartActivityForResult  之外,官方还默认提供了 RequestPermissionsDialRequestPermissionTakePicture ,它们都是 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 的我给掐掉了。这样看起来看清晰。本来准备单独水一篇源码解析的,这马上核心源码都讲完了。


前面展示过了 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 ,欢迎在评论区留下你的意见。



相关文章
|
存储 Android开发
Android startActivityForResult和onActivityResult的基本用法
Android startActivityForResult和onActivityResult的基本用法
113 0
|
存储 安全 API
还在用startActivityForResult处理数据传递?试试ActivityResultContract吧
还在用startActivityForResult处理数据传递?试试ActivityResultContract吧
844 0
还在用startActivityForResult处理数据传递?试试ActivityResultContract吧
startActivityForResult解析
startActivityForResult解析
131 0
startActivityForResult解析
|
安全 Android开发
如何向前一个Fragment回传信息?
我们使用Activity的时候,可以通过startActivityForResult来启动一个新的Activity,然后在新的Activity中可以用setResult来向前一个Activity回传一些结果信息,前一个Activity会在onActivityResult中得到这个信息。 那么当我们使用Fragment的形式来作为页面的时候呢?我们知道可以使用setArguments向后面的Fragment传递信息,但是如何回传结果信息? 作者:BennuCTech 链接:https://juejin.cn/post/7033953422357299231 来源:稀土掘金 著作权归作者所有。商
148 0
|
索引
解决Fragment多层嵌套时onActivityResult无法正确回调的问题
解决Fragment多层嵌套时onActivityResult无法正确回调的问题
|
API 开发者 数据处理
关于startActivityForResult
startActivityForResult使用场景是什么?requestCode、resultCode含义是什么? 使用场景 用户开始新的活动,并且希望得到新活动的某些信息。
948 0
Activity与Fragment的onActivityResult细节
在Fragment中跳转到另一个Activity,返回时是调用Activity的onActivityResult呢?还是调用Fragment的onActivityResult呢? 我当然知道,只不过我再碰到时忘记了,然后每次忘记都要从新去网上找,不如做个Test然后记录下来。
1271 0