Activity: 三、Android Result API

AndroidResultAPI 提供了一种更优雅的方式来管理 onActivityResult(),通过定义协议类,实现了请求和响应的参数管理,使代码更清晰、可读性更强。它通过创建启动器、启动和响应解析来封装启动Activity的过程,避免了传统方式中requestCode的混乱和Intent参数准备、解析的错误。此外,Android还预定义了一些常用协议,方便开发者快速集成常见功能。


一、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图片预览
下一篇: Service: 一、简介,分类,生命周期, 简单用法

<think>我们参考用户提供的引用内容,特别是引用[1]和引用[4]与问题直接相关。用户的问题:如何在Android中使用ResultAPI,并请求示例代码。根据引用[1]:ActivityResultAPI包含两个主要方法:-createIntent():创建一个用于startActivityForResult的Intent-parseResult():对onActivityResult的结果进行转换此外,我们知道在Android中,使用ActivityResultAPI可以避免在Activity中重写onActivityResult方法,而是通过注册一个ActivityResultLauncher来启动活动并处理结果。步骤:1.在Activity/Fragment中注册一个ActivityResultContract(系统预定义或自定义)和对应的回调处理。2.使用注册返回的ActivityResultLauncher来启动活动。3.在回调中处理结果。示例代码(使用预定义的合约,例如启动活动并获取结果):假设我们想要启动一个活动来获取文本内容(例如,从另一个活动返回一个字符串),我们可以使用预定义的合约ActivityResultContracts.StartActivityForResult。但是,更具体的例子:我们可以使用ActivityResultContracts.Pick来选择一个图片,然后显示在ImageView中。根据引用[4]中有一个布局文件,其中包含一个Button和一个ImageView,我们可以用这个布局来实现选择图片并显示的功能。下面我们结合引用[1]和引用[4]来写一个示例:步骤1:在Activity中注册一个ActivityResultLauncher步骤2:设置按钮点击事件,使用这个launcher启动选择图片的Intent步骤3:在回调中处理返回的Uri,并设置到ImageView上示例代码:首先,确保在AndroidManifest.xml中添加必要的权限(如果从外部存储选择图片,需要READ_EXTERNAL_STORAGE权限,但注意Android10以后可能需要使用MediaStore,并且权限策略有变化,这里为了示例简化,假设我们使用ActivityResultContracts.Pick,它不需要直接请求权限,因为系统会处理)。然后,在Activity中:</think> ### Android Activity Result API 使用示例 在 Android 中,Activity Result API 提供了更清晰的方式处理活动间通信,避免了传统的 `onActivityResult()` 回调。以下是完整实现代码: ```kotlin // MainActivity.kt class MainActivity : AppCompatActivity() { // 1. 声明结果启动器 private lateinit var getContent: ActivityResultLauncher<String> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 2. 注册结果合约 getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> // 3. 处理结果 uri?.let { findViewById<ImageView>(R.id.img).setImageURI(uri) } } // 4. 触发选择操作 findViewById<Button>(R.id.get_content).setOnClickListener { getContent.launch("image/*") // 指定MIME类型 } } } ``` ```xml <!-- activity_main.xml --> <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <Button android:id="@+id/get_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Select Image" /> <ImageView android:id="@+id/img" android:layout_width="300dp" android:layout_height="300dp" android:layout_marginTop="24dp" android:scaleType="centerCrop" /> </LinearLayout> ``` ### 核心组件解析: 1. **ActivityResultContracts** - `GetContent()`: 文件选择合约[^1] - `TakePicture()`: 拍照合约 - `RequestPermission()`: 权限请求合约 - 自定义合约需实现 `createIntent()` 和 `parseResult()` 2. **ActivityResultLauncher** - 通过 `registerForActivityResult()` 注册 - 使用 `launch()` 触发操作并传递参数 3. **结果处理** - 在 lambda 表达式中直接获取类型化结果(本例中为 `Uri?`) - 无需处理 `requestCode` 和 `resultCode`[^1] ### 优势对比传统方式: | 传统方式 | Result API | |---------|-----------| | 需要重写 `onActivityResult()` | 逻辑隔离更清晰 | | 请求码易冲突 | 无请求码管理 | | 结果处理分散 | 结果集中处理 | | 类型转换复杂 | 自动类型安全 | > ⚠️ **注意**:此 API 需要添加依赖: ```gradle dependencies { implementation "androidx.activity:activity-ktx:1.6.0" implementation "androidx.fragment:fragment-ktx:1.5.5" } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值