IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Activity: 三、Android Result API -> 正文阅读

[移动开发]Activity: 三、Android Result API


一、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)
        }
    }
}

为什么要这样写?
当该页面存在多处调用时:

  1. 我们不想因为修改一个参数的问题, 而修改每个调用类. 或者遗漏调用类;
  2. 当某个参数写错,或者类型错误时, 带来错误的程序结果, 甚至崩溃.

因此为方便管理. 我们把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()) {
    	// it 是 ActivityResult 类;
        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.注册返回启动器

// ComponentActivity
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
	@NonNull final ActivityResultContract<I, O> contract,
    @NonNull final ActivityResultRegistry registry,
    @NonNull final ActivityResultCallback<O> callback) {
    // getAndIncrement 是一个 int类型 自增函数;
    return registry.register(
            "activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);
}

// ActivityResultRegistry
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();
	
	// 这里判断了 生命周期. 必须要在 started 之前;
    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.");
    }
	// registerKey():  
	// 1.获取 requestCode; 随机数获取, 重复则再次随机; 
	// 2.将 requestCode 与 key 的关系用 map 保存;
	final int requestCode = registerKey(key);
	
	...	// 这里是 对生命周期的监听操作; 省略.

	// 最后返回一个 ActivityResultLauncher(启动器) 对象
	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.启动

// ActivityResultRegistry -> register() -> 匿名内部类
public void launch(I input, @Nullable ActivityOptionsCompat options) {
	// 保存key, 标志这个key对应的启动器已执行;
    mLaunchedKeys.add(key);
    // 通过 key, 拿固定的 requestCode
    Integer innerCode = mKeyToRc.get(key);
    onLaunch((innerCode != null) ? innerCode : requestCode, contract, input, options);
}

// ComponentActivity -> mActivityResultRegistry
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;

		// 1. 获取直接响应 的结果; 什么情况下需要直接响应呢? 
		// 目前只有申请权限时, 如果权限已经存在, 则直接响应; 
		// 因此 RequestMultiplePermissions,RequestPermission 重写了该方法;
        final ActivityResultContract.SynchronousResult<O> synchronousResult =
                contract.getSynchronousResult(activity, input);
        if (synchronousResult != null) {
        	// 如果存在直接返回的结果, 则直接响应;
            new Handler(Looper.getMainLooper()).post(...);
            return;
        }

        // 调用我们自定义的 协议类, 创建Intent
        Intent intent = contract.createIntent(activity, input);

		...	// 操作转场动画启动参数; 省略      

        if (ACTION_REQUEST_PERMISSIONS.equals(intent.getAction())) {
        	...	// 权限申请时, 防止 null异常; 省略
            ActivityCompat.requestPermissions(activity, permissions, requestCode);
        } else if (ACTION_INTENT_SENDER_REQUEST.equals(intent.getAction())) {
            ...
        } else {
            // 最后启动 startActivity
            ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle);
        }
    }
};

这一步干了啥:

  • 保存要接收响应的 key
  • 判断是否需要直接响应
  • 通过 协议类, 创建 Intent; 最终startActivityForResult

3.响应

// ComponentActivity 
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
	// 把响应截获, 先看是否由 mActivityResultRegistry 处理结果; 
	// 因此我们也可以重写, onActivityResult. 自己写更高级别的处理方式; 
	// 因此, 如果我们重写了 onActivityResult, 那得调到 super.onActivityResult()
    if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) {
        super.onActivityResult(requestCode, resultCode, data);
    }
}

// ActivityResultRegistry
public final boolean dispatchResult(int requestCode, int resultCode, @Nullable Intent data) {
	// 通过 requestCode 取出 key
    String key = mRcToKey.get(requestCode);
    // 没有这个key; 代表这次响应不归我管(并不是由 Android Result API 方式启动);
    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图片预览
下一篇: 酝酿中

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-28 09:27:50  更:2021-08-28 09:29:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/31 6:25:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码