android开机广播时序,Android P APP冷启动过程全解析(之一)

APP的启动过程是任何一个学习Android的人所必须了解的,这篇文章将会基于Android P完整的解析一个APP从其图标被点击到其界面显示完毕的全过程。

1、概述

概括地来说,一个APP的冷启动过程可以划分如下:

请求阶段

1.发起请求

2.解析Intent

3.创建ActivityRecord

4.分配Task

5.Pause前台activity

6.Resume请求的activity

进程启动阶段

7.AMS请求创建进程

8.Zygote fork进程

9.初始化 Runtime

10.注册进程到system_server

11.创建application

Activity初始化阶段

12.真正地start activity

13.加载activity

14.初始化窗口

Activity显示阶段

15.新建DecorView

16.新建ViewRootImpl

17.添加到Display

18.显示

注意,这里说的是APP冷启动的过程,如果不是冷启动,经过的阶段是上述阶段的一个子集。

2、发出启动Activity的请求

Android为了降低开发难度,屏蔽底层进程间通讯等细节,抽象出了四大组件。通过四大组件,我们可以方便、快捷地和其他组件进行交互,而不管目标组件是在哪个APP实现的、哪个进程中运行的。而一个APP的主Activity通常声明如下:

android.intent.action.MAIN:决定应用的入口Activity,也就是我们启动应用时首先显示哪一个Activity。

android.intent.category.LAUNCHER:表示activity应该被列入系统的启动器(launcher)(允许用户启动它)。Launcher是安卓系统中的桌面启动器,是桌面UI的统称。

凡是声明了上面filter的activity,都会被launcher解析出来,对应图标排列在桌面。这样,用户就可以通过点击的方式启动Activity了。

APP端启动activity流程是:

Acticity.startActivity(new Intent(this,target.class))

Acticity.startActivityForResult(...)

Instrumentation.execStartActivity(...)

IActivityManager.startActivity(...)

(为了简洁起见,上面过程省略一些重载的调用,以后调用过程同样如此)。

APP请求启动activity最重要的两个参数:Context和Intent。

Context指明我是谁。为了安全认证和管理,系统必须知道谁请求启动activity,而context里面包含着身份信息例如activity token等表明了我是谁,这样ActivityManagerService(以后简称AMS)才允许启动activity。

Intent指明我想做什么。上面的intent是显式intent,显式intent通常用在包内启动组件,如果是启动其他APP的组件,则通常用隐式intent。显式intent里面包含了一个ComponentName,ComponentName由包名 + 类名组成,可以唯一标识一个组件,系统通过ComponentName就可以找到要启动的组件。隐式intent通常通过Action来过滤出要启动的组件,这一点我们将在第3节中展开讲述。

在表明身份和说明意图后,最后通过Binder调用请求AMS启动目标activity

3、AMS解析Intent

本阶段的调用流程是:

AMS.startActivity(...)

AMS.startActivityAsUser(...)

ActivityStarter.startActivity(...)

ActivityStarter.startActivityMayWait(...)

ResolveInfo rInfo = ASS.resolveIntent(intent,uid,...)

PackageManagerService.resolveIntent()

ActivityInfo aInfo = ASS.resolveActivity(intent, rInfo...);

ActivityStarter.startActivty(intent,aInfo,...)

AMS解析Intent主要是通过PackageManagerService后面简称(PMS)来解析的,因为我们四大组件都是必须声明在AndroidManifest.xml文件中的(广播接收器允许动态注册)。Android这么做的原因上面也说过了,为了屏蔽进程间通讯细节,应用之间通过组件就可以交互,系统会在必要的时候拉起对方进程。在应用没起来之前,只有PMS知道应用都有哪些组件。应用四大组件的信息在应用安装的时候,就已经被PMS解析保存起来了。如果没有声明在AndroidManifest.xml文件中,那么AMS就无法获取目标组件的信息,对于显式intent,会抛出错误;对于隐式intent,也会启动失败。

解析出来的信息叫ResolveInfo,是一个容器类,里面包含了ActivityInfo,ServiceInfo,ProviderInfo等成员来表示四大组件的信息,activity和broadcast信息都是用ActivityInfo来表示的。这三个成员只有一个不为空,这里我们启动的是activity,所以ActivityInfo是不为空的。ActivityInfo包含了各种各样的activity信息,都是声明在AndroidManifest.xml文件中的,比较重要的包括launchMode、theme、screenOrientation等,其中还包含了ApplicationInfo,提供packageName、targetSdkVersion等重要信息。

