android系统代码看不下去?让我们从debug 开始

863 阅读6分钟

开头

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方法,一个一个找过去,很多方法中又代码很多, 可能看着看着就断了,然后回头找参数,哪里做了保存,再全局收索看哪里做了调用,然后看着收索框里一堆的结果,脑子里就闪过了 我是谁、我在哪、我要干嘛来的。如果看岔了,也是越看越迷,越看越偏,最后脑子里又荡起了,我是谁、我在哪、我要干嘛来的, 不知道你们有没有这种感受,反正我经常有。



想想我们遇到bug是怎么一步一步追溯,怎么解决的?
debug,打断点,然后跟着调用栈一步一步找问题,那么我们能不能对系统代码打断点呢?

中间

系统代码debug

下载系统 源码 mac视角 ,首先准备好至少100g空间

  1. 确保主目录下有一个 bin/ 目录,并且该目录包含在路径中:
    mkdir ~/bin
    PATH=~/bin:$PATH
    ps: 源码不支持在大小写敏感的文件系统中比编译,参考第8步,这个也可以放在第一步,随后所有命令在/Volumes/android下执行,但是速度很慢

  2. 下载 Repo 工具,并确保它可执行:
    curl storage.googleapis.com/git-repo-do… > ~/bin/repo
    chmod a+x ~/bin/repo

  3. 创建一个空目录来存放您的工作文件。如果您使用的是 MacOS,必须在区分大小写的文件系统中创建该目录。为其指定一个您喜欢的任意名称:
    mkdir WORKING_DIRECTORY
    cd WORKING_DIRECTORY

  4. 使用您的真实姓名和电子邮件地址配置 Git。要使用 Gerrit 代码审核工具,您需要一个与已注册的 Google 帐号关联的电子邮件地址。 确保这是您可以接收邮件的有效地址。您在此处提供的姓名将显示在您提交的代码的提供方信息中。
    git config --global user.name "Your Name"
    git config --global user.email "you@example.com"

  5. 运行 repo init 以获取最新版本的 Repo 及其最近的所有错误更正内容。您必须为清单指定一个网址,该网址用于指定 Android 源代码中包含的各个代码库将位于工作目录中的什么位置。
    repo init -u android.googlesource.com/platform/ma…

  6. 要对“master”以外的分支进行校验,请使用 -b 来指定相应分支。要查看分支列表,请参阅源代码标记和版本。
    repo init -u android.googlesource.com/platform/ma… -b android-10.0.0_r1

  7. 要将 Android 源代码树从默认清单中指定的代码库下载到工作目录,请运行以下命令:
    repo sync


ps: repo sync 耗了我,7、8个小时~~ 同步完了内存吃紧,可以把.repo文件夹删除

  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



如果哪里有不对的地方