在Android开发中时常需要用到跳转新页面获取结果回传数据,一直以来使用的方法就是startActivityForResult和onActivityResult两个方法,但是startActivityForResult方法却已经被deprecation,官方推荐使用Activity Result API。
跳转新页面回传数据之startActivityForResult
操作步骤一般为三步:
1、定义REQUEST_CODE,同一个页面有多个数据时,避免重复;
2、调用 startActivityForResult(Intent, REQUEST_CODE)进行新页面的跳转;
3、重写 onActivityResult(),判断requestCode和resultCode,获取到回传数据执行后续逻辑。
示例:
class MainActivity : AppCompatActivity() {
companion object {
private const val REQUEST_CODE_1 = 20
private const val REQUEST_CODE_2 = 21
}
private val binding: ActivityMainBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.hide()
setContentView(binding.root)
binding.click1.setOnClickListener {
startActivityForResult(Intent(this, SecondActivity::class.java), REQUEST_CODE_1)
}
binding.click2.setOnClickListener {
//ARouter里仍然是使用startActivityForResult
ARouter.getInstance()
.build(ARouterPath.SecondActivity)
.navigation(this, REQUEST_CODE_2)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
when (requestCode) {
REQUEST_CODE_1 -> {
Toast.makeText(
this,
"startActivityForResult回调:${data?.getStringExtra("data")}",
Toast.LENGTH_SHORT
)
.show()
}
REQUEST_CODE_2 -> {
Toast.makeText(
this,
"ARouter回调:${data?.getStringExtra("data")}",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
即使是大家常用的使用ARouter管理页面路由的方式,通过navigation跳转,也是塞入了REQUEST_CODE,其内部也是使用startActivityForResult方法跳转。
private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
if (requestCode >= 0) { // Need start for result
if (currentContext instanceof Activity) {
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
logger.warning(Consts.TAG, "Must use [navigation(activity, ...)] to support [startActivityForResult]");
}
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null != callback) { // Navigation over.
callback.onArrival(postcard);
}
}
在新页面通过一个简单的setResult(int resultCode, Intent data),在关闭新页面回到原页面时,在原页面的onActivityResult方法就能拿到回传数据。
跳转新页面回传数据之Activity Result API
使用Activity Result API进行跳转新页面回传数据,操作分为两步:
1、通过registerForActivityResult方法定义一个函数处理回传数据;
2、通过launch()进行新页面的跳转。
示例:
//使用registerForActivityResult
val secondLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
it.data?.getStringExtra("data")?.let {
Toast.makeText(
this,
"registerForActivityResult回调:${it}",
Toast.LENGTH_SHORT
)
.show()
}
}
}
binding.click3.setOnClickListener {
secondLauncher.launch(Intent(this, SecondActivity::class.java))
}
新页面仍然是通过setResult(int resultCode, Intent data)回传数据,可以看到原页面上移除了onActivityResult()的重写,并且少写了一个REQUEST_CODE。
探查Activity Result API原理
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull ActivityResultContract<I, O> contract,
@NonNull ActivityResultCallback<O> callback) {
return registerForActivityResult(contract, mActivityResultRegistry, callback);
}
Activity Results API由三个要素组成,Launcher、Contract、Callback
ActivityResultLauncher
public abstract class ActivityResultLauncher<I> {
/**
* Executes an {@link ActivityResultContract}.
*
* <p>This method throws {@link android.content.ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
* @param input the input required to execute an {@link ActivityResultContract}.
*
* @throws android.content.ActivityNotFoundException
*/
public void launch(@SuppressLint("UnknownNullness") I input) {
launch(input, null);
}
/**
* Executes an {@link ActivityResultContract}.
*
* <p>This method throws {@link android.content.ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
*
* @param input the input required to execute an {@link ActivityResultContract}.
* @param options Additional options for how the Activity should be started.
*
* @throws android.content.ActivityNotFoundException
*/
public abstract void launch(@SuppressLint("UnknownNullness") I input,
@Nullable ActivityOptionsCompat options);
/**
* Unregisters this launcher, releasing the underlying result callback, and any references
* captured within it.
*
* You should call this if the registry may live longer than the callback registered for this
* launcher.
*/
@MainThread
public abstract void unregister();
/**
* Get the {@link ActivityResultContract} that was used to create this launcher.
*
* @return the contract that was used to create this launcher
*/
@NonNull
public abstract ActivityResultContract<I, ?> getContract();
}
ActivityResultLauncher是registerForActivityResult的返回值,用于连接启动对象和返回对象的。
ActivityResultContract
ActivityResultContract是registerForActivityResult的第一个入参,约定了一个输入类型和一个结果的返回类型。
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);
/**
* An optional method you can implement that can be used to potentially provide a result in
* lieu of starting an activity.
*
* @return the result wrapped in a {@link SynchronousResult} or {@code null} if the call
* should proceed to start an activity.
*/
public @Nullable SynchronousResult<O> getSynchronousResult(
@NonNull Context context,
@SuppressLint("UnknownNullness") I input) {
return null;
}
/**
* The wrapper for a result provided in {@link #getSynchronousResult}
*
* @param <T> type of the result
*/
public static final class SynchronousResult<T> {
private final @SuppressLint("UnknownNullness") T mValue;
/**
* Create a new result wrapper
*
* @param value the result value
*/
public SynchronousResult(@SuppressLint("UnknownNullness") T value) {
this.mValue = value;
}
/**
* @return the result value
*/
public @SuppressLint("UnknownNullness") T getValue() {
return mValue;
}
}
}
ActivityResultContract内主要就两个方法,createIntent()方法创建一个Intent用于startActivityForResult,parseResult()方法对onActivityResult的结果进行转换。
ActivityResultContracts里提供了常用的ActivityResultContract,可以直接拿来使用。
image.png
比如我们最常用的跳转新页面回传数据:ActivityResultContracts.StartActivityForResult()
public static final class StartActivityForResult
extends ActivityResultContract<Intent, ActivityResult> {
/**
* Key for the extra containing a {@link android.os.Bundle} generated from
* {@link androidx.core.app.ActivityOptionsCompat#toBundle()} or
* {@link android.app.ActivityOptions#toBundle()}.
*
* This will override any {@link ActivityOptionsCompat} passed to
* {@link androidx.activity.result.ActivityResultLauncher#launch(Object,
ActivityOptionsCompat)}
*/
public static final String EXTRA_ACTIVITY_OPTIONS_BUNDLE = "androidx.activity.result"
+ ".contract.extra.ACTIVITY_OPTIONS_BUNDLE";
@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);
}
}
继承ActivityResultContract,约定输入类型为Intent,结果返回类型为ActivityResult。在createIntent方法中因为输入类型就是Intent,所以没做处理,直接返回。parseResult方法中根据指定的resultCode和intent,创建了一个ActivityResult实例返回。
再看一个ActivityResultContracts.TakePicturePreview()
public static class TakePicturePreview extends ActivityResultContract<Void, Bitmap> {
@CallSuper
@NonNull
@Override
public Intent createIntent(@NonNull Context context, @Nullable Void input) {
return new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
}
@Nullable
@Override
public final SynchronousResult<Bitmap> getSynchronousResult(@NonNull Context context,
@Nullable Void input) {
return null;
}
@Nullable
@Override
public final Bitmap parseResult(int resultCode, @Nullable Intent intent) {
if (intent == null || resultCode != Activity.RESULT_OK) return null;
return intent.getParcelableExtra("data");
}
}
输入类型为Void,因为在createIntent中自己创建了一个MediaStore.ACTION_IMAGE_CAPTURE的Intent实例。parseResult中根据指定的intent中获取到Bitmap实例返回。
如果ActivityResultContracts里常用的这些无法满足需求,自然也可以自定义一波,实现相应的createIntent方法和parseResult方法即可。
ActivityResultCallback
顾名思义,就是结果回调。
public interface ActivityResultCallback<O> {
/**
* Called when result is available
*/
void onActivityResult(@SuppressLint("UnknownNullness") O result);
}
registerForActivityResult在Activity中的实现
在Activity、Fragment中可以直接使用registerForActivityResult(),是因为ComponentActivity和Fragment都实现了ActivityResultCaller接口。
@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);
}
第一个参数使用"activity_rq#" + mNextLocalRequestCode.getAndIncrement()构造了一个key,mNextLocalRequestCode是一个AtomicInteger值,使用这种方式就不需要额外定义REQUEST_CODE来进行区分了。
继续ActivityResultRegistry的register方法:
@NonNull
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 lifecycle = lifecycleOwner.getLifecycle();
//register要在当前生命周期组件处于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.");
}
//通过传入的key生成requestCode
final int requestCode = registerKey(key);
//通过key在集合中获取LifecycleContainer实例,没有则生成一个
LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key);
if (lifecycleContainer == null) {
lifecycleContainer = new LifecycleContainer(lifecycle);
}
//生成观察者,当状态为ON_START时执行回调,为ON_STOP时移除与回调的关联,为ON_DESTROY时取消注册
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实例添加观察者
lifecycleContainer.addObserver(observer);
mKeyToLifecycleContainers.put(key, lifecycleContainer);
//返回了一个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);
}
@Override
public void unregister() {
ActivityResultRegistry.this.unregister(key);
}
@NonNull
@Override
public ActivityResultContract<I, ?> getContract() {
return contract;
}
};
}
在register方法中,首先获取到当前生命周期组件的lifecycle。然后register要在当前生命周期组件处于STARTED状态之前调用。通过传入的key生成requestCode。通过key在集合中获取LifecycleContainer实例,没有则生成一个。生成观察者,当状态为ON_START时执行回调,为ON_STOP时移除与回调的关联,为ON_DESTROY时取消注册。为LifecycleContainer实例添加观察者。最终返回了一个ActivityResultLauncher实例。
onLaunch在Activity中的实现
在registerForActivityResult中最终返回了ActivityResultLauncher实例,而ActivityResultLauncher的launch方法里调用了ActivityResultRegistry.onLaunch方法,该方法是一个抽象方法,其实现在ComponentActivity中。
this.mActivityResultRegistry = new ActivityResultRegistry() {
public <I, O> void onLaunch(final int requestCode, @NonNull ActivityResultContract<I, O> contract, I input, @Nullable ActivityOptionsCompat options) {
ComponentActivity activity = ComponentActivity.this;
final SynchronousResult<O> synchronousResult = contract.getSynchronousResult(activity, input);
if (synchronousResult != null) {
//不需要启动Activity就能知道结果的场景处理
(new Handler(Looper.getMainLooper())).post(new Runnable() {
public void run() {
dispatchResult(requestCode, synchronousResult.getValue());
}
});
} else {
//需要启动Activity才能知道结果的场景处理
//通过ActivityResultContract.createIntent初始化Intent实例
Intent intent = contract.createIntent(activity, input);
//初始化Bundle
Bundle optionsBundle = null;
if (intent.getExtras() != null && intent.getExtras().getClassLoader() == null) {
intent.setExtrasClassLoader(activity.getClassLoader());
}
if (intent.hasExtra("androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE")) {
optionsBundle = intent.getBundleExtra("androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE");
intent.removeExtra("androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE");
} else if (options != null) {
optionsBundle = options.toBundle();
}
//如果是权限申请,请求权限
if ("androidx.activity.result.contract.action.REQUEST_PERMISSIONS".equals(intent.getAction())) {
String[] permissions = intent.getStringArrayExtra("androidx.activity.result.contract.extra.PERMISSIONS");
if (permissions == null) {
permissions = new String[0];
}
ActivityCompat.requestPermissions(activity, permissions, requestCode);
} else if ("androidx.activity.result.contract.action.INTENT_SENDER_REQUEST".equals(intent.getAction())) {
IntentSenderRequest request = (IntentSenderRequest)intent.getParcelableExtra("androidx.activity.result.contract.extra.INTENT_SENDER_REQUEST");
try {
ActivityCompat.startIntentSenderForResult(activity, request.getIntentSender(), requestCode, request.getFillInIntent(), request.getFlagsMask(), request.getFlagsValues(), 0, optionsBundle);
} catch (final SendIntentException var11) {
(new Handler(Looper.getMainLooper())).post(new Runnable() {
public void run() {
dispatchResult(requestCode, 0, (new Intent()).setAction("androidx.activity.result.contract.action.INTENT_SENDER_REQUEST").putExtra("androidx.activity.result.contract.extra.SEND_INTENT_EXCEPTION", var11));
}
});
}
} else {
ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle);
}
}
}
};
首先区分是否需要启动Activity,需要启动Activity的情况下通过ActivityResultContract.createIntent初始化Intent实例,初始化Bundle,最终也是通过ActivityCompat.startActivityForResult跳转新页面。
总结
- ComponentActivity内部初始化了一个ActivityResultRegistry实例,并重写了 onLaunch()。
- 调用registerForActivityResult() 最终调用ActivityResultRegistry.register(),在此添加了一个观察者,当生命周期状态切换到ON_START时,执行Contract.parseResult()生成输出内容,并把结果作为参数传入回调callback.onActivityResult()中。
- 调用ActivityResultLauncher.launch() 才会发起跳转,其中回调了onLaunch()方法,在此调用了Contract.createIntent()创建一个和startActivityForResult()搭配使用的Intent实例。
- 跳转目标Activity后返回此页面,生命周期发生改变,在观察者中就会执行回调的相关代码。
后记
当一个页面上需要一下子通过同一个ActivityResultLauncher打开多个页面时,发现在不同Android版本上表现不一样。
每一个registerForActivityResult内部会生成一个RequestCode作为key,ActivityResultLauncher有一个观察者队列,ON_START会添加观察者,ON_STOP会移除观察者。
当onActivityResult回调时,执行dispatchResult方法,从观察者队列中取出观察者进行回传,进行doDispatch方法。
关键在doDispatch方法中,有观察者进行观察者的onActivityResult回调,没有观察者,使用key将数据存储在bundle信息中。
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
@MainThread
public final boolean dispatchResult(int requestCode, int resultCode, @Nullable Intent data) {
String key = mRcToKey.get(requestCode);
if (key == null) {
return false;
}
doDispatch(key, resultCode, data, mKeyToCallback.get(key));
return true;
}
private <O> void doDispatch(String key, int resultCode, @Nullable Intent data,
@Nullable CallbackAndContract<O> callbackAndContract) {
if (callbackAndContract != null && callbackAndContract.mCallback != null
&& mLaunchedKeys.contains(key)) {
ActivityResultCallback<O> callback = callbackAndContract.mCallback;
ActivityResultContract<?, O> contract = callbackAndContract.mContract;
callback.onActivityResult(contract.parseResult(resultCode, data));
mLaunchedKeys.remove(key);
} else {
// Remove any parsed pending result
mParsedPendingResults.remove(key);
// And add these pending results in their place
mPendingResults.putParcelable(key, new ActivityResult(resultCode, data));
}
}
在Android13系统上,回到页面时先ON_START会添加观察者,再onActivityResult回调,没有问题。
而在Android10系统上,回到页面时先onActivityResult回调,由于观察者还未添加回队列,所以使用key存储bundle信息的,因此多次使用同一个registerForActivityResult时会丢失数据,使用key存储bundle信息会覆盖,只留下最后一次返回的bundle信息。
网友评论