Activity 启动流程分析(Android10)

概览

Activity 启动流程比较复杂,分析起来很容易陷入复杂的代码中去,所以我这篇分析文章不会深入的分析代码细节,尽量抛开不重要的东西把启动流程按照重点分成几个部分来分析。
下图是 Activity 启动流程的总览。

(上图红色方块是与 WMS 关联的部分,后面会从这里开始介绍 WMS)
后面我们将以 Android 29 为例,按照上图步骤逐步分析。

Intent 解析到 Activity

调用 startActivity 之后,经过几步辗转最终会调用到 AMS 中,而 AMS 又会调用 ActivityStarter 来启动 Activity。
解析 Intent 的任务将由PackageManagerService#resolveIntent方法来处理。
Intent 匹配规则太负责了,我本意是想学习启动流程,所以就没深入进去看代码,就这样吧。

通过 Zygote 创建进程

如果目标进程未启动,则会先启动进程,这里先把启动进程的方法调用栈放上来,方便后面分析。

ActivityStackSupervisor#startSpecificActivityLocked
ActivityManagerService$LocalService#startProcess
ProcessList#startProcessLocked
Process#start
ZygoteProcess#start

方法最终是走到了 ZygoteProcess 中,这个类负责创建进程,我们知道 Android 系统的进程由 Zygote 创建,而 ZygoteProcess 实际上是负责与 Zygote 进行通信的类。

ZygoteProcess#attemptUsapSendArgsAndGetResult方法中打开了一个 LocalSocket 连接,通过这个 Socket 与 Zygote 进行通信。

该 LocalSocket 服务端在 ZygoteServer 中,我们再看下服务端具体是如何做的。

我们主要关注ZygoteServer#runSelectLoop方法,里面最重要的两行代码如下:

ZygoteConnection connection = peers.get(pollIndex);
final Runnable command = connection.processOneCommand(this);

那我们再跳到ZygoteConnection#processOneCommand中看看。

Runnable processOneCommand(ZygoteServer zygoteServer) {
    ...
    //处理并应用参数
    args = Zygote.readArgumentList(mSocketReader);
    parsedArgs = new ZygoteArguments(args);
    Zygote.applyUidSecurityPolicy(parsedArgs, peer);
    ...
    pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,
            parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
            parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
            parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion);
    ...
    if (pid == 0) {
        return handleChildProc(parsedArgs, descriptors, childPipeFd,
                parsedArgs.mStartChildZygote);
    } else {
        handleParentProc(pid, descriptors, serverPipeFd);
        return null;
    }
}

看到没,找了半天,终于找到了 Zygote,这里主要通过Zygote.forkAndSpecialize方法来孵化进程,并获取 pid。

forkAndSpecialize 方法主要就是调用了Zygote.nativeForkAndSpecialize方法,根据名字可以看出来这是一个原生方法,我们就到此为止吧,不继续了。

我们再回到ZygoteConnection#processOneCommand方法中,进程创建完成后,还会继续调用 handleChildProc 方法,我们顺着这个方法继续看下去。

private Runnable handleChildProc(ZygoteArguments parsedArgs, FileDescriptor[] descriptors,
                                 FileDescriptor pipeFd, boolean isZygote) {
    closeSocket();
    //省略...
    if (parsedArgs.mInvokeWith != null) {
    //省略...
    } else {
        if (!isZygote) {
            //isZygote 一般为 false
            return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
                    parsedArgs.mRemainingArgs, null /* classLoader */);
        } else {
            return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion,
                    parsedArgs.mRemainingArgs, null /* classLoader */);
        }
    }
}

最后调用了ZygoteInit.zygoteInit方法,这个方法是用来进程进行初始化操作的,上面进程创建完成后,还需要对其进行初始化操作才能让整个 APP 运转起来。

实际上 zygoteInit 方法就是通过反射找到ActivityThread.main方法并调用。关于这个方法后面会介绍。

ActivityThread

ActivityThread 实际上并不是个线程,他并没有继承 Thread 类,我们下面来分析下这个类到底是个什么鬼。

上面说到 Zygote 最后会调用Activity.main方法,main 方法也就是程序的入口,一个 Android 软件也就是从这里开始的。

