重温ActivityManagerService--应用启动流程分析

前言

前一篇文章《重温ActivityManagerService》重温了AMS,记录了AMS启动过程,与AMS交互的几个重要的类和数据结构。

分析的笔记见:AMS源码分析笔记

此文章主要记录一个应用是如何启动起来的。其中分析启动过程我将以两种不同的启动方式来分析:

  • 根Activity的启动流程(进程没有启动的状态)
  • 子Activity的启动过程(进程已经启动)

1 图解Acitivity启动过程

凡事由浅入深,一口气带出全过程必然一脸蒙蔽。因此我先放出一张简易版的子Activity的启动过程图。小伙伴们肯定可以看懂一个界面是如何启动起来的。但是该图只能说明大致流程,没有考虑进程还没有启动的情况。

启动子Activity流程图.png

然后看看稍微详细一些的子Activity启动流程图,可以更加详细得明白APP和AMS是如何调度的。

子Activity组件启动过程.png

最后看看详细的根Activity的启动流程图,可以全面看清楚一个应用从无到启动完成的过程。后面章节也会详细叙述每个步骤做了什么。

根Activity组件启动过程.png

注意:根Activity的23步到24步还做了很多事件,例如AMS通过Socket请求Zygote fork一个子进程等操作没有画入到该流程图中,因为该过程在《Android系统启动流程分析》中有分析到,此处只列出了调用AMS的startProcessLocked()就启动了一个进程,然后调用了ActivityThread的main()方法。

小结:

Launcher组件启动MainActivity组件的过程:

  • (1)Launcher组件向AMS发送一个启动MAinActivity组件的进程间通信请求
  • (2)AMS首先将要启动的MAinActivity组件信息保存下来,然后再向Launcher组件发送一个进入中止状态的进程间通信请求。
  • (3)Launcher组件进入到中止状态之后,就会向AMS发送一个已进入中止状态的进程间通信请求,以便AMS可以继续执行启动MAinActivity组件的操作。
  • (4)AMS发现用来运行MAinActivity组件的应用程序进程不存在,因此,他就会先启动一个新的应用程序进程。
  • (5)新的应用程序进程启动完成之后,就会向AMS发送一个启动完成的进程间通信请求,以便AMS可以继续执行启动MAinActivity组件的操作。
  • (6)AMS将第2步保存下来的MAinActivity组件的信息发送给第4步创建的应用程序进程,以便它可以将MAinActivity组件启动起来。

Ok, let's go! 为了叙述的完整性,我们还是从启动根Activity开始分析,因此启动子Activity绝大多数步骤是沿用启动根Activity的流程。

2.启动根Activity

启动根Activity我们以在Launcher应用中点击Icon开始分析。

2.1 在Launcher进程中完成的操作
Launcher_startActivity.png

在Launcher进程中完成了以下五个步骤的调用,具体实现的功能如下:

  • 1.用户调用launcher的startActivitySafely,设置Activity组件启动标志位Intent.FLAG_ACTIVITY_NEW_TASK为1,然后调用父类的startActivity。
  • 2.调用startActivityForResult(intent,int),第二个参数为-1,表示不需要知道即将启动的Activity组件的执行结果。
  • 3.传递ApplicationThread给AMS,以便AMS告知Launcher可以进入pause状态,mToken对象指向AMS中的ActivityRecord,每一个已经启动的activity都会在AMS中记录一个ActivityRecord,用来记录activity组件运行状态。
  • 4.在execStartActivity中通过AMS的代理对象调用AMS的startActivity。
  • 5.AMS代理透传信息。
2.2 在ActivityManagerService中完成的操作
AMS_pause.png

从6至12步骤是在AMS中完成的操作,具体实现的功能如下:

  • 6.AMS内部有一个activityStack,用来描述一个activity组件堆栈,在该步骤中进一步调用mActivityStack的startActivityMayWait进一步响应进程间通信请求。
  • 7.在PMS中解析Intent的内容,以获得要启动activity组件的信息。然后调用startActivityLocked继续启动activity
  • 8.每一个应用程序都会使用ProcessRecord对象来描述并且保存在AMS内部。通过传进来的ApplicationThread在AMS中查询到Launcher的ProcessRecord。从mHistory堆栈中获得launcher组件的ActivityRecord。创建一个Mactivity的ActivityRecord。调用startActivityUncheckedLocked启动目标组件
  • 9.判断新启动的activity是否要在新任务中启动,在AMS中查询该任务是否已经存在。为MainActivity创建新的任务,并且将该任务加入到AMS的task堆栈中。将目标ActivityRecord保存在mHistory中,并且把MAinActivity放在栈顶,调用resumeTopActivityLocked。
  • 10.从栈顶取出activity,判断当前activity是不是被激活的activity,如果是,并且状态是Resumed直接退出,说明已经启动了。判断当前activity是否是上一次被中止的activity组件。由于当前activity是launcher,调用startPausingLocked来通知他进入pause状态。
  • 11.向launcher发送一个中止通知,并且在AMS中发送一个中止超时消息,如果Launcher没有在超时时间内返回,那么就认为Launcher没有响应了。
  • 12.透传参数给ApplicationThread。
