1. 老的实现方式
日常开发中,实现页面传值,通常通过startActivityForResult和onActivityResult配合,通过判断requestCode处理不同的业务场景:
startActivityForResult(intent, requestCode)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode == Activity.RESULT_OK && requestCode == 100) {
// 处理页面返回的参数
}
}
2. 新Activity Result API
private val startForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
//页面返回值
val data = result.data
}
}
// 打开页面
startForResult.launch(Intent(this, ActivityB::class.java))
上面的实现是不是简单明了,直接注册回调实现业务逻辑,有多个传值的业务就定义多个回调协定(ActivityResultContract),可以很好的单独处理,不用在onActivityResult里面通过requestCode判断,非常方便的实现业务解耦。
3.如何使用
3.1 添加依赖,Activity Result API是在Androidx Activity和Fragment中引入的
最新版本
implementation "androidx.activity:activity-ktx:1.2.2"
implementation "androidx.fragment:fragment-ktx:1.3.2"
3.2 注册协定,获取ActivityResultLauncher
private val selectData =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data = result.data
}
}
3.3 构造Intent传递参数,启动页面
btnA.setOnClickListener {
val intent = Intent(this, ActivityB::class.java)
selectData.launch(intent)
}
启动一个activity需要一个laucher,这个laucher由registerForActivityResult返回,这个方法需要两个参数,一个参数为一个抽象类——ActivityResultContract<I, O>的实现,另一个参数是一个函数式接口的实现(用一个lambda表达式来代替)。
public abstract class ActivityResultContract<I, O> {
/** Create an intent that can be used for {@link Activity#startActivityForResult} */
public abstract @NonNull Intent createIntent(@NonNull Context context,
@SuppressLint("UnknownNullness") I input);
/** Convert result obtained from {@link Activity#onActivityResult} to O */
@SuppressLint("UnknownNullness")
public abstract O parseResult(int resultCode, @Nullable Intent intent);
第一个方法是配置结合input配置intent, 第二个方法是从intent取出数据返回。
第二个参数是对从intent取出的数据的处理。
因此:官方为我们提供了很多便捷的实现好的第一个参数:
4. 已有的协定
预定义的Contract
大伙都看出来,新的Activity Results API使用起来好像有点麻烦,每次都得定义Contract。Google肯定考虑到了这个问题的,于是,Google 预定义了很多Contract,把你们能想到的使用场景基本上都想到了,它们都定义在类ActivityResultContracts中,有以下这些Contract:
StartActivityForResult()
RequestMultiplePermissions()
RequestPermission()
TakePicturePreview()
TakePicture()
TakeVideo()
PickContact()
CreateDocument()
OpenDocumentTree()
OpenMultipleDocuments()
OpenDocument()
GetMultipleContents()
GetContent()
下面分别介绍一下这些Contract:
StartActivityForResult: 通用的Contract,不做任何转换,Intent作为输入,ActivityResult作为输出,这也是最常用的一个协定。
RequestMultiplePermissions: 用于请求一组权限
RequestPermission: 用于请求单个权限
TakePicturePreview: 调用MediaStore.ACTION_IMAGE_CAPTURE拍照,返回值为Bitmap图片
TakePicture: 调用MediaStore.ACTION_IMAGE_CAPTURE拍照,并将图片保存到给定的Uri地址,返回true表示保存成功。
TakeVideo: 调用MediaStore.ACTION_VIDEO_CAPTURE 拍摄视频,保存到给定的Uri地址,返回一张缩略图。
PickContact: 从通讯录APP获取联系人
GetContent: 提示用选择一条内容,返回一个通过ContentResolver#openInputStream(Uri)访问原生数据的Uri地址(content://形式) 。默认情况下,它增加了Intent#CATEGORY_OPENABLE, 返回可以表示流的内容。
CreateDocument: 提示用户选择一个文档,返回一个(file:/http:/content:)开头的Uri。
OpenMultipleDocuments: 提示用户选择文档(可以选择多个),分别返回它们的Uri,以List的形式。
OpenDocumentTree: 提示用户选择一个目录,并返回用户选择的作为一个Uri返回,应用程序可以完全管理返回目录中的文档。
具体可参考:https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts
比如说,打开联系人列表选择一个联系人的ActivityResultContracts的实现为:
/**
* An {@link ActivityResultContract} to request the user to pick a contact from the contacts
* app.
* <p>
* The result is a {@code content:} {@link Uri}.
*
* @see ContactsContract
*/
public static final class PickContact extends ActivityResultContract<Void, Uri> {
@NonNull
@Override
public Intent createIntent(@NonNull Context context, @Nullable Void input) {
return new Intent(Intent.ACTION_PICK).setType(ContactsContract.Contacts.CONTENT_TYPE);
}
@Nullable
@Override
public Uri parseResult(int resultCode, @Nullable Intent intent) {
if (intent == null || resultCode != Activity.RESULT_OK) return null;
return intent.getData();
}
}
附上一个实例:
private val aLauncher =
registerForActivityResult(ActivityResultContracts.PickContact()) { uri: Uri? ->
// Handle the returned Uri
//crimesuspect启动activity的注册laucher
val queryFields = arrayOf(ContactsContract.Contacts.DISPLAY_NAME)
val cursor =
uri?.let {
requireActivity().contentResolver.query(
it,
queryFields,
null,
null,
null
)
}
cursor?.use {
if (it.count > 0) {
it.moveToFirst()
val suspect = it.getString(0)
crime.suspect = suspect
crimeDetailViewModel.saveCrime(crime)
binding.crimeSuspect.text = suspect
}
}
}设置监听器的时候:binding.crimeSuspect.setOnClickListener { aLauncher.launch()}
4.自定义协定
除了上面的预定义协定,我们还可以根据实际的业务定义自己的协定,当然实现也很简单,只需要继承ActivityResultContract<I, O>抽象类即可,ActivityResultContract类有两个抽象方法,
//创建Intent,在Activity#startActivityForResult中使用I input是定义的范型
public abstract Intent createIntent(Context context, I input)
//解析Activity#onActivityResult中的返回结果,返回范型O
public abstract O parseResult(int resultCode, Intent intent)
下面实现一个例子:
// 定义协定
class MyActivityResultContract : ActivityResultContract<Int, String>() {
override fun createIntent(context: Context, input: Int?): Intent {
return Intent(context, ActivityB::class.java).apply {
putExtra("projectId", input)
}
}
override fun parseResult(resultCode: Int, intent: Intent?): String? {
if (intent == null || !intent.hasExtra("projectName")) {
return null
}
return intent.getStringExtra("projectName").orEmpty()
}
}
//注册协定
private val getProject = registerForActivityResult(MyActivityResultContract()) {
//it 是返回值
}
//启动页面
getProject.launch(23)
5.非Activity、Fragment页面接收协定结果
一般的onActivityResult只能在Activity类处理返回结果,处理业务逻辑,增加了代码的耦合性,同时有很多场景希望在自己的类里面接收结果,处理逻辑,比如在Adapter里面响应点击事件,处理返回结果,下面介绍如何在业务类实现打开新页面,处理返回结果。
页面的启动管理是通过ActivityResultRegistry实现的,Activity里面已经定义好了ActivityResultRegistry方便我们使用,在自己的类里面只需要传递过来就可以处理跳转逻辑了。
class MyLifecycleObserver(private val registry : ActivityResultRegistry)
: DefaultLifecycleObserver {
lateinit var getContent : ActivityResultLauncher<String>
override fun onCreate(owner: LifecycleOwner) {
getContent = registry.register("key", owner, GetContent()) { uri ->
// Handle the returned Uri
}
}
fun selectImage() {
getContent.launch("image/*")
}
}
class MyFragment : Fragment() {
lateinit var observer : MyLifecycleObserver
override fun onCreate(savedInstanceState: Bundle?) {
// ...
observer = MyLifecycleObserver(requireActivity().activityResultRegistry)
lifecycle.addObserver(observer)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val selectButton = view.findViewById<Button>(R.id.select_button)
selectButton.setOnClickListener {
// Open the activity to select an image
observer.selectImage()
}
}
}
使用 ActivityResultRegistry API 时,强烈建议您使用可接受 LifecycleOwner 作为参数的 API,因为 LifecycleOwner 会在 Lifecycle 被销毁时自动移除已注册的启动器。不过,如果 LifecycleOwner 不存在,每个 ActivityResultLauncher 类都允许您手动调用 unregister() 作为替代。