public static void main(String[] args) {
    //省略...
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

上面是 main 函数的部分代码,主要做了如下几件事:

  • 实例化 ActivityThread
  • 调用 ActivityThread#attach 方法
  • 启动 Loop 消息循环队列

着重看一下 attach 方法,其中主要做了一些程序启动的前置准其中的方法调用顺序如下:

ActivityThread#attach
ActivityManagerService#attachApplication
ActivityThread$ApplicationThread#bindApplication
ActivityThread#handleBindApplication

ApplicationThread 跟 ActivityThread 一样,都不是一个线程,我们直接看ActivityThread#handleBindApplication方法好了,另外几个都是一些乱七八糟的判断和参数组织之类的。

handleBindApplication 这个方法比较长,主要就是一些前置准备、检查、初始化等操作,例如:

  • 调试模式下启动 APP 时显示 waiting for debugger 的等待对话框
  • 初始化分辨率
  • 设置时间格式
  • 初始化包名、依赖库路径、资源文件路径
  • 创建并初始化 Instrumentation,这个类很重要
  • 调用 LoadApk#makeApplication 创建 Application 实例

如果未指定程序的 Application 则使用默认的 Application 类,否则使用我们指定的,然后获取到 ClassLoader 并调用 Instrumentation#newApplication 方法,Application 创建完成后继续回调 Application#onCreate 方法。

Activity#onCreate

我们上面看到 ActivityThread#main 中开启了消息循环,主线程所有的操作都将通过 Handler 来分发。

关于生命周期的调用链也比较复杂,有个专门的 ClientLifecycleManager 类负责管理生命周期,这个类最终也是通过 Handler 的方式来控制生命周期。具体的调用链这里就不贴了,挺复杂的,也没必要,不是本文的重点,我们只需要知道最终调用到了ActivityThread#performLaunchActivity方法就行了。

这个方法将会创建目标 Activity 实例,并回调 attach 及 onCreate 方法,我们来看下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    java.lang.ClassLoader cl = appContext.getClassLoader();
    //通过 Instrumentation 实例化 Activity 对象
    Activity activity = mInstrumentation.newActivity(
            cl, component.getClassName(), r.intent);
    if (activity != null) {
        CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
        //回调 attach 方法,设置 title 等等
        activity.attach(appContext, this, getInstrumentation(), r.token,
                r.ident, app, r.intent, r.activityInfo, title, r.parent,
                r.embeddedID, r.lastNonConfigurationInstances, config,
                r.referrer, r.voiceInteractor, window, r.configCallback,
                r.assistToken);
        int theme = r.activityInfo.getThemeResource();
        if (theme != 0) {
            //设置样式
            activity.setTheme(theme);
        }
        //回调 Activity#onCreate
        if (r.isPersistable()) {
            mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
        } else {
            mInstrumentation.callActivityOnCreate(activity, r.state);
        }
    }
    return activity;
}

(上面代码只是简写版,中间省略了很多)
重点都在注释里面了。

Activity#attach

attach 方法也比较重要,这是 Activity 初始化方法,除了基本设置之外,还负责创建 Window 对象,为后面的显示页面做好准备。

final void attach(Context context, ActivityThread aThread,
                  Instrumentation instr, IBinder token, int ident,
                  Application application, Intent intent, ActivityInfo info,
                  CharSequence title, Activity parent, String id,
                  NonConfigurationInstances lastNonConfigurationInstances,
                  Configuration config, String referrer, IVoiceInteractor voiceInteractor,
                  Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
    //初始化 FragmentManager
    mFragments.attachHost(null /*parent*/);

    //实例化 Window,一般为 PhoneWindow
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    //设置 Window.Callback 回调,用于接收各种输入事件
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
    mUiThread = Thread.currentThread();

    mMainThread = aThread;
    mApplication = application;
    mActivityInfo = info;
    mTitle = title;
    //设置 WindowManager
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
}

这个重点也写在注释里了,主要就是创建 PhoneWindow 并设置回调,关于 Window 系列的东西我的下一篇文章会继续介绍,这里先把他们互相调用的接口留出来。

ActivityThread#handleResumeActivity

onCreate 回调完成后就开始准备显示页面然后回调 onResume。这个操作从 handleResumeActivity 方法开始。

@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
                                 String reason) {
    //通过 performResumeActivity 方法获取到 Activity,该方法还会调用 Activity#onCreate 方法
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    final Activity a = r.activity;

    //是否需要显示
    boolean willBeVisible = ActivityManager.getService().willActivityBeVisible(
            a.getActivityToken());
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        //获取 DecorView,这里的 DecorView 是在 setContentView 时 Window 创建好的
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        if (!a.mWindowAdded) {
            a.mWindowAdded = true;
            //将 DecorView 添加到 WindowManager 中
            wm.addView(decor, l);
        } else {
            a.onWindowAttributesChanged(l);
        }
    } else if (!willBeVisible) {
        r.hideForNow = true;
    }

    //调用 Activity#makeVisible 方法使其可见
    if (r.activity.mVisibleFromClient) {
        r.activity.makeVisible();
    }
}

该方法主要做了如下几件事:

  • 回调Activity#onCreate
  • 将 DecorView 添加到 WindowManager 中
  • 调用Activity#makeVisible方法使 Activity 可见

还有一些其他的操作这里就不介绍了,上面代码也省略了很多本文不关心的部分。

Activity#makeVisible方法其实很简单,看上面代码,再把 DecorView 添加到 WM 之前会先调用decor.setVisibility(View.INVISIBLE)方法使其不可见,而Activity#makeVisible方法就是调用了mDecor.setVisibility(View.VISIBLE)使其变成可见状态。

好了,关于 Activity 的启动流程就这么多了,关于具体 Activity 如何显示的属于 WindowManagerService 部分,我们上面也说了是哪里调用了 WMS,下一篇文章将会从本文提到的几个 WMS 的接口方法开始说起,一直到 Window 如何被加载显示等等。

如果觉得还不错的话,欢迎关注我的个人公众号:zhangke_blog

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270