一、Android Result API 的优势?
我们先来看一下, 下面这段代码:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
when (requestCode) {
REQUESTCODE_GET_URL -> {}
REQUESTCODE_SELECT -> {}
....
}
}
}
这样类似的代码 可以说是随处可见; 对于管理比较规范的项目来说, 倒还好. 不规范的呢如下:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
when (requestCode) {
1 -> {}
2 -> {}
....
}
}
}
更可能注释都没写. 结果的处理代码可能全都堆在 onActivityResult() 中; 最终导致 onActivityResult() 中的代码能有几百行甚至更多. 难以阅读,一眼极乱,让人火大 😂
然后, 我们再看看这段代码:
companion object{
fun getCallingIntent(context: Context?,
userId: Int, source: Int = 0, checked: Boolean = false): Intent {
return Intent(context, InviteShareActivity::class.java).also {
it.putExtra("userId", userId)
it.putExtra("source", source)
it.putExtra("checked", checked)
}
}
}
为什么要这样写? 当该页面存在多处调用时:
- 我们不想因为修改一个参数的问题, 而修改每个调用类. 或者遗漏调用类;
- 当某个参数写错,或者类型错误时, 带来错误的程序结果, 甚至崩溃.
因此为方便管理. 我们把Intent的创建抽调出来; 而在 Android Result API 中, 我们将会定义协议来管理它;
再然后, 响应的Intent需要解析. 可能还需要预处理; 这段代码同样存在 参数易错,一改都改,各种null判断等; 如果能把这部分代码抽调出来复用. 那再好不过了.
总结:
onActivityResult() 中代码臃肿, 混乱. 启动页面时 Intent的参数准备. 页面响应时的参数解析. 它们较容易出错, 且同样的代码存在多处
Android Result API:
将每个请求的响应逻辑单独书写, 代码分离 通过定义协议, 统一处理: 请求时Intent的参数准备; 响应时Intent的参数解析
二、简单使用
1.引入库
implementation "androidx.activity:activity-ktx:1.3.0"
implementation "androidx.fragment:fragment-ktx:1.3.6"
2.单参数请求及响应
①.定义协议类
需继承 ActivityResultContract 类; 提供请求及响应参数的泛型
class ResultTwoContract : ActivityResultContract<String, UserInfo>() {
override fun createIntent(context: Context, id: String?) =
Intent(context, ResultTwoActivity::class.java)
.putExtra("id", id)
override fun parseResult(resultCode: Int, intent: Intent?) =
if (resultCode == RESULT_OK) {
intent?.getSerializableExtra("user") as UserInfo?
} else {
null
}
}
这里 请求时传入一个 id(String类型), 响应时返回一个 UserInfo; 所以 ActivityResultContract 的泛型为 : <String, UserInfo>
看一下两个重写函数:
函数 | 意义 |
---|
createIntent() | 准备请求的 Intent. | parseResult() | 解析响应参数,解析成我们想要的参数类型. 这里可以加入预处理的代码 |
②.创建启动器
private val launcher2 = registerForActivityResult(ResultTwoActivity.ResultTwoContract()) {
Toast.makeText(applicationContext, it?.name, Toast.LENGTH_SHORT).show()
}
这里的回调就相当于在 onActivityResult 中处理结果;
注意:
启动器的实例化 需要在 Activity started 之前; 否则会抛异常 回调的参数(这里默认it), 就是协议类中定义的响应类型
③.启动
launcher2.launch("123")
launch 就相当于 startActivityForResult了; 不用给 requestCode; 因为协议泛型中定义的请求参数类型为 String, 所以这里需要传入 String 参数;
如果是转场动画启动, 则使用双参启动方式, 如下:
launcher2.launch("123", ActivityOptionsCompat.makeSceneTransitionAnimation(this))
④.后页 setResult
这一步跟以前一样;
setResult(RESULT_OK, Intent().putExtra("user", UserInfo("小明")))
⑤.小结
这代码量虽然跟 startActivityForResult 的时候差不多; 但提高了代码可读性, 分离职责 方便迭代维护. 启动器的实例化 需要在 Activity started 之前; 否则会抛异常; 所以页面启动时就要定义好. 并且不能懒加载
3.多参数请求及响应
我们会发现, ActivityResultContract 只有两个泛型. 我们请求时有多个参数怎么办?
好吧这并不好办; 我们只能用 Object 数组或集合来传递数据; 如下:
class ResultThreeContract : ActivityResultContract<Array<Any>, Array<Any>>() {
override fun createIntent(context: Context, array: Array<Any>) =
Intent(context, ResultThreeActivity::class.java)
.putExtra("id", array[0] as String?)
.putExtra("num", array[1] as Int)
.putExtra("user", array[2] as Serializable?)
override fun parseResult(resultCode: Int, intent: Intent?): Array<Any>? =
if (resultCode == RESULT_OK) {
arrayOf(intent?.getSerializableExtra("user") as UserInfo)
} else {
null
}
}
可以看到, 博主请求和响应参数 都用的数组; 启动时如下:
launcher3.launch(arrayOf("123", 1, UserInfo("小红")))
但博主认为这样并不太优雅. 容易弄错; 还有一种方式, 使用API已定义好的协议类: StartActivityForResult; 但该类是要自己处理 Intent;
三、已定义好的协议类
除了要自己定义协议以外, Android 还帮我们定义了一些常用协议
1.概览
类名 | 泛型 | 简介 |
---|
StartActivityForResult | <Intent, ActivityResult> | 自己组装Intent, 自己处理响应 | RequestPermission | <String, Boolean> | 请求指定权限, 响应是否申请成功 | RequestMultiplePermissions | <String[], Map<String, Boolean>> | 申请一组权限 | TakePicturePreview | <Void, Bitmap> | 拍照; 注意这里的 Void, 代表不需要参数(Kotlin 可使用 Void? 或 Unit) | TakePicture | <Uri, Boolean> | 拍照, 指定Uri(保存路径), 响应是否成功 | TakeVideo | <Uri, Bitmap> | 拍视频, 指定Uri(保存路径), 返回视频缩略图 已弃用, 因缩略图很少返回. 建议用 CaptureVideo | CaptureVideo | <Uri, Boolean> | 拍视频, 保存到指定Uri. 响应是否成功 | PickContact | <Void, Uri> | 通讯录选取联系人 | CreateDocument | <String, Uri> | 创建文档, 传入建议文件名. (随后会选路径) | OpenDocumentTree | <Uri, Uri> | 给定路径(Uri), 返回文档树? | OpenMultipleDocuments | <String[], List | 输入过滤类型(如: “image/*”), 返回文档集合 | OpenDocument | <String[], Uri> | 输入过滤类型, 返回文档 | GetMultipleContents | <String, List> | 输入过滤类型, 返回 Content? | GetContent | <String, Uri> | 输入过滤类型, 返回 |
2.随便测试几个
①.StartActivityForResult:
private val launcher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
Toast.makeText(this, it.data?.getStringExtra("s"), Toast.LENGTH_SHORT).show()
}
}
launcher.launch(
Intent(this, ResultThreeActivity::class.java)
.putExtra("str", "我是小旋风")
.putExtra("entity", UserInfo().also { it.name = "小明" })
.putExtra("num", 20))
再看一下 ActivityResult 类:
public final class ActivityResult implements Parcelable {
private final int mResultCode;
@Nullable
private final Intent mData;
...
}
ActivityResult 类存放 resultCode , 以及响应的 Intent; 可以看出, 请求参数组装, 响应参数解析 这里都需要自行处理.
②.TakePicturePreview
private val launcher4 = registerForActivityResult(ActivityResultContracts.TakePicturePreview()) {
binding.ivImg.setImageBitmap(it)
}
launcher4.launch(null)
这里响应的 bitmap 是缩略图, 很不清晰;
③.其他 兄弟们, 想了解的自己测吧, 大多不是太常用;
private val launcher5 = registerForActivityResult(ActivityResultContracts.GetContent()) {}
private val launcher6 = registerForActivityResult(ActivityResultContracts.CreateDocument()) {}
private val launcher7 = registerForActivityResult(ActivityResultContracts.PickContact()) {}
launcher5.launch("image/*")
launcher6.launch("ccc.txt")
launcher7.launch(null)
四、源码分析
1.注册返回启动器
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultRegistry registry,
@NonNull final ActivityResultCallback<O> callback) {
return registry.register(
"activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);
}
public final <I, O> ActivityResultLauncher<I> register(
@NonNull final String key,
@NonNull final LifecycleOwner lifecycleOwner,
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultCallback<O> callback) {
Lifecycle lifecycle = lifecycleOwner.getLifecycle();
if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
throw new IllegalStateException("LifecycleOwner " + lifecycleOwner + " is "
+ "attempting to register while current state is "
+ lifecycle.getCurrentState() + ". LifecycleOwners must call register before "
+ "they are STARTED.");
}
final int requestCode = registerKey(key);
...
return new ActivityResultLauncher<I>() {
@Override
public void launch(I input, @Nullable ActivityOptionsCompat options) {
mLaunchedKeys.add(key);
Integer innerCode = mKeyToRc.get(key);
onLaunch((innerCode != null) ? innerCode : requestCode, contract, input, options);
}
...
};
这一步干了啥:
总体来说是注册并创建启动器, 中间有对生命周期的监听操作; 并生成固定的 requestCode 与 key 对应存储;
这个自增 key 的作用是什么呢? 让我们接着看;
2.启动
public void launch(I input, @Nullable ActivityOptionsCompat options) {
mLaunchedKeys.add(key);
Integer innerCode = mKeyToRc.get(key);
onLaunch((innerCode != null) ? innerCode : requestCode, contract, input, options);
}
private final ActivityResultRegistry mActivityResultRegistry = new ActivityResultRegistry() {
@Override
public <I, O> void onLaunch(
final int requestCode,
@NonNull ActivityResultContract<I, O> contract,
I input,
@Nullable ActivityOptionsCompat options) {
ComponentActivity activity = ComponentActivity.this;
final ActivityResultContract.SynchronousResult<O> synchronousResult =
contract.getSynchronousResult(activity, input);
if (synchronousResult != null) {
new Handler(Looper.getMainLooper()).post(...);
return;
}
Intent intent = contract.createIntent(activity, input);
...
if (ACTION_REQUEST_PERMISSIONS.equals(intent.getAction())) {
...
ActivityCompat.requestPermissions(activity, permissions, requestCode);
} else if (ACTION_INTENT_SENDER_REQUEST.equals(intent.getAction())) {
...
} else {
ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle);
}
}
};
这一步干了啥:
保存要接收响应的 key 判断是否需要直接响应 通过 协议类, 创建 Intent; 最终startActivityForResult
3.响应
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
public final boolean dispatchResult(int requestCode, int resultCode, @Nullable Intent data) {
String key = mRcToKey.get(requestCode);
if (key == null) {
return false;
}
mLaunchedKeys.remove(key);
doDispatch(key, resultCode, data, mKeyToCallback.get(key));
return true;
}
总结
所以 Android Result API 的优势:
代码分离, 复用, 方便维护管理. 并省了写 requestCode
Android Result API 原理:
key 与启动器 一一对应, 与 requestCode 一一对应 拦截 onActivityResult() 优先交给 ActivityResultRegistry 处理 所以, 它就是给 startActivityForResult-onActivityResult 做了一层封装
上一篇: Activity: 二、转场动画, RecycleView+ViewPager图片预览 下一篇: 酝酿中
|