2.3 在Launcher进程中完成的操作
launcher_pause.png

从13至17步骤是在Launcher中完成的操作,具体内容如下:

  • 13.调用ActivityThread的queueOrSendMessage,这个步骤已经在launcher进程中。准备将通知暂停的消息抛到主线程。

  • 14.封装一个message发送到mH主线程。

  • 15.处理activityThread发来的PAUSE_ACTIVITY

  • 16.(1)从launcher进程中的mActivities中取出ActivityClientRecord。

      (2)调用performUserLeavingActivity向launcher组件发送一个用户离开事件通知,即调用成员函数onUserLeaveHint
      (3)调用performPauseActivity向Launcher发送中止事件通知,调用成员函数onPause
      (4)调用QueuedWork类的静态方法waitToFinish()等待完成前面的一些数据写入操作。为了回到Resumed状态时,能够恢复当前的状态信息。
      (5)调用handlePauseActivity向AMS发送中止Launcher组件的进程间通信请求。
    
  • 17.从AMS代理调用activityPaused。

2.4 在ActivityManagerService中完成的操作
AMS_startproc.png

从18至23步骤是在AMS中完成的操作,具体实现的功能如下:

  • 18.收到launcher已经暂停的通知之后,调用activitystack的activityPaused.
  • 19.从mHistory取出launcher的ActivityRecord,从mHandler中移除暂停超时通知。调用completePauseLocked。
  • 20.把当前进入mPausingAcitivity取出,如果是空的,表示当前正在进入中止的Activity已经进入了Paused状态。检查当前系统是不是正在进入睡眠或者关闭状态。
  • 21.取出栈顶的ActivityRecord,MainActivity的app是空的,所以调用startSpecificActivityLocked将MainActivity的进程启动起来。
  • 22.通过activityRecord中的UID和进程名字查找是否存在MAinActivity所在的进程,如果存在则直接通知Activity启动,否则AMS的startProcessLocked来启动MAinActivity应用进程。如果进程已经起来了,那就调用realStartActivityLocked
  • 23.(1)通过processName和uid判断ProcessRecord是否存在,如果不存在,调用startProcessLocked启动新进程。
    (2)创建新进程用户Id和用户组Id
    (3)Process.start方法启动一个新的应用进程
    (4)将新创建的ProcessRecord保存在AMS的mPidsSelfLocked中,并且发送一个启动计时Message,如果超时AMS认为进程启动失败,否则AMS就会通知应用启动Maintivity。
2.5 在Launcher进程中完成的操作
launcher_attach.png

24步之前,AMS会调到Zygote, Zygote会fork一个进程出来,然后调用Launcher的ActivityThread.main().

从24至25步骤是在Launcher中完成的操作,具体内容如下:

  • 24.此步骤已经进入了新进程Main方法,在Main方法中新建MainLooper,然后new一个ActivityThread,并且调用attach方法,最后Looper循环起来。在ActivityThread中新建一个ApplicationThread,通过AMS代理调用attachApplication。
  • 25.新建进程状态透传给AMS。此处将IApplicationThread传递给AMS,AMS之后就可以通过Binder 客户端和新起来的进程通信了。
2.6 在ActivityManagerService中完成的操作
AMS_launch_activity.png

从27至30步骤是在AMS中完成的操作,具体实现的功能如下:

  • 27.(1)通过PID在AMS中找到新建的ProcessRecors并且初始化他们。
    (2)移除应用启动超时消息
    (3)获取栈顶activityRecord,并且判断栈顶的activityRecord和要启动的是不是一致的。如果是一致的,调用activityStack的realStartAcitivityLocked
  • 28.将ActivityRecord的app值赋成新建的APP,然后将activity组件存入app所描述的进程中。然后在app描述的线程中调用scheduleLauncherActivity来通知新创建的进程启动activityRecord所描述的组件,即MainActivity
  • 29.透传activityRecord信息
  • 30.此步已经回到了新启动的应用进程。新建一个ActivityClientRecord,将AMS传来的参数对该ActivityClientRecord初始化,并且调用queueOrSendMessage
2.7 在Launcher进程中完成的操作
launcher_handle_create.png

