關(guān)于 Activity.startActivityForResult 的一些討論

注意以下基于 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ù)處理流程

Activity.finish.png

關(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é)果問題

    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)部原理、注意事項

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末莱褒,一起剝皮案震驚了整個濱河市击困,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌广凸,老刑警劉巖阅茶,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谅海,居然都是意外死亡脸哀,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門扭吁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撞蜂,“玉大人,你說我怎么就攤上這事侥袜◎蚬睿” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵枫吧,是天一觀的道長浦旱。 經(jīng)常有香客問我,道長九杂,這世上最難降的妖魔是什么颁湖? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮例隆,結(jié)果婚禮上甥捺,老公的妹妹穿的比我還像新娘。我一直安慰自己裳擎,他們只是感情好涎永,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鹿响,像睡著了一般羡微。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惶我,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天妈倔,我揣著相機與錄音,去河邊找鬼绸贡。 笑死盯蝴,一個胖子當著我的面吹牛毅哗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捧挺,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼虑绵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了闽烙?” 一聲冷哼從身側(cè)響起翅睛,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎黑竞,沒想到半個月后捕发,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡很魂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年扎酷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遏匆。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡法挨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拉岁,到底是詐尸還是另有隱情坷剧,我是刑警寧澤惰爬,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布喊暖,位于F島的核電站,受9級特大地震影響撕瞧,放射性物質(zhì)發(fā)生泄漏陵叽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一丛版、第九天 我趴在偏房一處隱蔽的房頂上張望巩掺。 院中可真熱鬧,春花似錦页畦、人聲如沸胖替。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽独令。三九已至,卻和暖如春好芭,著一層夾襖步出監(jiān)牢的瞬間燃箭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工舍败, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留招狸,地道東北人敬拓。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像裙戏,于是被迫代替她去往敵國和親乘凸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容