Android系統啟動過程分析
Android系統啟動過程分析
一、Android平臺架構
首先貼一張Android系統架構圖方便理解整個Android架構,這可以讓我們從整體上對整個啟動流程有個大概認知。
可以看出整個架構由5部分構成,從下到上分別為:
1. Linux核心層
Android 的核心繫統服務基於Linux 核心,在此基礎上添加了部分Android專用的驅動。系統的安全性、記憶體管理、程序管理、網路協議棧和驅動模型等都依賴於該核心。
2. 硬體抽象層(HAL)
硬體抽象層是位於作業系統核心與硬體電路之間的介面層,其目的在於將硬體抽象化,為了保護硬體廠商的智慧財產權,它隱藏了特定平臺的硬體介面細節,為作業系統提供虛擬硬體平臺,使其具有硬體無關性,可在多種平臺上進行移植。 HAL 由多個庫模組組成,每個模組都為特定型別的硬體元件實現了一個介面,例如相機或藍芽模組。 當框架 API 呼叫裝置硬體時,Android 系統為該硬體元件載入庫模組。
3. 系統執行庫層(Native)
系統執行庫層分為兩部分,分別是C/C++程式庫和Android執行時庫。 C/C++程式庫被Android中不同的部分使用 runtime庫主要是Java核心庫(提供了Java語言核心功能因此開發者可以使用Java編寫應用)和ART(Android 5.0 之前是Dalvik)該虛擬機器不同於JVM是專門用於移動裝置的虛擬機器 允許在有限的記憶體內執行多個虛擬機器例項 並且每個應用都是獨立的linux程序這樣可以防止虛擬機器崩潰時不會導致所有的程序被關閉。ART和Dalvik的區別是 後者在應用每次啟動時才會通過即時編譯把位元組碼轉化為機器碼 (這會影響應用執行效率)ART則是在應用第一次安裝的時候就會把位元組碼檔案轉換為機器碼 這樣雖然會在安裝時耗時增加 但app每次啟動的時間會減少
4. 應用框架層(Java Framework)
應用框架層為開發人員提供了可以開發應用程式所需要的API,我們平常開發應用程式都是呼叫的這一層所提供的API,當然也包括系統的應用。這一層的是由Java程式碼編寫的,可以稱為Java Framework
該層包含以下內容:
- 一個豐富和可擴充套件的檢視系統
- Resource Manager
- Notification Manager
- Activity Manager
- Content Providers
- 應用層
系統內建的應用程式以及非系統級的應用程式都是屬於應用層。負責與使用者進行直接互動,通常都是用Java進行開發的
二、Android系統的啟動流程
Android系統的啟動流程大致分為三個階段:
- 電源鍵按下後加載載入程式Bootloader到RAM 然後Bootloader把OS拉起
- Linux 核心層面的啟動
- Android系統層面的啟動
本文主要分析Android系統層面的啟動
一、Bootloader的載入
當電源鍵被按下後會去載入Bootloader,這個程式會把OS拉活,然後就會進入OS的啟動過程。
二、Linux核心的啟動
Android底層是基於Linux核心的並做了一些擴充套件,新增一些驅動比如binder。Linux核心的啟動流程是先啟動idle執行緒(整個Linux系統的初始執行緒 pid=0)然後idle執行緒會啟動init和kthread執行緒。init執行緒是負責啟動Android系統並且是Android系統其他程序的父程序。kthread執行緒則負責建立並管理Linux系統核心執行緒。
1、ildle程序
主要是完成Linux系統的啟動然後完成一些初始化任務之後會進入一個死迴圈,不斷判斷是否有新的程序需要啟動如果沒有則會讓cpu和系統時鐘進入休眠狀態 當有新程序需要建立時在喚醒cpu
2、kthread程序
主要負責建立Linux系統核心相關執行緒 並始終執行在核心執行緒中 作為核心執行緒中其他執行緒的父類 負責核心執行緒的排程和管理
3、init程序
它是Linux系統第一個使用者程序,主要工作分為兩部分。首先會完成核心的建立和初始化這部分內容跟Linux核心相關, 其次就是使用者空間的建立和啟動這部分內容跟Android系統的啟動相關
init程序第一階段做的主要工作是掛載分割槽,建立裝置節點和一些關鍵目錄,初始化日誌輸出系統,啟用SELinux安全策略
init程序第二階段主要工作是初始化屬性系統,解析SELinux的匹配規則,處理子程序終止訊號,啟動系統屬性服務,可以說每一項都很關鍵,如果說第一階段是為屬性系統,SELinux做準備,那麼第二階段就是真正去把這些落實的。
其實init程序在完成第二階段工作的時候就已經涉及到Android系統層面的啟動了 因為init程序會去載入init.rc配置檔案然後啟動Zygote程序。
Init的啟動入口是在其main函式中
int main(int argc, char** argv) {
......
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage) {
boot_clock::time_point start_time = boot_clock::now();
umask(0);
//建立和掛載啟動所需要的檔案目錄
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
#define MAKE_STR(x) __STRING(x)
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
chmod("/proc/cmdline", 0440);
gid_t groups[] = { AID_READPROC };
setgroups(arraysize(groups), groups);
mount("sysfs", "/sys", "sysfs", 0, NULL);
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
......
// Set up SELinux, loading the SELinux policy.啟動SELinux
selinux_initialize(true);
......
}
......
//對屬性服務進行初始化
property_init();
......
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
PLOG(ERROR) << "epoll_create1 failed";
exit(1);
}
//用於設定子程序訊號處理函式,如果子程序異常退出,init程序會呼叫該函
//數中設定的訊號處理函式進行處理。
signal_handler_init();
......
//啟動屬性服務
start_property_service();
......
if (bootscript.empty()) {
//解析init.rc配置檔案
parser.ParseConfig("/init.rc");
parser.set_is_system_etc_init_loaded(
parser.ParseConfig("/system/etc/init"));
parser.set_is_vendor_etc_init_loaded(
parser.ParseConfig("/vendor/etc/init"));
parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
}
......
while (true) {
......
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
am.ExecuteOneCommand();
}
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
//重啟死去的服務
restart_processes();
}
......
}
return 0;
}
我們注意下init.rc檔案,它的路徑為:system/core/rootdir/init.rc。它是一個非常重要的配置檔案,是由Android初始化語言編寫的指令碼。Android8.0對該檔案進行來拆分,每個服務對應一個rc檔案,啟動Zygote的指令碼在init.zygoteXX.rc中定義,代表多少位處理器。這裡以64位為例。
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
我們只需要知道上面的命令就是建立名為zygote的程序即可。實際建立是通過app_process64完成的,對應的檔案為app_main.cpp,建立完成後會呼叫zygote的mian函式。
int main(int argc, char* const argv[])
{
...
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));//1
...
// Parse runtime arguments. Stop at first unrecognized option.
bool zygote = false;
bool startSystemServer = false;
bool application = false;
String8 niceName;
String8 className;
++i; // Skip unused "parent dir" argument.
while (i < argc) {
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
zygote = true;//2
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
className.setTo(arg);
break;
} else {
--i;
break;
}
}
...
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);//3
} else if (className) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
}
首先在註釋1處聲明瞭一個AppRuntime型別的runtime,然後在註釋2處將zygote置為true,最後在註釋3處用runtime.start()啟動zygote。
三、Android 系統的啟動
之前我們跟蹤到zygote程序的啟動,zygote可以看做是Android系統所有程序的父程序,DVM和ART、應用程式程序以及執行系統的關鍵服務SystemServer程序都是由Zygote程序通過fork建立的。Zygote預載入初始化核心庫類,讓DVM和ART虛擬機器共享程式碼、降低記憶體佔用和啟動時間,因此只要Zygote裝載好了這些類和資源後,新的程序就不需要在裝載這些類和資源了,它們共享Zygote程序的資源和類。
那接下來我們看下AppRuntime的start方法
frameworks/base/core/jni/AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
...
/* start the virtual machine */
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
//1 啟動Java虛擬機器
if (startVm(&mJavaVM, &env, zygote) != 0) {
return;
}
onVmCreated(env);
/*
* Register android functions.
*
*/
if (startReg(env) < 0) {//2 註冊jni方法
ALOGE("Unable to register all android natives\n");
return;
}
/*
* We want to call main() with a String array with arguments in it.
* At present we have two arguments, the class name and an option string.
* Create an array to hold them.
*/
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
stringClass = env->FindClass("java/lang/String");
assert(stringClass != NULL);
strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
assert(strArray != NULL);
//從app_main傳過來的引數classname值為:“com.android.internal.os.ZygoteInit”
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
for (size_t i = 0; i < options.size(); ++i) {
jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
assert(optionsStr != NULL);
env->SetObjectArrayElement(strArray, i + 1, optionsStr);
}
/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
char* slashClassName = toSlashClassName(className != NULL ? className : "");
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");//3 找到ZygoteInit的main函式,該函式是要啟動的函式
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);//4 通過Jni呼叫ZygoteInit的main函式
#if 0
if (env->ExceptionCheck())
threadExitUncaughtException(env);
#endif
}
}
在start方法中我們看到先是在註釋1處啟動虛擬機器然後在註釋2處註冊jni.之後就是通過傳入的類名找到要啟動的class最終在註釋3處將其賦值給startMeth(此時表示要啟動ZygoteInit的main函式),最終的啟動是在註釋4處。
我們看下ZygoteInit的main函式
public static void main(String argv[]) {
...
ZygoteServer zygoteServer = new ZygoteServer();//1 構造zygoteServer例項
...
boolean startSystemServer = false;
String socketName = "zygote";
String abiList = null;
boolean enableLazyPreload = false;
for (int i = 1; i < argv.length; i++) {
if ("start-system-server".equals(argv[i])) {
startSystemServer = true;
} else if ("--enable-lazy-preload".equals(argv[i])) {
enableLazyPreload = true;
} else if (argv[i].startsWith(ABI_LIST_ARG)) {
abiList = argv[i].substring(ABI_LIST_ARG.length());
} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
socketName = argv[i].substring(SOCKET_NAME_ARG.length());
} else {
throw new RuntimeException("Unknown command line argument: " + argv[i]);
}
}
zygoteServer.registerServerSocket(socketName);//2 註冊服務端的scoket
if (startSystemServer) {//通過fork啟動SystemServer程序
Runnable r = forkSystemServer(abiList, socketName, zygoteServer);//3
// {@code r == null} in the parent (zygote) process, and {@code r != null} in the
// child (system_server) process.
if (r != null) {
r.run();//此處呼叫的run方法是MethodAndArgsCaller類的run 該類是RuntimeInit的一個靜態內部類
return;
}
}
Log.i(TAG, "Accepting command socket connections");
// The select loop returns early in the child process after a fork and
// loops forever in the zygote.
caller = zygoteServer.runSelectLoop(abiList);//4等待客戶端請求 fork程序
} catch (Throwable ex) {
Log.e(TAG, "System zygote died with exception", ex);
throw ex;
} finally {
zygoteServer.closeServerSocket();
}
在註釋1處構造了一個zygoteServer例項,之後在註釋2處註冊服務端的scoket,這裡scoket的型別是loaclscoket它是Android對Linuxscoket的一種封裝。Local Socket 是Linux提供的一種基於Socket的程序間通訊方式,對Server端來講,唯一的區別就是bind到一個本地的檔案描述符(fd)而不是某個IP地址和埠號。Android裡很多地方用到了Local Socket做程序間的通訊。scoket建立完成後就等待用來相應客戶端fork的請求,即在註釋3處通過fork啟動SystemServer程序,在註釋4處等待客戶端fork程序的請求。
總結來說ZygoteInit主要做了兩件事:
1、註冊了服務端的scoket
2、fork程序 啟動SystemServer程序
此處說下zygoteinit這個類 Android8.0發生了一些改動。之前在main函式會先預載入(preload)一些東西然後註冊服務端socket。在8.0版本預載入的操作放在了靜態程式碼塊中,下面是preload的靜態程式碼塊。preload主要做了兩件事 preloadClasses(); preloadResources();
preloadClassess 將framework.jar裡的preloaded-classes 定義的所有class load到記憶體裡,preloaded-classes 編譯Android後可以在framework/base下找到。而preloadResources 將系統的Resource(不是在使用者apk裡定義的resource)load到記憶體。
資源preload到Zygoted的程序地址空間,所有fork的子程序將共享這份空間而無需重新load, 這大大減少了應用程式的啟動時間,但反過來增加了系統的啟動時間。通過對preload 類和資源數目進行調整可以加快系統啟動。
static void preload(TimingsTraceLog bootTimingsTraceLog) {
Log.d(TAG, "begin preload");
bootTimingsTraceLog.traceBegin("BeginIcuCachePinning");
beginIcuCachePinning();
bootTimingsTraceLog.traceEnd(); // BeginIcuCachePinning
bootTimingsTraceLog.traceBegin("PreloadClasses");
preloadClasses();
bootTimingsTraceLog.traceEnd(); // PreloadClasses
bootTimingsTraceLog.traceBegin("PreloadResources");
preloadResources();
bootTimingsTraceLog.traceEnd(); // PreloadResources
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs");
nativePreloadAppProcessHALs();
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
preloadOpenGL();
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
preloadSharedLibraries();
preloadTextResources();
// Ask the WebViewFactory to do any initialization that must run in the zygote process,
// for memory sharing purposes.
WebViewFactory.prepareWebViewInZygote();
endIcuCachePinning();
warmUpJcaProviders();
Log.d(TAG, "end preload");
sPreloadComplete = true;
}
上面我們總結了ZygoteInit會先註冊一個五福段的Scoket,我們來看下具體是如何註冊的。
void registerServerSocket(String socketName) {
if (mServerSocket == null) {
int fileDesc;
final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; //1
try {
String env = System.getenv(fullSocketName);
fileDesc = Integer.parseInt(env);
} catch (RuntimeException ex) {
throw new RuntimeException(fullSocketName + " unset or invalid", ex);
}
try {
FileDescriptor fd = new FileDescriptor();
fd.setInt$(fileDesc);
mServerSocket = new LocalServerSocket(fd);//2
} catch (IOException ex) {
throw new RuntimeException(
"Error binding to local socket '" + fileDesc + "'", ex);
}
}
}
註釋1處的fullSocketName是該scoket的全名,在註釋2處new了一個LocalServerSocket例項並傳入檔案描述符fd最後賦值給mServerSocket,它就是我們啟動的scoket。當Zygote程序將SystemServer程序啟動後, 就會在這個服務端的Socket上來等待ActivityManagerService請求Zygote程序來建立新的應用程式程序。
在建立完scoket之後便通過fork方式建立SystemServer程序,具體看main函式的註釋3。最後在註釋4處呼叫runSelectLoop函式。先看下注釋3處是如何建立SystemServer程序,具體是通過forkSystemServer方法
private static Runnable forkSystemServer(String abiList, String socketName,
ZygoteServer zygoteServer) {
/* Containers run without this capability, so avoid setting it in that case */
if (!SystemProperties.getBoolean(PROPERTY_RUNNING_IN_CONTAINER, false)) {
capabilities |= posixCapabilitiesAsBits(OsConstants.CAP_BLOCK_SUSPEND);
}
/* Hardcoded command line to start the system server
*/
String args[] = {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1032,3001,3002,3003,3006,3007,3009,3010",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server", //代表程序名
"--runtime-args",
"com.android.server.SystemServer", //啟動的類名
};
ZygoteConnection.Arguments parsedArgs = null;
int pid;
try {
parsedArgs = new ZygoteConnection.Arguments(args);
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);//1//將啟動的引數解析成parsedArgs
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
/* Request to fork the system server process
*/
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.debugFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);//2 fork System Server程序
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
/* For child process
*/
if (pid == 0) {//pid=0代表fork成功 並在子執行緒中啟動system server程序
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
zygoteServer.closeServerSocket();
return handleSystemServerProcess(parsedArgs);//3啟動system server程序 system server在這個函式進行初始化
}
return null;
}
首先是構造一個字串陣列來設定使用者和使用者組id然後在註釋1處將其解析成parsedArgs。在註釋2處通過fork建立SystemServer程序並傳入一些引數
啟動system server程序,程序的最終建立是通過native方法nativeForkSystemServer完成的。
最後在註釋3處啟動了system server程序。
接下來看下注釋4處的runSelectLoop作用 runSelectLoop函式如下所示
Runnable runSelectLoop(String abiList) {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
//mServerSocket 就是之前註冊的server scoket ,在這裡得到它的fd並放到fd列表中
fds.add(mServerSocket.getFileDescriptor());
peers.add(null);
while (true) {
//遍歷Fds列表把其中的資訊放到pollFds 中
StructPollfd[] pollFds = new StructPollfd[fds.size()];
for (int i = 0; i < pollFds.length; ++i) {
pollFds[i] = new StructPollfd();
pollFds[i].fd = fds.get(i);
pollFds[i].events = (short) POLLIN;
}
try {
Os.poll(pollFds, -1);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
for (int i = pollFds.length - 1; i >= 0; --i) {
if ((pollFds[i].revents & POLLIN) == 0) {
continue;
}
//i=0表示服務端Socket與客戶端連線上,也就是當前Zygote程序與ActivityManagerService建立了連線
if (i == 0) {
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
//將ZygoteConnection的fd新增到fds列表中 以便接受AMS傳送的建立應用的請求
fds.add(newPeer.getFileDesciptor());
} else {
//如果i>0表示有建立新應用程序的請求
try {
ZygoteConnection connection = peers.get(i);
//根據請求建立新的應用程序
final Runnable command = connection.processOneCommand(this);
if (mIsForkChild) {
// We're in the child. We should always have a command to run at this
// stage if processOneCommand hasn't called "exec".
if (command == null) {
throw new IllegalStateException("command == null");
}
return command;
} else {
// We're in the server - we should never have any commands to run.
if (command != null) {
throw new IllegalStateException("command != null");
}
// We don't know whether the remote side of the socket was closed or
// not until we attempt to read from it from processOneCommand. This shows up as
// a regular POLLIN event in our regular processing loop.
if (connection.isClosedByPeer()) {
connection.closeSocket();
peers.remove(i);
fds.remove(i);
}
}
} catch (Exception e) {
if (!mIsForkChild) {
// We're in the server so any exception here is one that has taken place
// pre-fork while processing commands or reading / writing from the
// control socket. Make a loud noise about any such exceptions so that
// we know exactly what failed and why.
Slog.e(TAG, "Exception executing zygote command: ", e);
// Make sure the socket is closed so that the other end knows immediately
// that something has gone wrong and doesn't time out waiting for a
// response.
ZygoteConnection conn = peers.remove(i);
conn.closeSocket();
fds.remove(i);
} else {
// We're in the child so any exception caught here has happened post
// fork and before we execute ActivityThread.main (or any other main()
// method). Log the details of the exception and bring down the process.
Log.e(TAG, "Caught post-fork exception in child process.", e);
throw e;
}
}
}
}
}
}
新程序的建立是通過processOneCommand完成的。下面貼下processOneCommand函式
Runnable processOneCommand(ZygoteServer zygoteServer) {
...
//建立應用程序
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
parsedArgs.appDataDir);
...
Zygote的forkAndSpecialize函式最終是通過native方法完成程序建立。
int pid = nativeForkAndSpecialize(
uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
fdsToIgnore, instructionSet, appDataDir);
好了,到此init和zygote程序的建立和啟動流程以及它過程中都做了哪些事我們都梳理了一遍。現在簡單總結下:
1、init程序
- 建立一些資料夾並掛載裝置
- 初始化和啟動屬性服務
- 解析init.rc配置檔案並啟動zygote程序
2、zygote程序 - 建立VM
- 建立並啟動system server程序
zygote啟動流程簡單總結:首先通過AndroidRunTime.start來啟動zygoteinit的main函式,在main函式中通過ZygoteServer註冊了scoket並通過runSelectLoop進行監聽AMS是否有啟動新程序的請求,如果有就通過native方法建立新程序。並且建立了systemserver程序。
system server程序的啟動
我們知道在zygote程序當中建立並啟動system server程序。那麼接下來我們一起看下system server程序啟動過程都幹了什麼。
我們知道system server程序的啟動是通過handleSystemServerProcess該函式位於com.android.internal.os.ZygoteInit
private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
...
if (parsedArgs.invokeWith != null) {
String[] args = parsedArgs.remainingArgs;
// If we have a non-null system server class path, we'll have to duplicate the
// existing arguments and append the classpath to it. ART will handle the classpath
// correctly when we exec a new process.
if (systemServerClasspath != null) {
String[] amendedArgs = new String[args.length + 2];
amendedArgs[0] = "-cp";
amendedArgs[1] = systemServerClasspath;
System.arraycopy(args, 0, amendedArgs, 2, args.length);
args = amendedArgs;
}
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion,
VMRuntime.getCurrentInstructionSet(), null, args);
throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
} else {
ClassLoader cl = null;
if (systemServerClasspath != null) {
cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
Thread.currentThread().setContextClassLoader(cl);
}
/*
* Pass the remaining arguments to SystemServer.
*/
return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);//1
}
}
可以看出最後呼叫ZygoteInit.zygoteInit
public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
RuntimeInit.redirectLogStreams();
RuntimeInit.commonInit();
ZygoteInit.nativeZygoteInit(); //呼叫native方法對Zygote進行init
return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader); //1
}
ZygoteInit.zygoteInit呼叫RuntimeInit.applicationInit
protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
ClassLoader classLoader) {
...
nativeSetExitWithoutCleanup(true);
// We want to be fairly aggressive about heap utilization, to avoid
// holding on to a lot of memory that isn't needed.
VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
final Arguments args = new Arguments(argv);
// The end of of the RuntimeInit event (see #zygoteInit).
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// Remaining arguments are passed to the start class's static main
return findStaticMain(args.startClass, args.startArgs, classLoader);//1
}
RuntimeInit.applicationInit又會呼叫findStaticMain方法
private static Runnable findStaticMain(String className, String[] argv,
ClassLoader classLoader) {
Class<?> cl;
try {
cl = Class.forName(className, true, classLoader);//1
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
"Missing class when invoking static main " + className,
ex);
}
Method m;
try {
m = cl.getMethod("main", new Class[] { String[].class });//2
} catch (NoSuchMethodException ex) {
throw new RuntimeException(
"Missing static main on " + className, ex);
} catch (SecurityException ex) {
throw new RuntimeException(
"Problem getting static main on " + className, ex);
}
int modifiers = m.getModifiers();//3
if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
throw new RuntimeException(
"Main method is not public and static on " + className);
}
/*
* This throw gets caught in ZygoteInit.main(), which responds
* by invoking the exception's run() method. This arrangement
* clears up all the stack frames that were required in setting
* up the process.
*/
return new MethodAndArgsCaller(m, argv);
}
註釋2處根據傳入的類名獲取Class物件,註釋2處則獲取該類的main方法。註釋3處獲取main方法的修飾判斷是否符合要求(必須是pubilc static)最終返回一個MethodAndArgsCaller物件。
它是RuntimeInit類的 一個靜態內部類並且實現了runnable介面。
static class MethodAndArgsCaller implements Runnable {
/** method to call */
private final Method mMethod;
/** argument array */
private final String[] mArgs;
public MethodAndArgsCaller(Method method, String[] args) {
mMethod = method;
mArgs = args;
}
public void run() {//1
try {
mMethod.invoke(null, new Object[] { mArgs });//2
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InvocationTargetException ex) {
Throwable cause = ex.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException(ex);
}
}
}
首先看下在它的run方法中的註釋2處執行了傳進來的main方法,這個其實就是SystemServer的main方法。那麼還剩一個問題是它的run函式(註釋1)在哪裡執行的呢? 你是否還記得ZygoteInit類的main函式中有如下呼叫
public static void main(String argv[]) {
...
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, socketName, zygoteServer);//建立SystemServer程序
// {@code r == null} in the parent (zygote) process, and {@code r != null} in the
// child (system_server) process.
if (r != null) {
r.run();//1
return;
}
}
我們看forkSystemServer返回了一個runnable物件然後如果它不為空就呼叫它的run方法。其實這個runnable就是一個MethodAndArgsCaller例項。由此我們就進入到了SystemServer類的main方法中。
/**
* The main entry point from zygote.
*/
public static void main(String[] args) {
new SystemServer().run();
}
該main方法非常簡單就是呼叫自身的run方法。
private void run() {
try {
//手機時間早於1970 會重新設定為1970
if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) {
Slog.w(TAG, "System clock is before 1970; setting to 1970.");
SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME);
}
...
// Prepare the main looper thread (this thread).
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_FOREGROUND);
android.os.Process.setCanSelfBackground(false);
Looper.prepareMainLooper();//1準備主執行緒looper
// Initialize native services.
System.loadLibrary("android_servers");//2初始化native相關服務
// Check whether we failed to shut down last time we tried.
// This call may not return.
performPendingShutdown();
// Initialize the system context.
createSystemContext();//3初始化系統context
// Create the system service manager.
mSystemServiceManager = new SystemServiceManager(mSystemContext);//4建立system service manager
mSystemServiceManager.setRuntimeRestarted(mRuntimeRestart);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
// Prepare the thread pool for init tasks that can be parallelized
SystemServerInitThreadPool.get();
} finally {
traceEnd(); // InitBeforeStartServices
}
// Start services.
try {
traceBeginAndSlog("StartServices");
//5啟動各種服務
startBootstrapServices();
startCoreServices();
startOtherServices();
SystemServerInitThreadPool.shutdown();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
} finally {
traceEnd();
}
...
// Loop forever.
Looper.loop();//6
throw new RuntimeException("Main thread loop unexpectedly exited");
run方法資訊量還是很大的,先看下注釋1處初始化主執行緒的looper,註釋2初始化了native相關服務,註釋3初始化system context,註釋4建立system service manager,註釋5則先後初始化了各種服務,註釋6呼叫了Looper.loop()。
註釋3處system context其實最終是返回contextimpl例項。註釋4處建立的SystemServiceManager主要是用在稍後初始化各種服務時用來啟動的服務的。
一個Service啟動需要的幾個步驟:
1. 初始化Service 物件,獲得IBinder物件。
2. 啟動後臺執行緒,並進入Loop等待。
3. 將自己註冊到Service Manager, 讓其他程序通過名字可以獲得遠端呼叫必須的IBinder的物件。
註釋5處先後啟動了多種系統服務,這些服務分為3類:引導服務、系統服務、其他服務。毫無疑問,這麼多服務之間是有依賴關係的,比如說,ActivityManager Service 在WindowManager Service 初始化完成之前是不能啟動應用的。那如何控制這些先後順序的?這裡由System server的啟動執行緒通過SystemReady()介面來完成。每個系統服務必須實現一個SystemReady() 介面,當被呼叫,表明系統已經OK, 該服務可以訪問(直接或間接)其他服務的資源。 最後一個被調到的服務就是AcitivyManager Service. AM的SystemReady()。
系統的啟動方式有兩種:
- 第一種是通過SystemServiceManager.startService啟動,下面以PowerManager啟動為例:
private void startBootstrapServices() {
mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);
}
public <T extends SystemService> T startService(Class<T> serviceClass) {
try {
try {
Constructor<T> constructor = serviceClass.getConstructor(Context.class);
service = constructor.newInstance(mContext);
} catch (InstantiationException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service must have a public constructor with a Context argument", ex);
} catch (NoSuchMethodException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service must have a public constructor with a Context argument", ex);
} catch (InvocationTargetException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service constructor threw an exception", ex);
}
startService(service);//1
return service;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
可以看出首先會用反射得到一個service例項然後呼叫同名startService方法
private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
public void startService(@NonNull final SystemService service) {
// Register it.
mServices.add(service);//1
// Start it.
long time = SystemClock.elapsedRealtime();
try {
service.onStart();//2
} catch (RuntimeException ex) {
throw new RuntimeException("Failed to start service " + service.getClass().getName()
+ ": onStart threw an exception", ex);
}
warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStart");
}
startService首先會把當前的service新增到一個名為mServices的list中
之後便呼叫service.onStart()來啟動service。
以上就是通過SystemServiceManager.startService啟動系統服務的過程,總結來說就是通過反射來構造service然後再呼叫其onStart()來啟動。
- 第二種啟動系統服務的方式是通過呼叫服務的main方法啟動即XXService.main(),以PackageManagerService為例來說明下
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
呼叫了PackageManagerService的main函式
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);//1
m.enableSystemUserPackages();
ServiceManager.addService("package", m);//2
final PackageManagerNative pmn = m.new PackageManagerNative();
ServiceManager.addService("package_native", pmn);
return m;
}
在註釋1處構造了一個 PackageManagerService例項,然後在註釋2處新增到ServiceManager啟動。
ServiceManager它是用來管理系統中各種Service的,由它管理的服務都實現了IBinder,所以在ServiceManager中註冊的服務是用於程序間通訊的:用於系統C/S架構中的Binder通訊機制。客戶端要使用某個Service,需要先到ServiceManager中查詢Service的資訊,然後根據該資訊與Service所在的Server程序建立通訊,這樣客戶端就可以使用Service了。
至此SystemServer的啟動流程完成,我們來簡單總結下:
1、zygote會通過forkSystemServe來建立SystemServer程序。
2、之後通過handleSystemServerProcess來啟動SystemServer程序。
3、啟動過程是通過反射呼叫SystemServer的main函式。
4、SystemServer的main函式會呼叫run方法,在run方法中初始化了主執行緒的looper、系統的context等,之後啟動了引導、核心、其他等三類系統服務。
Launcher的啟動
SystemServer的startOtherServices()方法的最後呼叫了AMS的systemReady
這個呼叫會啟動launcher程序。
launcher通俗講就是我們開機看到的桌面,它向我們展示安裝完成的app的圖示。並且當我們按下對應的圖示時啟動相應的app。好了 我們看下launcher啟動入口。
private void startOtherServices() {
...
mActivityManagerService.systemReady(() -> {//1
Slog.i(TAG, "Making services ready");
traceBeginAndSlog("StartActivityManagerReadyPhase");
mSystemServiceManager.startBootPhase(
SystemService.PHASE_ACTIVITY_MANAGER_READY);
traceEnd();
traceBeginAndSlog("StartObservingNativeCrashes");
try {
mActivityManagerService.startObservingNativeCrashes();
} catch (Throwable e) {
reportWtf("observing native crashes", e);
}
traceEnd();
//...
}, BOOT_TIMINGS_TRACE_LOG);
}
在startOtherServices中註釋1處呼叫ActivityManagerService(AMS)的systemReady方法。
public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
...
startHomeActivityLocked(currentUserId, "systemReady");
...
}
省略無關內容,我們看在systemReady呼叫startHomeActivityLocked方法。
boolean startHomeActivityLocked(int userId, String reason) {
if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
&& mTopAction == null) {
// We are running in factory test mode, but unable to find
// the factory test app, so just sit around displaying the
// error message and don't try to start anything.
return false;
}
Intent intent = getHomeIntent();//1
ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
if (aInfo != null) {
intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
// Don't do this if the home app is currently being
// instrumented.
aInfo = new ActivityInfo(aInfo);
aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
ProcessRecord app = getProcessRecordLocked(aInfo.processName,
aInfo.applicationInfo.uid, true);
if (app == null || app.instr == null) {
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid);
// For ANR debugging to verify if the user activity is the one that actually
// launched.
final String myReason = reason + ":" + userId + ":" + resolvedUserId;
mActivityStarter.startHomeActivityLocked(intent, aInfo, myReason);//2
}
} else {
Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
}
return true;
}
startHomeActivityLocked方法中在註釋1處呼叫
getHomeIntent,該方法返回一個intent例項,該例項是啟動launcher的intent。該intent的action和Category是匹配launcher的manifest的。
Intent getHomeIntent() {
Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);//mTopAction = Intent.ACTION_MAIN
intent.setComponent(mTopComponent);
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
intent.addCategory(Intent.CATEGORY_HOME);//該intent的Category是Intent.CATEGORY_HOME
}
return intent;
}
下面貼下manifest檔案
<application
android:backupAgent="com.android.launcher3.LauncherBackupAgent"
android:fullBackupOnly="true"
android:fullBackupContent="@xml/backupscheme"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher_home"
android:label="@string/derived_app_name"
android:theme="@style/LauncherTheme"
android:largeHeap="@bool/config_largeHeap"
android:restoreAnyVersion="true"
android:supportsRtl="true" >
<!--
Main launcher activity. When extending only change the name, and keep all the
attributes and intent filters the same
-->
<activity
android:name="com.android.launcher3.Launcher"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="nosensor"
android:configChanges="keyboard|keyboardHidden|navigation"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
<category android:name="android.intent.category.LAUNCHER_APP" />
</intent-filter>
</activity>
看出launcher的action和category是和上面的intent相符合的。
startHomeActivityLocked方法中的註釋2呼叫了startHomeActivityLocked方法把獲得的intent傳入來啟動launcher。
launcher啟動完成後我們就在看到了桌面程式,上面顯示了安裝完成的app的圖示。
我們在此簡單總結下launcher的啟動流程:
1、首先systemserver在啟動其他系統服務時會呼叫AMS的systemReady方法
2、systemReady方法中呼叫startHomeActivityLocked,在這個方法中我們會構造出啟動launcher的intent,然後通過startHomeActivityLocked去啟動launcher。
systemserver啟動launcher後會執行launcher的onCreate函式
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LauncherAppState app = LauncherAppState.getInstance(this);//1
// Load configuration-specific DeviceProfile
mDeviceProfile = app.getInvariantDeviceProfile().getDeviceProfile(this);
if (isInMultiWindowModeCompat()) {//2
Display display = getWindowManager().getDefaultDisplay();
Point mwSize = new Point();
display.getSize(mwSize);
mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
}
//3
mOrientation = getResources().getConfiguration().orientation;
mSharedPrefs = Utilities.getPrefs(this);
mIsSafeModeEnabled = getPackageManager().isSafeMode();
mModel = app.setLauncher(this);
mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout());
mIconCache = app.getIconCache();
mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
mDragController = new DragController(this);
mAllAppsController = new AllAppsTransitionController(this);
mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController);
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
mAppWidgetHost = new LauncherAppWidgetHost(this);
if (Utilities.ATLEAST_MARSHMALLOW) {
mAppWidgetHost.addProviderChangeListener(this);
}
mAppWidgetHost.startListening();
// If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
// this also ensures that any synchronous binding below doesn't re-trigger another
// LauncherModel load.
mPaused = false;
mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
setupViews();//4
mDeviceProfile.layout(this, false /* notifyListeners */);
loadExtractedColorsAndColorItems();//5
mPopupDataProvider = new PopupDataProvider(this);
((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
.addAccessibilityStateChangeListener(this);
lockAllApps();
restoreState(savedInstanceState);
if (LauncherAppState.PROFILE_STARTUP) {
Trace.endSection();
}
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
int currentScreen = PagedView.INVALID_RESTORE_PAGE;
if (savedInstanceState != null) {
currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
}
if (!mModel.startLoader(currentScreen)) {//6
// If we are not binding synchronously, show a fade in animation when
// the first page bind completes.
mDragLayer.setAlpha(0);
} else {
// Pages bound synchronously.
mWorkspace.setCurrentPage(currentScreen);
setWorkspaceLoading(true);
}
// For handling default keys
//7
mDefaultKeySsb = new SpannableStringBuilder();
Selection.setSelection(mDefaultKeySsb, 0);
mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
// In case we are on a device with locked rotation, we should look at preferences to check
// if the user has specifically allowed rotation.
if (!mRotationEnabled) {
mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
mRotationPrefChangeHandler = new RotationPrefChangeHandler();
mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
}
if (PinItemDragListener.handleDragRequest(this, getIntent())) {
// Temporarily enable the rotation
mRotationEnabled = true;
}
// On large interfaces, or on devices that a user has specifically enabled screen rotation,
// we want the screen to auto-rotate based on the current orientation
setOrientation();
setContentView(mLauncherView);
// Listen for broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
registerReceiver(mReceiver, filter);
mShouldFadeInScrim = true;
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onCreate(savedInstanceState);
}
}
整個launcher的啟動大致分為7步。
註釋1處執行第一步建立LauncherAppState 物件。不同的手機顯示的Launcher佈局是一樣的,但是其中真正顯示的每個圖示,
每個畫面的畫素點大小是不同的。Launcher需要根據手機的尺寸密度等引數,計算出更多的資訊。第一步是將和手機硬體掛鉤的引數都獲取出來。
註釋2處執行第二步,分屏模式也叫做多屏模式,在多屏模式的時候,Launcher的佈局有很多的變化。此處檢查當前是否處於分屏模式,若是則會做相應的處理。
註釋3執行第三步統一建立物件,Launcher啟動時需要用到的物件,在這裡統一建立,為後面進行佈局的顯示進行鋪墊。
註釋4執行第四步生成桌面分佈局,將桌面的各個部分都建立物件,繫結一些事件監聽器等,這一步基本將桌面的各個UI子模組都定義完成。
註釋5執行第五步,UI子模組的細節規劃,各個模組的大小,真正的尺寸等等。這一步是採用第一步獲取的方案,把第四步的模組細節進行完成
註釋6執行第六步,生成佈局。Launcher不是一張圖片,因為不同的手機之間有區別。前五步完成不同手機的區別, 保證上至平板,下至翻蓋機,不同的解析度下都能夠很好的顯示。而手機桌面的變化重點是桌面圖示佈局不一樣,手機中安裝的軟體不一樣。第六步就是生成這種佈局。
註釋7第七步,橫屏和CallBack等善後工作
至此launcher啟動完成我們已經可以在桌面看到安裝的app的icon,點選icon就會啟動相應的應用。
其實在launcher內部我們看到的圖示排列是通過Android自定義的recycleview實現的,當我們點選icon時會觸發onclicklistener監聽,而對點選事件的處理則是在launcher的onclick函式中(launcher實現了onclicklistener介面),launcher收到點選事件後會呼叫startAppShortcutOrInfoActivity啟動對應的應用。
至此Android從啟動到桌面顯示,然後點選icon啟動app的流程已經梳理完成