用ActivityResultContracts代替startA
activity之间的传递不一定是单向的,有时候会需要从跳转过去的activity往前回传数据(例如,您的应用可启动相机应用并接收拍摄的照片作为结果),过去我们一般采用底层 [startActivityForResult()
](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#startActivityForResult(android.content.Intent, int)) 和 [onActivityResult()
](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#onActivityResult(int, int, android.content.Intent)) API,现在谷歌推出了新的Activity Result API为我们解决这类问题。
即ActivityResultContracts
基础用法
-
在
ComponentActivity
或Fragment
中,使用 [registerForActivityResult()
](https://developer.android.com/reference/androidx/activity/result/ActivityResultCaller?hl=zh-cn#registerForActivityResult(androidx.activity.result.contract.ActivityResultContract, androidx.activity.result.ActivityResultCallback)) API,用于注册结果回调。此方法传入两个参数,ActivityResultContract
和ActivityResultCallback
,该方法返回ActivityResultLauncher
,供您用来启动另一个 activity。val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { res:ActivityResult ? -> if (result.resultCode == BACTIVITY_RESULT_CODE){ val data = result.data?.extras?.getString("extra_data") data?.let { toast(it) } } }
-
用返回的launcher启动另一个activity.
val intent = Intent(this, MainActivity::class.java) launcher.launch(intent)
注意:步骤一
registerForActivityResult
要在oncreate
之前调用(即作为当前activity的属性声明);步骤二
launch
要在oncreate
之后调用。 -
第二个activity还是照常使用setResult回传数据。
setResult(BACTIVITY_RESULT_CODE, Intent().putExtra("extra-data", "data"))
ActivityResultContract (协定)
第一步的registerForActivityResult方法传的第一个参数是ActivityResultContract,我们称之为协定。里面要约定两个东西:1. 你启动launcher时要传入的对象(本例中为intent);2. 返回到当前页面时带回来的对象(本例中为ActivityResult)。
第二个参数就是带着你约定好的对象的回调了。
示例中用的约定是:ActivityResultContracts.StartActivityForResult(),这是系统提供的,意思是传入Intent,返回ActivityResult。
除此之外,系统还提供了很多,如ActivityResultContracts.GetContent(),传入string,返回UrI,一般是用来获取系统资源用的,像传入"image/*",然后返回图片URI{content://} 之类的。
val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
// Handle the returned Uri
}
override fun onCreate(savedInstanceState: Bundle?) {
// ...
val selectButton = findViewById<Button>(R.id.select_button)
selectButton.setOnClickListener {
// Pass in the mime type you'd like to allow the user to select
// as the input
getContent.launch("image/*")
}
}
我们用Input<I>表示传入的约定,Output<O>表示回传回来的约定。协定就可以抽象成:
public abstract class ActivityResultContract<I, O> {
/**
* 创建Intent,不管你约定的输入是啥,要跳转最后还是用的Intent跳,所以在这得自己创建Intent
**/
public abstract Intent createIntent(Context context,I input);
/**
* 解析回调的结果
**/
public abstract O parseResult(int resultCode, @Nullable Intent intent);
/**
* 可选
**/
public SynchronousResult<O> getSynchronousResult(Context context,I input) {
return null;
}
}
先来看示例中用的这个协定 - StartActivityForResult
官方提供的协定
public static final class StartActivityForResult extends ActivityResultContract<Intent, ActivityResult> {
@NonNull
@Override
public Intent createIntent(@NonNull Context context, @NonNull Intent input) {
return input;
}
@NonNull
@Override
public ActivityResult parseResult(
int resultCode, @Nullable Intent intent) {
return new ActivityResult(resultCode, intent);
}
}
很简单,传入的就是intent,返回的就是resultCode + intent的封装类;
再看下刚刚提到的传图片的那个:
public static class GetContent extends ActivityResultContract<String, Uri> {
//CallSuper是提醒继承该类的话应该重写该方法,上面那个是final不能重写,所以没有
@CallSuper
@NonNull
@Override
public Intent createIntent(@NonNull Context context, @NonNull String input) {
return new Intent(Intent.ACTION_GET_CONTENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType(input);
}
@Nullable
@Override
public final SynchronousResult<Uri> getSynchronousResult(@NonNull Context context,
@NonNull String input) {
return null;
}
@Nullable
@Override
public final Uri parseResult(int resultCode, @Nullable Intent intent) {
if (intent == null || resultCode != Activity.RESULT_OK) return null;
return intent.getData();
}
}
传入特定的Intent,返回的结果先解析下再返回回去。
理解了这个协定后,我们也可以自己约定协定。
创建自定义协定
比如说一个时间选择的Activity,它的返回到上一页面的时候要携带当前界面选择的时间。因为进入到该界面的时候Intent是固定的,所以Input可以传入void,意思是不用传。
class TimeResultContract : ActivityResultContract<Unit,String>(){
override fun createIntent(context: Context, input: Unit?): Intent {
return Intent(context,TimeSelectActivity::class.java)
}
override fun parseResult(resultCode: Int, intent: Intent?): String {
if (resultCode != RESULT_OK){
return "error"
}
return intent?.getStringExtra(TimeSelectActivity_EXTRA_DATE).toString()
}
}
用的时候只需要在需要跳转的activity中声明即可。
val timeLauncher = registerForActivityResult(TimeResultContract()){ time ->
toast(time)
viewBinding.tvTime.text = time
}
相比以前需要把requestCode判断的逻辑写到各个Activity中的写法,这样封装起来看起来是不是干净多了?
如果你还想把registerForActivityResult这个方法也提到activity外面去,也是可以做到的。
进阶
在单独的类中接收 activity 结果
之所以能在ComponentActivity
和 Fragment
类中直接调用registerForActivityResult()
方法是因为它们实现了ActivityResultCaller
接口。如果你想在未实现ActivityResultCaller
接口的类中获取launcher,那么就需要用到ActivityResultRegistry
类了。
例如,您可能需要实现一个 LifecycleObserver
,用于处理协定的注册和启动器的启动:
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()
}
}
}
源码解析
从上面的用法可以看出,实现这个功能的核心其实是传进来的registry:ActivityResultRegistry。 而在ComponentActivity
和Fragment
中都维护了有一个registry,以activity为例:
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;
// Immediate result path
final ActivityResultContract.SynchronousResult<O> synchronousResult =
contract.getSynchronousResult(activity, input);
if (synchronousResult != null) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
dispatchResult(requestCode, synchronousResult.getValue());
}
});
return;
}
// Start activity path
Intent intent = contract.createIntent(activity, input);
Bundle optionsBundle = null;
// If there are any extras, we should defensively set the classLoader
if (intent.getExtras() != null && intent.getExtras().getClassLoader() == null) {
intent.setExtrasClassLoader(activity.getClassLoader());
}
if (intent.hasExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE)) {
optionsBundle = intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE);
intent.removeExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE);
} else if (options != null) {
optionsBundle = options.toBundle();
}
if (ACTION_REQUEST_PERMISSIONS.equals(intent.getAction())) {
// requestPermissions path
String[] permissions = intent.getStringArrayExtra(EXTRA_PERMISSIONS);
if (permissions == null) {
permissions = new String[0];
}
ActivityCompat.requestPermissions(activity, permissions, requestCode);
} else if (ACTION_INTENT_SENDER_REQUEST.equals(intent.getAction())) {
IntentSenderRequest request =
intent.getParcelableExtra(EXTRA_INTENT_SENDER_REQUEST);
try {
// startIntentSenderForResult path
ActivityCompat.startIntentSenderForResult(activity, request.getIntentSender(),
requestCode, request.getFillInIntent(), request.getFlagsMask(),
request.getFlagsValues(), 0, optionsBundle);
} catch (final IntentSender.SendIntentException e) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
dispatchResult(requestCode, RESULT_CANCELED,
new Intent().setAction(ACTION_INTENT_SENDER_REQUEST)
.putExtra(EXTRA_SEND_INTENT_EXCEPTION, e));
}
});
}
} else {
// startActivityForResult path
ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle);
}
}
};
然后实际调用的是registry.register()方法(这是个抽象类,该方法写在抽象类中)
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);
LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key);
if (lifecycleContainer == null) {
lifecycleContainer = new LifecycleContainer(lifecycle);
}
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);
}
}
};
lifecycleContainer.addObserver(observer);
mKeyToLifecycleContainers.put(key, lifecycleContainer);
return new ActivityResultLauncher<I>() {
@Override
public void launch(I input, @Nullable ActivityOptionsCompat options) {
mLaunchedKeys.add(key);
onLaunch(requestCode, contract, input, options);
}
@Override
public void unregister() {
ActivityResultRegistry.this.unregister(key);
}
@NonNull
@Override
public ActivityResultContract<I, ?> getContract() {
return contract;
}
};
}
可以看到这里对生命周期所在状态进行了很多判断,可以防止声明周期外的内存泄漏的问题。
调用launch方法的时候实际走的是onLaunch(),也就是第一段代码里面的内容,最终调用的还是startActivityForResult方法,所以这个新的用法还是基于startActivityForResult的,但是却很好的将回传的数据的判断相关功能封装到了协定类中,以后就算有多个回传数据,用起来也会很清晰,代码就不会臃肿到onActivityResult中了。
示例-获取权限
request_permission.setOnClickListener {
requestPermission.launch(permission.BLUETOOTH)
}
request_multiple_permission.setOnClickListener {
requestMultiplePermissions.launch(
arrayOf(
permission.BLUETOOTH,
permission.NFC,
permission.ACCESS_FINE_LOCATION
)
)
}
// Request permission contract
private val requestPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
// Do something if permission granted
if (isGranted) toast("Permission is granted")
else toast("Permission is denied")
}
// Request multiple permissions contract
private val requestMultiplePermissions =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions : Map<String, Boolean> ->
// Do something if some permissions granted or denied
permissions.entries.forEach {
// Do checking here
}
}
相较原来的获取权限的写法清晰多了。