额外说一句,这里的ActivityStarter.startActivityMayWait并不会等待,只有从AMS. startActivityMayWait调过来的才会真正等待activity起来,这个通常用在调试APP的启动过程。这个时候App还没起来,我们无法选择要调试的进程,这个就可以使用startActivityMayWait,App进程起来以后,会等我们设置断点,这样就可以第一时间调试了。命令如下:

adb shell am set-debug-app -w com.example.demo

4、创建ActivityRecord

本阶段的调用流程是:

ActivityStarter.startActivity(...)

ActivityRecord r = new ActivityRecord(callerApp,intent,aInfo,mSupervisor,...)

appToken = new Token(this, _intent);

setState(INITIALIZING, "ActivityRecord ctor");

在解析完intent以后,AMS会进一步的做各种的权限检查,包括没有找到目标组件的处理。随后就会新建一个ActivityRecord。ActivityRecord是AMS中保存activity信息的数据结构,AMS就是通过着一个结构来管理activity的,其主要包括的信息有:appToken、launchedFromPid、launchedFromUid、launchedFromPackage、userId、intent、componentName、requestCode、visible、taskAffinity、icon、logo、theme、processName、launchMode、fullscreen等信息。

在其构造函数中比较重要的工作是:

1)新建了AppToken,这是activity的唯一标识

2)ActivityRecord的状态初始化为INITIALIZING

到了这一步,表明activity相关档案已被建立,接下来要做的事情就是为该activity分配任务栈。

5、分配Task

本阶段的调用流程是:

ActivityStarter.startActivity(r,sourceRecord,true/* doResume */,...)

ActivityStarter.startActivityUnchecked(r,sourceRecord,doResume...)

ActivityRecord reusedActivity = getReusableIntentActivity()

result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, topStack)

mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask...)

mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,...)

startActivityUnchecked是本阶段的重点函数,需要详细分析

ActivityStarter.startActivityUnchecked(r, sourceRecord,doResume..){

//计算mLaunchFlags

computeLaunchingTaskFlags();

// 设置刚才在上面计算出来的mLaunchFlags

mIntent.setFlags(mLaunchFlags);

// 根据mLaunchFlags来查找是否有可重用的activity

ActivityRecord reusedActivity = getReusableIntentActivity();

if (reusedActivity != null) {

//如果找到了可重用的activity,需要清理掉原来的信息,并把当前启动的activity的信息拷贝进去

做清理和拷贝工作...

if((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0 ||

isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)){

// 没必要新建实例,回调onNewIntent并将top移至前台

deliverNewIntent(top);

}

// 计算哪个task和activity要移至前台,必要时会进行task的清理工作

reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity);

return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;

}

if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask

&& (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {

newTask = true;

// 重用或者新建task

result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, topStack);

}else if (mSourceRecord != null) {

// 不是新建task的,重用原activity的task

result = setTaskFromSourceRecord();

}

mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask...);

if (mDoResume) {

// 将任务栈移至前台

mTargetStack.moveToFront("startActivityUnchecked");

// 开始resume

mSupervisor.resumeFocusedStackTopActivityLocked(...)

}

}

总体来说,这个阶段就是根据activty的启动模式找到合适的task来放置activity,如果找不到或者强制新建,就会新建一个。具体过程如下:

首先,根据launchMode和Intent中的FLAG_ACTIVITY_NEW_TASK等flag综合计算activity的启动模式,结果保存在mLaunchFlags中。计算的过程不仅要考虑目标activity的launchMode,也要考虑原来activity的launchMode和Intent中所带着的flag。例如原来activity的launchMode是LAUNCH_SINGLE_INSTANCE,那么新activity只能新建task。

调用getReusableIntentActivity()查找是否有可以重用的activity,这个只对LAUNCH_SINGLE_INSTANCE和LAUNCH_SINGLE_TASK或者FLAG_ACTIVITY_NEW_ TASK不为0有用的actvity,对于standard的activity,该方法永远返回null。查找的依据包括ActivityType、ComponentName和taskAffinity等。

如果成功找到了可以重用的activity,要进行清理工作,把原来activity的信息替换成现在activity的信息。例如同一个activity,两次启动的caller可能不同,要进行更新。同时,还要进行根据launchMode来进行task内的清理。例如LAUNCH_SINGLE_TASK的activity,如果此类所在的task上面有其它Activity,那么其它的Activity会被销毁。