从31至35步骤是在Launcher中完成的操作,具体内容如下:

  • 31.封装一个message向主线程handle一个消息。
  • 32.通过ActivityClientRecord的applicationInfo查询新启动应用程序的包信息。然后调用handleLaunchActivity来启动一个新的activity
  • 33.调用performLaunchActivity将MainActivity启动起来,并且调用HandleResumeActivity表示当前activity已经处于已激活状态。
  • 34.(1)获取componentName,并且用ActivityClientRecord.packageInfo的类加载器加载MainActivity类。
    (2)ActivityClientRecord.packageInfo的makeApplication创建一个Application对象。
    (3)如果上面加载的MainActivity不是空的。新建一个contextImpl并且用ActivityClientRecord来初始化他。
    (4)调用activity.attach来初始化activity
    (5)调用mInstrumentation.callActivityOnCreate
    (6)将activityClientRecord的token作为关键字,并且将他保存在Activity的mActivity中。token是一个Binder代理,指向AMS中对应activity的ActivityRecrod
  • 35.onCreate被调用,加载用户界面,已经对用户界面上的控件进行初始化。
2.8 小结

MainActivity组件作为应用程序Activity的根activity,他启动起来就意味着应用程序启动起来了。因此我们可以将一个根Activity的启动过程看做一个Android应用程序的启动过程。

3.启动子Activity

启动子Activity和启动根Activity步骤相似度非常高,只是在startActivityUncheckedLocked中判断是否能通过AcitivityRecord获得到IApplicationThread,如果获取不到就说明进程没有启动,就去先启进程;如果不为空,就直接启动子Activity。

3.1 在Launcher进程中完成的操作
launcher_sub_startactivity.png

从1至5步骤是在Launcher中完成的操作,具体内容如下:

  • 1.设置intent,调用startActivity
  • 2.3.4.5.沿用根activity启动的流程,并且目前是在原应用进程中执行。
3.2 在ActivityManagerService中完成的操作
AMS_sub_pause.png

从6至12步骤是在AMS中完成的操作,具体实现的功能如下:

  • 6.7.8.沿用根activity流程。从这三步开始在AMS进程中执行。
  • 9.判断是否需要创建一个新的任务来启动子activity组件。将新建的activityRecord加入到mHistory中。调用resumeTopActivityLocked
  • 10.检查要启动的activity是否已经是当前激活的activity,判断要启动的activity的是否是上次中止的activity。
  • 11.发送暂停通知,并且开始计时
  • 12.透传暂停通知
3.3 在Launcher进程中完成的操作
launcher_sub_pause.png

从13至17步骤是在Launcher中完成的操作,具体内容如下:

  • 13.调用queueOrSendMessage
  • 14.封装message并且发送到主线程
  • 15.处理暂停事件
  • 16.(1)获得前一个组件ActivityClientRecord
    (2)向前一个组件发送离开通知,调用onUserLeaveHint
    (3)调用前一个组件的中止通知onPause
    (4)等待前一个组件将数据写入磁盘
  • 17.将前一个组件已经暂停的通知告知AMS
3.4 在ActivityManagerService中完成的操作
AMS_sub_launch_activity.png

从18至25步骤是在AMS中完成的操作,具体实现的功能如下:

  • 18.透传已暂停通知给AMS
  • 19.暂停计时,设置上一个activity为pause状态
  • 20.设置中止中的activity为上一个activity,说明暂停状态已经结束
  • 21.取出栈顶的ActivityRecord,SubActivity的app是空的,所以调用startSpecificActivityLocked将SubActivity的进程启动起来。
  • 22.查看processRecord是否是空,如果不是空,则调用realStartActivityLocked真正启动一个activity
  • 23.将ActivityRecord的app值赋成新建的APP,然后将activity组件存入app所描述的进程中。然后在app描述的线程中调用scheduleLauncherActivity来通知新创建的进程启动activityRecord所描述的组件,即SubActivity
  • 24.透传activityRecord信息
  • 25.用传来的activityRecord初始化activityClientRecord
3.5 在Launcher进程中完成的操作
launcher_sub_handle_create.png

从26至30步骤是在Launcher中完成的操作,具体实现的功能如下:

  • 26.封装一个message,向主线程发送一个message
  • 27.获取APK资源来处理启动activity事件
  • 28.调用performLaunchActivity将MainActivity启动起来,并且调用HandleResumeActivity表示当前activity已经处于已激活状态。
  • 29.(1)获取componentName,并且用ActivityClientRecord.packageInfo的类加载器加载MainActivity类。
    (2)ActivityClientRecord.packageInfo的makeApplication创建一个Application对象。
    (3)如果上面加载的MainActivity不是空的。新建一个contextImpl并且用ActivityClientRecord来初始化他。
    (4)调用activity.attach来初始化activity
    (5)调用mInstrumentation.callActivityOnCreate
    (6)将activityClientRecord的token作为关键字,并且将他保存在Activity的mActivity中。token是一个Binder代理,指向AMS中对应activity的ActivityRecrod
  • 30.onCreate被调用,加载用户界面,已经对用户界面上的控件进行初始化。
3.6 说明

如果子activity设置了android:process属性为另一个进程,启动过程跟MainActivity启动一样,只是不用再新建一个task了,因为子activity的android:taskAffinity和MAinActivity是一样的,MainActivity已经新创建了一个task,可以在同一个task中进程。

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

推荐阅读更多精彩内容