在Android開發(fā)中時常需要用到跳轉新頁面獲取結果回傳數(shù)據(jù),一直以來使用的方法就是startActivityForResult和onActivityResult兩個方法凡泣,但是startActivityForResult方法卻已經(jīng)被deprecation艰匙,官方推薦使用Activity Result API。
跳轉新頁面回傳數(shù)據(jù)之startActivityForResult
操作步驟一般為三步:
1、定義REQUEST_CODE棋电,同一個頁面有多個數(shù)據(jù)時,避免重復苇侵;
2赶盔、調用 startActivityForResult(Intent, REQUEST_CODE)進行新頁面的跳轉;
3榆浓、重寫 onActivityResult()于未,判斷requestCode和resultCode,獲取到回傳數(shù)據(jù)執(zhí)行后續(xù)邏輯陡鹃。
示例:
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方法就能拿到回傳數(shù)據(jù)片习。
跳轉新頁面回傳數(shù)據(jù)之Activity Result API
使用Activity Result API進行跳轉新頁面回傳數(shù)據(jù),操作分為兩步:
1蹬叭、通過registerForActivityResult方法定義一個函數(shù)處理回傳數(shù)據(jù);
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)回傳數(shù)據(jù),可以看到原頁面上移除了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的第一個入?yún)⑻男Γs定了一個輸入類型和一個結果的返回類型梦碗。
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()方法創(chuàng)建一個Intent用于startActivityForResult蓖救,parseResult()方法對onActivityResult的結果進行轉換洪规。
ActivityResultContracts里提供了常用的ActivityResultContract,可以直接拿來使用循捺。
比如我們最常用的跳轉新頁面回傳數(shù)據(jù):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方法中根據(jù)指定的resultCode和intent叉谜,創(chuàng)建了一個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中自己創(chuàng)建了一個MediaStore.ACTION_IMAGE_CAPTURE的Intent實例正罢。parseResult中根據(jù)指定的intent中獲取到Bitmap實例返回。
如果ActivityResultContracts里常用的這些無法滿足需求驻民,自然也可以自定義一波翻具,實現(xiàn)相應的createIntent方法和parseResult方法即可。
ActivityResultCallback
顧名思義回还,就是結果回調裆泳。
public interface ActivityResultCallback<O> {
/**
* Called when result is available
*/
void onActivityResult(@SuppressLint("UnknownNullness") O result);
}
registerForActivityResult在Activity中的實現(xiàn)
在Activity、Fragment中可以直接使用registerForActivityResult()柠硕,是因為ComponentActivity和Fragment都實現(xiàn)了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);
}
第一個參數(shù)使用"activity_rq#" + mNextLocalRequestCode.getAndIncrement()構造了一個key,mNextLocalRequestCode是一個AtomicInteger值蝗柔,使用這種方式就不需要額外定義REQUEST_CODE來進行區(qū)分了闻葵。
繼續(xù)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狀態(tài)之前調用
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);
}
//生成觀察者癣丧,當狀態(tài)為ON_START時執(zhí)行回調槽畔,為ON_STOP時移除與回調的關聯(lián),為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狀態(tài)之前調用鳞尔。通過傳入的key生成requestCode。通過key在集合中獲取LifecycleContainer實例早直,沒有則生成一個寥假。生成觀察者,當狀態(tài)為ON_START時執(zhí)行回調霞扬,為ON_STOP時移除與回調的關聯(lián)糕韧,為ON_DESTROY時取消注冊。為LifecycleContainer實例添加觀察者祥得。最終返回了一個ActivityResultLauncher實例兔沃。
onLaunch在Activity中的實現(xiàn)
在registerForActivityResult中最終返回了ActivityResultLauncher實例,而ActivityResultLauncher的launch方法里調用了ActivityResultRegistry.onLaunch方法级及,該方法是一個抽象方法乒疏,其實現(xiàn)在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);
}
}
}
};
首先區(qū)分是否需要啟動Activity怕吴,需要啟動Activity的情況下通過ActivityResultContract.createIntent初始化Intent實例,初始化Bundle县踢,最終也是通過ActivityCompat.startActivityForResult跳轉新頁面转绷。
總結
- ComponentActivity內部初始化了一個ActivityResultRegistry實例,并重寫了 onLaunch()硼啤。
- 調用registerForActivityResult() 最終調用ActivityResultRegistry.register()议经,在此添加了一個觀察者,當生命周期狀態(tài)切換到ON_START時谴返,執(zhí)行Contract.parseResult()生成輸出內容煞肾,并把結果作為參數(shù)傳入回調callback.onActivityResult()中。
- 調用ActivityResultLauncher.launch() 才會發(fā)起跳轉嗓袱,其中回調了onLaunch()方法籍救,在此調用了Contract.createIntent()創(chuàng)建一個和startActivityForResult()搭配使用的Intent實例。
- 跳轉目標Activity后返回此頁面渠抹,生命周期發(fā)生改變蝙昙,在觀察者中就會執(zhí)行回調的相關代碼。
后記
當一個頁面上需要一下子通過同一個ActivityResultLauncher打開多個頁面時梧却,發(fā)現(xiàn)在不同Android版本上表現(xiàn)不一樣奇颠。
每一個registerForActivityResult內部會生成一個RequestCode作為key,ActivityResultLauncher有一個觀察者隊列放航,ON_START會添加觀察者大刊,ON_STOP會移除觀察者。
當onActivityResult回調時三椿,執(zhí)行dispatchResult方法缺菌,從觀察者隊列中取出觀察者進行回傳,進行doDispatch方法搜锰。
關鍵在doDispatch方法中伴郁,有觀察者進行觀察者的onActivityResult回調,沒有觀察者蛋叼,使用key將數(shù)據(jù)存儲在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系統(tǒng)上,回到頁面時先ON_START會添加觀察者狈涮,再onActivityResult回調狐胎,沒有問題。
而在Android10系統(tǒng)上歌馍,回到頁面時先onActivityResult回調握巢,由于觀察者還未添加回隊列,所以使用key存儲bundle信息的松却,因此多次使用同一個registerForActivityResult時會丟失數(shù)據(jù)暴浦,使用key存儲bundle信息會覆蓋,只留下最后一次返回的bundle信息晓锻。