欢迎关注千巅科技,江西领先的企业信息化服务商!

南昌APP开发电话业务咨询:186-7912-6858    faq帮助中心 pay付款方式

行业新闻

Google 更新:Android开发者是时候丢掉 onActivityResult 了 !

发布日期:2020-04-23

Google 更新:Android开发者是时候丢掉 onActivityResult 了 !

为什么要丢掉 onActivityResult ?

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

你的答案肯定是 startActivityForResult 和 onActivityResult 。

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

当遇到多个 Activity 跳转的时候痛不欲生,以至于很多同学都放弃了写 onActivityResult,改用 EventBus 来传递结果,终于等到这一天,Google 要对这个 API 下手了!

在最新的 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") ?: "")
        }
    }
}
  • 定义一个 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 一定是类型安全的。

下表是系统内置协议和其返回值类型的对应关系。

  • StartActivityForResult -》ActivityResult
  • TakePicture -》 Bitmap
  • Dial/RequestPermission -》Boolean
  • RequestPermissions -》 Map


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)

拨打电话,拍照就不在这里展示了。


如何自定义返回值 ?

前面提到的都是系统预置的协议,返回值也都是固定的。那么,如何返回自定义类型的值呢?其实也很简单,自定义 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 ,欢迎在评论区留下你的意见。

新闻留言

姓名 (*) :

不能为空

内容 (*) :

不能为空

咨询热线 186-7912-6858

微信公众号

扫一扫官方微信