android系統代碼看不下去?讓咱們從debug 開始

開頭

ActivityA startActivityForResult啓動了ActivityB,ActivityB在startActivity啓動了ActivityC,這個時候finish掉activityB,ActivityA的onActivityResult會響應嗎?

這兩天不知道爲啥忽然想到了這個問題,正常思惟,可能理所應當的以爲,ActivityA startActivityForResult啓動了ActivityB, 那麼activityB finish的時候,就會把結果傳回ActivityA。
因此開篇的問題,我想都沒想就以爲,ActivityA的onActivityResult會響應,過了兩天,又想到了這個問題,好吧,仍是 用代碼來實踐一下,結果ActivityA並無響應~~

好吧那咱們就看一下源碼,爲何沒有響應~
ActivityTaskManager.getService().finishActivity(mToken, resultCode, resultData, finishTask)
activity調用finish後,會交給ActivityTaskManager的finishActivity方法,而且參數中帶有resultCode, resultData 而後瀏覽了ActivityTaskManager裏面帶result的相關方法、參數,跟着finishActivity方法,一個一個找過去,不少方法中又代碼不少, 可能看着看着就斷了,而後回頭找參數,哪裏作了保存,再全局收索看哪裏作了調用,而後看着收索框裏一堆的結果,腦子裏就閃過了 我是誰、我在哪、我要幹嗎來的。若是看岔了,也是越看越迷,越看越偏,最後腦子裏又蕩起了,我是誰、我在哪、我要幹嗎來的, 不知道大家有沒有這種感覺,反正我常常有。android



想一想咱們遇到bug是怎麼一步一步追溯,怎麼解決的?
debug,打斷點,而後跟着調用棧一步一步找問題,那麼咱們能不能對系統代碼打斷點呢?

中間

系統代碼debug

下載系統 源碼 mac視角 ,首先準備好至少100g空間
git

  1. 確保主目錄下有一個 bin/ 目錄,而且該目錄包含在路徑中:
    mkdir ~/bin
    PATH=~/bin:$PATH
    ps: 源碼不支持在大小寫敏感的文件系統中比編譯,參考第8步,這個也能夠放在第一步,隨後全部命令在/Volumes/android下執行,可是速度很慢api

  2. 下載 Repo 工具,並確保它可執行:
    curl storage.googleapis.com/git-repo-do… > ~/bin/repo
    chmod a+x ~/bin/repobash

  3. 建立一個空目錄來存放您的工做文件。若是您使用的是 MacOS,必須在區分大小寫的文件系統中建立該目錄。爲其指定一個您喜歡的任意名稱:
    mkdir WORKING_DIRECTORY
    cd WORKING_DIRECTORYapp

  4. 使用您的真實姓名和電子郵件地址配置 Git。要使用 Gerrit 代碼審覈工具,您須要一個與已註冊的 Google 賬號關聯的電子郵件地址。 確保這是您能夠接收郵件的有效地址。您在此處提供的姓名將顯示在您提交的代碼的提供方信息中。
    git config --global user.name "Your Name"
    git config --global user.email "you@example.com"curl

  5. 運行 repo init 以獲取最新版本的 Repo 及其最近的全部錯誤更正內容。您必須爲清單指定一個網址,該網址用於指定 Android 源代碼中包含的各個代碼庫將位於工做目錄中的什麼位置。
    repo init -u android.googlesource.com/platform/ma…ide

  6. 要對「master」之外的分支進行校驗,請使用 -b 來指定相應分支。要查看分支列表,請參閱源代碼標記和版本。
    repo init -u android.googlesource.com/platform/ma… -b android-10.0.0_r1函數

  7. 要將 Android 源代碼樹從默認清單中指定的代碼庫下載到工做目錄,請運行如下命令:
    repo sync工具


ps: repo sync 耗了我,七、8個小時~~ 同步完了內存吃緊,能夠把.repo文件夾刪除ui

  1. source.android.com/setup/build… 在默認安裝過程當中,macOS 會在一個保留大小寫但不區分大小寫的文件系統中運行。 Git 不支持這種類型的文件系統,並且此類文件系統會致使某些 Git 命令(如 git status)的行爲出現異常。所以,咱們建議您始終在區分大小寫的文件系統中處理 AOSP 源文件。使用下文中介紹的磁盤映像能夠很是輕鬆地作到這一點。有了適當的文件系統,在新型 macOS 環境中編譯 master 分支就會變得很是簡單。要編譯較早版本的分支,則須要一些額外的工具和 SDK。
    hdiutil create -type SPARSE -fs 'Case-sensitive Journaled HFS+' -size 80g ~/android.dmg
    hdiutil attach ~/android.dmg -mountpoint /Volumes/android;

  2. 移動源碼到上一步建立的磁盤映像
    mv ~/bin/WORKING_DIRECTORY /Volumes/android

  3. 編譯idegen模塊: cd 到/Volumes/android/WORKING_DIRECTORY
    source build/ensetup.sh
    make idegen

  4. 執行./development/tools/idegen/idegen.sh 在源碼根目錄會生成android.iml 和 android.ipr 兩個文件

  5. 用AndroidStudio找到打開android.ipr 下載好對應版本的Android源碼,打開對應版本的虛擬機,就能夠選擇system_process執行debug了