找到了可以重用的activity,那么就相当于把原来的activty替换成现在的activty,也就不用新建task了。但是如果没有找到重用的activity,那么调用setTaskFromReuseOrCreateNewTask()尝试寻找可以重用的task,注意此时前提是mStartActivity.resultTo == null && mLaunchFlags & FLAG_ACTIVITY_NEW_TASK,即,如果我们调用startActivityForResult启动的activity,那么是不能新建task的,二者是不兼容的。没有找到重用的task,此时会新建一个TaskRecord,置于前台,然后把activityt也置于task中activity队列的最后(即前台)。如果是重用原activity的task,那么执行setTaskFromSourceRecord,首先也是把该task移到前台,然后把activity置于该task的activity队列的最后。

经过前面四步,actvity及其对应的task位置已经安排妥当,现在可以准备让这个activity开始工作了!那么这开工的第一步就是mTargetStack.startActivityLocked,这里面的工作主要是通知WindowManagerService(以后简称为WMS)准备activty的过渡动画和startingWindow。

至此,为activity分配task的工作已经完毕。

6、Pause前台activity

上面说到了为activity分配了任务栈之后,会调用resumeFocusedStackTopActivityLocked准备进行其resume。调用流程如下:

ActivityStackSupervisor.resumeFocusedStackTopActivityLocked(targetStack,target...)

ActivityStack.resumeTopActivityUncheckedLocked(...)

ActivityStack.resumeTopActivityInnerLocked(...)

// next是要resume的activity

final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);

next.launching = true; // 将activity的状态置为launching

// pause所有的任务栈

boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, next, false);

// pause当前栈的activity

if (mResumedActivity != null && mResumedActivity != next) {

pausing |= startPausingLocked(userLeaving, false, next, false);

}

if (pausing && !resumeWhilePausing) {

//第一次进来的时候会在这里返回

return true;

}

if (next.app != null && next.app.thread != null {

//进行resume操作 {1}

}else{

// 这里会启动app进程 {2}

ActivityStackSupervisor.startSpecificActivityLocked(next, true, true);

}

因为之前已经新的activity做好入栈工作了,就是放在栈顶,所以通过topRunningActivityLocked可以找到它。如果当前要resume的activity不是之前已经resume的activity,那么必须将pause之前的activity才行,具体工作分两部分完成:

pause其他所有已经focus的任务栈的mResumedActivity

pause当前任务栈mResumedActivity

两步都会调到核心函数是:ActivityStack.startPausingLocked

粗略介绍如下:

ActivityStack.startPausingLocked()

ActivityRecord prev = mResumedActivity;

// 变更ActivityStack中pauseActivity的记录

mPausingActivity = prev;

mLastPausedActivity = prev;

prev.setState(PAUSING, "startPausingLocked");

if (prev.app != null && prev.app.thread != null) {

// 通知APP执行pause操作

mService.getLifecycleManager().scheduleTransaction(prev.app.thread, prev.appToken,

PauseActivityItem.obtain(prev.finishing, userLeaving,

prev.configChangeFlags, pauseImmediately));

}

// 监控APP是否pause超时,时间只有500ms

schedulePauseTimeout(prev);

上面代码很简单,就是变更一下mPausingActivity的状态,然后通过LifecycleManager来通知APP进行pause操作,最后设置一下超时msg,至此完成了所有resumedActivity的pause工作。

7、Resume请求的activity

接上面,当APP pause以后,会回调activityPaused通知AMS,然后AMS会执行completePauseLocked。该函数也会调用resumeTopActivityInnerLocked。这一次,由于所有resumedActivity都已经paused了,所以返回的结果pausing为false,所以可以继续进行目标activity的resume工作。

不过因为目标App的进程还没起来,所以上面的resumeTopActivityInnerLocked函数并没有真正做了resume操作,只是将target activtiy置为launching状态,通知前台的activity进行pause,然后调用startSpecificActivityLocked来启动进程,进程起来以后会再一次调到resumeTopActivityInnerLocked继续resume操作。

如果不是冷启动,在通知前台activity pause以后,会在{1}(第6节中的resumeTopActivityInnerLocked函数)中进行resume的工作,然后通知target app进行resume操作。所以在时序上应该是前台activity pause --> 目标activity resume,可以用图显示如下:

9e5da7dda3cf925e02fe218ae25ed6d6.png

图中虚线表示跨进程调用,同一种颜色表示同一个进程,启动时的入口是startActivtiyUncheck,app pause完了以后的入口是completePauseLocked。

准备阶段总结

其实各节的小标题已经说的很清楚了,做了一下事情:

1.发起请求,主要是是表明身份和目的

2.解析Intent,验证合法性

3.创建ActivityRecord,建立activity的档案记录

4.分配Task,安排activity的位置

5.Pause前台activity,先下

6.Resume请求的activity,后上

除了第1点是在APP进程进行的,其实都是在SystemServer进程进行的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值