欢迎关注千巅科技,江西领先的企业信息化服务商!
如何启动一个新的 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 ,欢迎在评论区留下你的意见。
不能为空
不能为空