注意以下基于 7.0 源碼討論
1. startActivityForResult 使用場景是什么? requestCode、 resultCode 含義是什么坪哄?
1.1 使用場景
用戶開始新的活動,并且希望在新活動結(jié)束時收到某些信息。比如選擇照片血久、選擇聯(lián)系人、選擇收貨地址帮非、進行某塊數(shù)據(jù)編輯工作等氧吐。
1.2 requestCode
- 解決的是「區(qū)分多個異步任務(wù)」的問題讹蘑。與其他異步 API 的設(shè)計類似,如果沒有這個信息筑舅,那么 Activity 在收到響應(yīng)時會進入混亂的狀態(tài)座慰。比如他不知道自己得到的是選擇照片還是選擇聯(lián)系人的結(jié)果。
- 該信息會發(fā)送到 AMS 那邊的 ActivityRecord.requestCode 變量進行記錄翠拣,Client 端新 Activity 并不知道這個信息版仔。
- 為什么 requestCode < 0 時收不到結(jié)果?
- ActivityStarter 收到 startActivityLocked 時误墓,寫入 ActivityRecord.resultTo 變量為空蛮粮,源碼
- 在 ActivityStack 收到 finishActivityResultsLocked 時,讀取 ActivityRecord.resultTo 變量為空优烧,結(jié)果數(shù)據(jù)不會添加到源 ActivityRecord.results 變量蝉揍,源碼
- 在 ActivityStack 收到 resumeTopActivityInnerLocked 時链峭,讀取 ActivityRecord.results 數(shù)組為空畦娄,不會分發(fā)結(jié)果數(shù)據(jù),這樣源 Activity 也就沒有結(jié)果回調(diào)弊仪,源碼
1.3 resultCode
- 異步調(diào)用結(jié)果碼熙卡,告訴調(diào)用者成功/失敗/其它信息
- 該信息由被調(diào)用 Activity / framework 寫入,并經(jīng)過 AMS 傳遞給源 Activity
2. A 啟動 B 励饵,B 中何時執(zhí)行 setResult ? setResult 是否可以位于 finish 之后驳癌?
2.1 setResult 在 finish 之前執(zhí)行,只是把數(shù)據(jù)記錄在 Activity.mResultCode 和 Activity.mResultData 變量中
- 最早 Activity 構(gòu)造器階段
- 最晚 Activity.finalize 內(nèi)存回收階段
// Home 鍵 + 不保留后臺 Activity 可觸發(fā) onDestroy
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy() called");
new ReqGC().start();
}
@Override
protected void finalize() throws Throwable {
Log.d(TAG, "finalize() called");
finish();
super.finalize();
}
@Override
public void finish() {
Log.d(TAG, "finish() called");
setResult(RESULT_OK, new Intent().putExtra("key", "resultData"));
super.finish();
}
// 不泄漏 Activity 對象
static class ReqGC {
public void start() {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
System.gc();
handler.postDelayed(this, 10);
}
}, 10);
}
}
2.2 否
- 如果位于 finish 之后執(zhí)行役听,那信息無法傳遞給源 Activity
- 從代碼可以看出 setResult 和 finish 類似生產(chǎn)者/消費者模型颓鲜,setResult 負責寫入數(shù)據(jù),finish 負責讀取數(shù)據(jù)
2.3 線程安全問題
- Activity.mResultCode 和 Activity.mResultData 變量由 Activity 對象的鎖進行保護
- 支持后臺線程和 UI 線程分別進行 setResult 和 finish
- 但是為什么需要加鎖保護這兩個信息典予?需要「解決什么問題」甜滨?
2.4 API 設(shè)計/數(shù)據(jù)組裝問題
- 底層 AMS 提供的接口的參數(shù)是 setResult 和 finish 的參數(shù)的組合形式,但是為什么 Activity 為什么把一個接口拆分成兩個接口給開發(fā)者使用瘤袖?
- 使用方便衣摩。很多情況下上層調(diào)用者只關(guān)心 finish ,不需要理解太多的信息捂敌。
3. Activity.finish 數(shù)據(jù)處理流程
關(guān)鍵節(jié)點:
- Client 端通過 AMP 把數(shù)據(jù)發(fā)送給 Server 端 AMS
- AMS 把數(shù)據(jù)包裝成 ActivityResult 并保存在源 ActivityRecord 的 results 變量中
- AMS 向 Client 端發(fā)送 pause 信息讓棧頂 Activity 進入 paused 狀態(tài)艾扮,并等待 Client 端回復或超時
- AMS 接收 Client 端已 paused 信息,恢復下一個獲取焦點的 Activity 占婉,讀取之前保存在 ActivityRecord.results 變量的數(shù)據(jù)派發(fā)給 Client 端對應(yīng)的 Activity
- Client 端數(shù)據(jù)經(jīng)過 ApplicationThread 對象泡嘴、ActivityThread 對象的分發(fā)最后到達 Activity
4. startActivityForResult 和 singleTask 導致源 Activity 收不到正確結(jié)果問題
- 基本原則:源 Activity 和目標 Activity 無法在跨 Task 情況下通過 onActivityResult 傳遞數(shù)據(jù)
ActivityStarter.sendNewTaskResultRequestIfNeeded
private void sendNewTaskResultRequestIfNeeded() {
if (mStartActivity.resultTo != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0
&& mStartActivity.resultTo.task.stack != null) {
// For whatever reason this activity is being launched into a new task...
// yet the caller has requested a result back. Well, that is pretty messed up,
// so instead immediately send back a cancel and let the new task continue launched
// as normal without a dependency on its originator.
Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
mStartActivity.resultTo.task.stack.sendActivityResultLocked(-1, mStartActivity.resultTo,
mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED, null);
mStartActivity.resultTo = null;
}
}
- Android 5.0 以上 AMS 在處理 requestCode >= 0 情況且 launchMode 為 singleTask 和 singleInstance 信息時「不會」創(chuàng)建新的 Task,因此可以收到正衬婕茫回調(diào)
ActivityStarter.startActivityUnchecked
// Should this be considered a new task?
// 因為新啟動 ActivityRecord.resultTo 變量不為空磕诊,所以不創(chuàng)建新 Task
if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
&& (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
newTask = true;
setTaskFromReuseOrCreateNewTask(taskToAffiliate);
//...
} else if (mSourceRecord != null) {
// 復用源 ActivityRecord 所屬的 Task
final int result = setTaskFromSourceRecord();
//...
} else if (mInTask != null) {
...
} else {
...
}
- Android 4.4.4 以下 AMS 在處理 requestCode >= 0 情況且 launchMode 為 singleTask 和 singleInstance 信息時「會」創(chuàng)建新的 Task填物,因此在 startActivity 之后立即收到取消的回調(diào)
if (sourceRecord == null) {
// This activity is not being started from another... in this
// case we -always- start a new task.
if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
"Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
}
} else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
// The original activity who is starting us is running as a single
// instance... this new activity it is starting must go on its
// own task.
// 源 Activity 獨占一個 Task
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
} else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
// The activity being started is a single instance... it always
// gets launched into its own task.
// 新 Activity 需要在新 Task
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
}
if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
// For whatever reason this activity is being launched into a new
// task... yet the caller has requested a result back. Well, that
// is pretty messed up, so instead immediately send back a cancel
// and let the new task continue launched as normal without a
// dependency on its originator.
Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
r.resultTo.task.stack.sendActivityResultLocked(-1,
r.resultTo, r.resultWho, r.requestCode,
Activity.RESULT_CANCELED, null);
// 這里清空需要接收結(jié)果的 Activity 的引用
r.resultTo = null;
}
// Should this be considered a new task?
// 因為 r.resultTo 已經(jīng)被清空,所以會創(chuàng)建新的 Task
if (r.resultTo == null && !addingToTask
&& (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
...
r.setTask(targetStack.createTaskRecord(getNextTaskId(),
newTaskInfo != null ? newTaskInfo : r.info,
newTaskIntent != null ? newTaskIntent : intent,
true), null, true);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
r.task);
newTask = true;
...
} else if (sourceRecord != null) {
...
- 通過 dumpsys activity activities 命令查看 AMS 狀態(tài)霎终,驗證兩個 Activity 是否屬于不同的 Task
- 通過 debug AMS 源碼的方式驗證以上假設(shè)
總結(jié)
以上就是該 API 的使用場景滞磺、內(nèi)部原理、注意事項