ps:原本準備編譯系統的,可是第一次跑了3個小時,結果報錯~~,原本想修改,可是驗證代價是在太大,因爲不影響調試,最後放棄了。還有最好不要用真機,屢次debug到重啓不能~~

最後

回到開篇的問題

先簡單的瞭解一下ActivityRecord,其對應一個Activity,保存了一個Activity的全部信息,其中的變量resultTo,指向啓動它的ActivityRecord

startActivityForResult過程

  1. Activity執行startActivityForResult,調用到mInstrumentation.execStartActivity傳入token參數, 這個token也就是Activity對應服務端的ActivityRecord.Token,能夠拿到ActivityRecord

  2. mInstrumentation.execStartActivity內執行ActivityTaskManager.getService().startActivity(... ActivityRecord resultTo ,int requestCode ...), 參數中resultTo也就是從上一步中的token獲取來的,而後一路走

    到ActivityStarter的startActivity建立了一個ActivityRecord,傳入resultTo,這個ActivityRecord會被傳入將被打開的activity。

  3. TaskRecord、ActivityStack建立關聯等邏輯後,ActivityStackSupervisor執行realStartActivityLocked與activityThread交互,執行應用進程後續流程

startActivityForResult打開的activity的finish流程

  1. Activity執行finish,ActivityTaskManager.getService().finishActivity傳入mToken, resultCode, resultData
  2. 執行到ActivityStack finishActivityResultsLocked,把resultCode, resultData添加到resultTo
private void finishActivityResultsLocked(ActivityRecord r, int resultCode, Intent resultData) {
        ActivityRecord resultTo = r.resultTo;
        ...
        resultTo.addResultLocked(r, r.resultWho, r.requestCode, resultCode, resultData);
        ...
    }

複製代碼

activityResume過程當中,resumeTopActivityInnerLocked(嘗試顯示 棧頂的activity)

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
       // 棧頂的 ActivityRecord
        ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
        final boolean hasRunningActivity = next != null;
        
        // mResumedActivity 當前處在Resume的ActivityRecord,
        // 棧頂的ActivityRecord是否已是RESUMED狀態
        // 若是系統當前正在中斷一個Activity,須要先等待那個Activity pause完畢,以後系統會從新調用resumeTopActivityInnerLocked函數,找到下一個要啓動的Activity
        if (mResumedActivity == next && next.isState(RESUMED)
                && display.allResumedActivitiesComplete()) { 
            executeAppTransition(options); 
            return false;
        }

        // transaction 能夠與對應進程的activityThread通信
        final ClientTransaction transaction = ClientTransaction.obtain(next.app.getThread(), next.appToken);
        ArrayList<ResultInfo> a = next.results;
        if (a != null) {
            final int N = a.size();
            if (!next.finishing && N > 0) { 
                // ActivityResultItem被傳到activityThread後,經過其execute方法,
                // 最終調用到deliverResults
                transaction.addCallback(ActivityResultItem.obtain(a));
            }
        }
    }
    
/**
 * r 經過ActivityClientRecord r = mActivities.get(token)獲得,經過它能夠獲得對應的activity對象
 */
private void deliverResults(ActivityClientRecord r, List<ResultInfo> results, String reason) {
        final int N = results.size();
        for (int i=0; i<N; i++) {
            ResultInfo ri = results.get(i);
            // 如下,也就執行到了activity內部,dispatchActivityResult會調到onActivityResult
            r.activity.dispatchActivityResult(ri.mResultWho,
                ri.mRequestCode, ri.mResultCode, ri.mData, reason);
        }
    }


複製代碼

簡單總結,由startActivityForResult啓動的activity,finish時,resultData會傳入resultTo也就是啓動它的 activity對應activityRecord,而後在這個在activity將要從新顯示在屏幕上時,若是存在results,將其分發到activityThread 最終吊起onActivityResult

若是哪裏有不對的地方
相關文章
相關標籤/搜索