又忙了好一段时间,秋天是收获的季节啊。是时候总结一波咯。这次带来的是新API的使用。 PS: 关于Android的博客文章,以后都会使用Kotlin来进行展示,还没有学习Kotlin的小伙伴抓紧学习波咯,这是Android的趋势。
在项目开发中,发现startActivityForResult
和onActivityResult
方法已经被废弃了,这是为什么呢?有代码强迫症的我开始了研究。
/**
* {@inheritDoc}
*
* @deprecated use
* {@link #registerForActivityResult(ActivityResultContract, ActivityResultCallback)}
* passing in a {@link StartActivityForResult} object for the {@link ActivityResultContract}.
*/
@Override
@Deprecated
public void startActivityForResult(@SuppressLint("UnknownNullness") Intent intent,
int requestCode) {
super.startActivityForResult(intent, requestCode);
}
/**
* {@inheritDoc}
*
* @deprecated use
* {@link #registerForActivityResult(ActivityResultContract, ActivityResultCallback)}
* with the appropriate {@link ActivityResultContract} and handling the result in the
* {@link ActivityResultCallback#onActivityResult(Object) callback}.
*/
@CallSuper
@Override
@Deprecated
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
在Android应用程序开发中,比较常见的场景是从启动的Activity获取数据,传统的方式是使用startActivityForResult
方法来启动下一个Activity,然后通过onActivityResult
方法来接收返回的结果。
val intent = Intent(this, ResultActivity::class.java)
startActivityForResult(intent, ACTION_CODE)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == ACTION_CODE && resultCode == Activity.RESULT_OK) {
val title = intent.getStringExtra("title")
Log.e("yhj", "onActivityResult: " + title)
}
}
这种方式不仅能在同一个应用中获取数据,也可以从其它应用中获取数据,例如调用系统相机,相册获取图片,获取系统通讯录等。
但随之产生了许多问题,随着应用功能不断添加迭代,onActivityResult
方法会存在各种处理数据的回调,嵌套严重,难以维护,并且还得定义一堆额外的常量REQUEST_CODE,用于判断是哪个请求的回调结果。
Google可能已经到了onActivityResult方法的这些问题,因此在androidx.activity:activity:1.2.0-alpha02
和androidx.fragment:fragment:1.3.0-alpha02
中,已经废弃了startActivityForResult
和onActivityResult
方法。并且推出了一种新的API👉Activity Results API。
Activity Results API 是 Google官方推荐的Activity、Fragment获取返回结果的方式。
Activity Results API中有两个重要的组件,ActivityResultContract
和ActivityResultLauncher
。
ActivityResultContract
。它定义了如何传递数据和如何处理返回的数据。它是一个抽象类,你需要继承它来创建自己的协议,每个 ActivityResultContract
都需要定义输入和输出类,如果您不需要任何输入,默认使用 Void(在 Kotlin 中,使用 Void? 或 Unit)作为输入类型。ActivityResultLauncher
。启动器,调用ActivityResultLauncher
的launch
方法来启动页面跳转,作用相当于原来的startActivity()新建一个MyResultContract
类,继承自 ActivityResultContract<I, O>
,I是输入类型,O是输出类型。实现两个方法,createIntent
和parseResult
。输入类型I作为createIntent
方法的参数,输出类型O作为parseResult
方法的返回值。
class MyResultContract : ActivityResultContract<Int, String>() {
override fun createIntent(context: Context, input: Int?): Intent {
return Intent(context, ResultActivity::class.java)
}
override fun parseResult(resultCode: Int, intent: Intent?): String {
val data = intent?.getStringExtra("title")
return if (data != null && resultCode == Activity.RESULT_OK) data
else ""
}
}
我在createIntent
方法里创建了Intent,在parseResult
方法里处理返回的数据。
使用registerForActivityResult
方法,该方法由ComponentActivity或者Fragment提供,接受2个参数,第一个参数就是我们定义的Contract,第二个参数是一个回调ActivityResultCallback<O>
,其中O就是前面Contract的输出类型。
private val myActivityLauncher = registerForActivityResult(MyResultContract()) { result ->
Log.e("yhj", result)
}
result就是从上个界面传递回来的数据,,registerForActivityResult
方法的返回值是ActivityResultLauncher
。
调用ActivityResultLauncher
的launch
方法进行界面跳转。
myActivityLauncher.launch(ACTION_CODE)
但是Activity Results API使用起来还是有点麻烦,每次都得定义Contract。
Google也考虑到了这个问题,已经自定义了很多Contract,覆盖了开发中的使用场景。
在类ActivityResultContracts
中,系统已经定义如下图所示的Contract,具体可查看源码说明。
StartActivityForResult()
。通用的Contract,不做任何转换,Intent作为输入,ActivityResult作为输出。这是最常用的一个Contract。private val activityResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
val result = activityResult.data?.getStringExtra("title")
result?.let { Log.e("yhj", it) }
}
}
activityResultLauncher.launch(Intent(this, ResultActivity::class.java))
RequestPermission()
。用于请求单个权限。private val singlePermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
when {
isGranted -> Log.e("yhj", "申请成功")
!ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.CAMERA
) -> Log.e("yhj", "拒绝且不在询问")
else -> Log.e("yhj", "拒绝")
}
}
singlePermissionLauncher.launch(Manifest.permission.CAMERA)
RequestMultiplePermissions()
。用于请求一组权限。private val permissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.entries.forEach { permission ->
if (!permission.value) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(
this,
permission.key
)
) {
Log.e("yhj", "${permission.key}拒绝且不在询问")
} else {
Log.e("yhj", "${permission.key}拒绝")
}
}
}
}
permissionsLauncher.launch(
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_FINE_LOCATION
)
)
TakePicture()
。调用系统相机拍照,并将图片保存到指定Uri地址,返回true则保存成功。 private val captureLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) {
Toast.makeText(this, "$it", Toast.LENGTH_SHORT).show()
}
if (havePermission(Manifest.permission.CAMERA)) {
val name = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.getDefault()).format(System.currentTimeMillis())
val file = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), name)
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
FileProvider.getUriForFile(this, applicationContext.packageName + ".provider", file)
} else {
Uri.fromFile(file)
}
captureLauncher.launch(uri)
} else {
singlePermissionLauncher.launch(Manifest.permission.CAMERA)
}
TakePicturePreview()
。调用系统相机拍照,返回为Bitmap的图片。TakeVideo()
。调用系统录像拍摄视频,保存到给定的Uri地址,返回一张缩略图。PickContact()
。从手机通讯录获取联系人。GetContent()
。提示用户选择一条内容,返回一个通过ContentResolver.openInputStream(Uri)
访问原生数据的Uri地址(content://形式) 。默认情况下,它增加了Intent.CATEGORY_OPENABLE
, 返回可以表示流的内容。OpenMultipleDocuments()
。提示用户选择文件(可以选择多个),以List的形式,分别返回它们的Uri。OpenDocumentTree()
。提示用户选择一个目录,并返回用户选择的作为一个Uri返回,应用程序可以完全管理返回目录中的文档OpenDocument()
。提示用户打开一个文件,接收其内容(file:/http:/content:)作为Uri 。输入是要过滤的 mime 类型,例如image/* 。GetMultipleContents()
。提示用户选择多个内容,,以List的形式,分别返回它们的Uri。默认情况下,它增加了Intent.CATEGORY_OPENABLE
, 返回可以表示流的内容。CreateDocument()
。提示用户选择一个文档,返回一个(file:/http:/content:)开头的Uri。在Activity和Fragment中,我们能直接使用registerForActivityResult
方法 ,那是因为ConponentActivity和Fragment基类实现了ActivityResultCaller 接口,在非Activity/Fragment中,如果我们想要接收Activity回传的数据,可以直接使用ActivityResultRegistry
来实现。
class MyLifecycleObserver(private val registry: ActivityResultRegistry) : DefaultLifecycleObserver {
private lateinit var getContent: ActivityResultLauncher<String>
private lateinit var onUriListener: OnUriListener
override fun onCreate(owner: LifecycleOwner) {
getContent =
registry.register("key", owner, ActivityResultContracts.GetContent()) { uri ->
onUriListener.onUri(uri)
}
}
fun selectImage(onUriListener: OnUriListener) {
this.onUriListener = onUriListener
getContent.launch("image/*")
}
}
observer = MyLifecycleObserver(activityResultRegistry)
lifecycle.addObserver(observer)
observer.selectImage(object : OnUriListener {
override fun onUri(uri: Uri) {
try {
val descriptor = contentResolver.openFileDescriptor(uri, "r")
if (descriptor != null) {
val bitmap = BitmapFactory.decodeFileDescriptor(descriptor.fileDescriptor)
binding.ivContent.setImageBitmap(bitmap)
}
} catch (e: FileNotFoundException) {
e.printStackTrace()
}
}
})
为什么要实现LifecycleObserver
呢,因为使用生命周期组件,LifecycleOwner
会在 Lifecycle 被销毁时自动移除已注册的启动器。当然你也可以手动调用unregister
方法。
Activity和Fragment中为什么不需要手动调用unregister
方法呢,因为ComponentActivity和Fragment已经实现了LifecycleObserver
。源码如下
@NonNull
@Override
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);
}
//Fragment
registerOnPreAttachListener(new OnPreAttachedListener() {
@Override
void onPreAttached() {
final String key = generateActivityResultKey();
ActivityResultRegistry registry = registryProvider.apply(null);
ref.set(registry.register(key, Fragment.this, contract, callback));
}
});
ComponentActivity中有两个registerForActivityResult
方法,区别是有无ActivityResultRegistry
,默认情况下使用内部定义的mActivityResultRegistry
,然后调用ActivityResultRegistry.register
方法。当然可以自定义ActivityResultRegistry
,和前面非Activity/Fragment中接收数据中类似。
@NonNull
@Override
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);
}
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull ActivityResultContract<I, O> contract,
@NonNull ActivityResultCallback<O> callback) {
return registerForActivityResult(contract, mActivityResultRegistry, callback);
}
register
方法内部会将ActivityResultContract
存入一个HashMap
中。register方法接受一个LifecycleOwner
,在合适的生命周期将回调存入或移除Map,保证回调响应的时机正确。
Lifecycle lifecycle = lifecycleOwner.getLifecycle();
LifecycleEventObserver observer = new LifecycleEventObserver() {
@Override
public void onStateChanged(
@NonNull LifecycleOwner lifecycleOwner,
@NonNull Lifecycle.Event event) {
if (Lifecycle.Event.ON_START.equals(event)) {
mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));
if (mParsedPendingResults.containsKey(key)) {
@SuppressWarnings("unchecked")
final O parsedPendingResult = (O) mParsedPendingResults.get(key);
mParsedPendingResults.remove(key);
callback.onActivityResult(parsedPendingResult);
}
final ActivityResult pendingResult = mPendingResults.getParcelable(key);
if (pendingResult != null) {
mPendingResults.remove(key);
callback.onActivityResult(contract.parseResult(
pendingResult.getResultCode(),
pendingResult.getData()));
}
} else if (Lifecycle.Event.ON_STOP.equals(event)) {
mKeyToCallback.remove(key);
} else if (Lifecycle.Event.ON_DESTROY.equals(event)) {
unregister(key);
}
}
};
在register
方法中还发现requestCode
,以前onActivityResult
方法中需要通过requestCode
来识别是哪个startActivityForResult
方法的返回。现在都通过AutoIncrement
来管理。
final int requestCode = registerKey(key);
return registry.register(
"activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);
onSaveInstanceState
方法会自动保存requestCode
和ActivityResultRegistry
对应的key,当onActivityResult
方法返回requestCode
时,可以通过对应关系找到key,然后对应ActivityResultCallback
。
public final void onSaveInstanceState(@NonNull Bundle outState) {
outState.putIntegerArrayList(KEY_COMPONENT_ACTIVITY_REGISTERED_RCS,
new ArrayList<>(mRcToKey.keySet()));
outState.putStringArrayList(KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS,
new ArrayList<>(mRcToKey.values()));
outState.putStringArrayList(KEY_COMPONENT_ACTIVITY_LAUNCHED_KEYS,
new ArrayList<>(mLaunchedKeys));
outState.putBundle(KEY_COMPONENT_ACTIVITY_PENDING_RESULTS,
(Bundle) mPendingResults.clone());
outState.putSerializable(KEY_COMPONENT_ACTIVITY_RANDOM_OBJECT, mRandom);
}
源码来自于ComponentActivity
和ActivityResultRegistry
,详细说明可查看源码。
新的Activity Result API提供一种简便方法来完成数据接收,例如打开相机、相册,权限的处理(是时候抛弃各种权限框架了RxPermission,EasyPermission ,xxx )等等,代码不仅简便,而且降低了代码的耦合,减少了样板代码。是时候跟startActivityForResult
方法说再见了,新的Activity Results API,小伙伴们快快用起来吧。