欢迎关注千巅科技,江西领先的企业信息化服务商!
如何启动一个新的 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") ?: "") } } }
上面的逻辑中不乏重复的样板代码,且大多都耦合在视图控制器(Activity/Fragment)中,也就造成了不易测试。
细品一下,的确不是那么的合理。
可能一直以来我们也只有这一个选择,所以也很少看到有人抱怨 onActivityResult。精益求精的 Google 工程师为我们改进了这一问题。
下面来看看如何使用最新的 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 可以理解为一种协议,它是一个抽象类,提供了两个能力,createIntent 和 parseResult 。
这两个能力放到启动 Activity 中就很好理解了,createIntent 负责为 startActivityForResult 提供 Intent ,parseResult 负责处理 onActivityResult 中获取的结果。
上面的例子中,prepare() 方法传入的协议实现类是 StartActivityForResult 。
它是 ActivityResultContracts 类中的静态内部类。除了 StartActivityForResult 之外,官方还默认提供了 RequestPermissions ,Dial ,RequestPermission ,TakePicture,它们都是 ActivityResultContract 的实现类。
所以,除了可以简化 startActivityForResult ,权限请求,拨打电话,拍照,都可以通过 Activity Result API 得到了简化。
除了使用官方默认提供的这些之外,我们还可以自己实现 ActivityResultContract,在后面的代码中会进行演示。
public interface ActivityResultCallback<O> { /** * Called when result is available */ void onActivityResult(@SuppressLint("UnknownNullness") O result); }
这个就比较简单了。当回调结果可用时,通过该接口通知。
需要注意的一点是,由于 prepare() 方法的泛型限制,这里的返回值 result 一定是类型安全的。
下表是系统内置协议和其返回值类型的对应关系。
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 ,欢迎在评论区留下你的意见。
不能为空
不能为空