Android應用啟動流程分析

天才木木木木發表於2020-04-06

1 前言

網上看過很多Activity啟動過程的原始碼解析,很多文章會貼上一大段程式碼,然後從startActivity()函式開始深究整個原始碼的呼叫棧。個人感覺這類文章程式碼細節太多,反而容易迷失在原始碼呼叫之中,從而忽略了Activity啟動過程的本質。所以本文就簡單地定性地對Activity啟動過程進行描述,不會貼上大篇幅的原始碼,同時梳理一下相關的經典問題。也是對以前的所學做一個複習總結。

2 冷啟動與熱啟動

Activity啟動過程中,一般會牽涉到應用啟動的流程。應用啟動又分為冷啟動和熱啟動。

  1. 冷啟動:點選桌面圖示,手機系統不存在該應用程式,這時系統會重新fork一個子程式來載入Application並啟動Activity,這個啟動方式就是冷啟動。
  2. 熱啟動:應用的熱啟動比冷啟動簡單得多,開銷也更低。在熱啟動中,因為系統裡已有該應用的程式,所以系統的所有工作就是將您的 Activity 帶到前臺。 冷啟動是應用完全從0開始啟動,涉及到更多的內容,所以就應用冷啟動的過程展開討論。

3 應用啟動流程

一般來說,冷啟動包括了以下內容:

  1. 啟動程式 點選圖示發生在Launcher應用的程式,startActivity()函式最終是由Instrumentation通過Android的Binder跨程式通訊機制 傳送訊息給 system_server 程式; 在 system_server 中,啟動程式的操作由ActivityManagerService 通過 socket 通訊告知 Zygote 程式 fork 子程式(app程式)
  2. 開啟主執行緒 app 程式啟動後,首先是例項化 ActivityThread,並執行其main()函式:建立 ApplicationThread,Looper,Handler 物件,並開啟主執行緒訊息迴圈Looper.loop()
  3. 建立並初始化 Application和Activity ActivityThread的main()呼叫 ActivityThread#attach(false)方法進行 Binder 通訊,通知system_server程式執行 ActivityManagerService#attachApplication(mAppThread)方法,用於初始化Application和Activity。 在system_server程式中,ActivityManagerService#attachApplication(mAppThread)裡依次初始化了Application和Activity,分別有2個關鍵函式: - thread#bindApplication()方法通知主執行緒Handler 建立 Application 物件、繫結 Context 、執行 Application#onCreate() 生命週期 - mStackSupervisor#attachApplicationLocked()方法中呼叫 ActivityThread#ApplicationThread#scheduleLaunchActivity()方法,進而通過主執行緒Handler訊息通知建立 Activity 物件,然後再呼叫 mInstrumentation#callActivityOnCreate()執行 Activity#onCreate() 生命週期
  4. 佈局&繪製 原始碼流程可以參考Android View 的繪製流程分析及其原始碼呼叫追蹤

至此,應用啟動流程完成。

其中1、2、3的原始碼流程可以參考Android Application 啟動流程分析及其原始碼呼叫探究,但程式碼細節不是本篇重點。

下面說說上述流程中的幾個關鍵角色,以及其作用:

3.1 zygote程式

這裡稍微說下Android系統下的程式機制,每個應用執行時都是:

  1. 一個單獨的dalvik虛擬機器(DVM) java程式碼在編譯後需要執行在JVM上,同樣android中使用了java語言,也需要執行在一個VM上。所以谷歌針對手機處理器和記憶體等硬體資源不足而研究出來DVM,為android執行提供環境。 參考JVM與DVM的關係
  2. 一個單獨的程式 每個應用啟動都執行一個單獨的DVM,每個DVM單獨佔用一個Linux程式。獨立的程式可以防止在虛擬機器崩潰的時候所有程式都被關閉。 dalvik程式管理是依賴於linux的程式體系結構的,如要為應用程式建立一個程式,它會使用linux的fork機制來複制一個程式。

眾所周知,Android是基於Linux系統的,在Linux中所有的程式都是由init程式直接或者是間接fork出來的。fork程式往往比建立程式效率更高。在Android中,所有的應用的程式都是由zygote程式fork出來的。

提到zygote程式,就不得不介紹下Android開機流程:

  1. Android手機開機Linux核心啟動後,會載入system/core/init/init.rc檔案,啟動init程式。這個程式是Android系統特有的初始化程式,簡單介紹下它的工作:

    • 各種複雜工作
    • 負責開關機畫面
    • 檔案系統的建立和掛載
    • 啟動Zygote(孵化器)程式
    • 啟動ServiceManager,它是Binder服務管理器,管理所有Android系統服務
  2. 在系統啟動後init程式會fork Zygote程式,Zygote作為孵化器程式,它的main函式會建立好自己的環境準備孵化子程式,並開始等待孵化請求:

    • 建立一個server端的socket, name為zynote,用於和客戶端程式通訊
    • 預載入類和資源,提高應用啟動速度
    • 啟動SystemServer程式
    • 監聽socket,當有一個應用程式啟動時,就會向它發出請求,然後zygote程式fock自己來建立的一個新的子程式。
  3. Zygote程式首先會fork自己孵化出SystemServer程式,它的main函式主要負責:

    • 啟動binder執行緒池,這是SystemServer與其他程式通訊的基礎
    • 初始化Looper
    • 建立了SystemServiceManager物件,它會啟動Android中的各種服務。包括AMS、PMS、WMS
    • 啟動桌面程式,這樣才能讓使用者見到手機的介面。
    • 開啟loop迴圈,開啟訊息迴圈,SystemServer程式一直執行,保障其他應用程式的正常執行。
  4. 當系統裡面的zygote程式執行之後,後續啟動APP,就相當於開啟一個新的程式。而Android為了實現資源共用和更快的啟動速度,子程式都是通過zygote程式fork出來的。所以說,除了init程式fork出來的第一個程式zygote,其他應用程式都是zygote的子程式,也不難理解為何這個孵化器程式的英文名叫zygote(受精卵),因為所有的應用程式都是由它孵化誕生。

3.2 SystemServer程式

SystemServer是由zygote程式fork出來的第一個程式,SystemServer和Zygote是Android Framework最重要的2個程式。 系統裡面重要的服務都是在這個程式裡面開啟的,比如ActivityManagerService、PackageManagerService、WindowManagerService。

應用啟動流程基本是圍繞著ActivityManagerService和ActivityThread展開。

3.3 Android系統裡的Client/Server模式

平時我們所熟知的前端(Web\Android\iOS)通過網路與伺服器通訊是客戶端-服務端模式的體現,而在Android Framework中,四大元件的建立、生命週期也是通過這樣的模式進行通訊:

  1. 伺服器端(server)指的就是SystemServer程式,這個程式提供了很多服務 比如AMS、PMS、WMS等等,所有的App程式都可以與其通訊。
  2. 客戶端(client)指的就是各個獨立的App程式。

Android開發者都應該知道,通過包名和Activity類名就可以開啟一個APP。實際上,專案裡的業務程式碼startActivity()方法並不是直接建立程式、拉起APP的。而是通過一系列的呼叫,把請求傳遞給SystemServer的AMS。AMS收到來自客戶端的請求後,再通知zygote程式來fork一個新程式,來開啟我們的目標App的。 這就像是在瀏覽器裡開啟一個網頁,瀏覽器把url和引數傳送到伺服器,然後還是伺服器處理請求,並返回相應的html並展示在瀏覽器上。

這個過程涉及到3個程式:App程式、AMS(SystemServer程式)、zygote程式。

  1. App程式與AMS通過Binder機制進行跨程式通訊
  2. AMS(SystemServer程式)與zygote通過Socket進行跨程式通訊。

在Android系統中,任何一個Activity的啟動都是由AMS和App程式(主要是ActivityThread)相互配合來完成的。AMS服務統一排程系統中所有程式的Activity啟動,而每個Activity的啟動過程則由其所屬的程式具體來完成。

3.4 Android Binder機制

我們知道AMS與ActivityThread的互動主要是通過程式間通訊 (IPC) 。跨程式通訊的機制就是將方法呼叫及其資料分解至作業系統可識別的程度,並將其從本地程式和地址空間傳輸至遠端程式和地址空間,然後在遠端程式中重新組裝並執行該呼叫。 Android 提供了執行這些 IPC 事務的方案——Binder機制,因此我們只需關心介面定義和實現 RPC 程式設計介面。

而App程式與SystemServer程式也是通過Binder機制進行程式間通訊,Android為此設計了兩個Binder介面:

  1. IApplicationThread: 作為系統程式請求應用程式的介面;
  2. IActivityManager: 作為應用程式請求系統程式的介面。

對於一個Binder介面,在客戶端和服務端各有一個實現:Proxy和Native,它們之間的通訊主要是通過transact和onTransact觸發。一般從命名上可以區分:xxxNative是在本程式內的Binder代理類,xxxProxy是在對方程式的Binder代理類。

多說一句,這些Binder都由ServiceManager統一管理:

  1. ServiceManager管理所有的Android系統服務,有人把ServiceManager比喻成Binder機制中的DNS伺服器,client端應用如果要使用系統服務,呼叫getSystemService介面,ServiceManager就會通過字串形式的Binder名稱找到並返回對應的服務的Binder物件。
  2. 它是一個系統服務程式,在native層啟動,它在system/core/rootdir/init.rc指令碼中描述並由init程式啟動。
  3. ServiceManager啟動後,會通過迴圈等待來處理Client程式的通訊請求。

App程式與SystemServer程式的Binder介面如下圖:

出處見水印

3.4.1 服務端 IActivityManager——ActivityManagerNative——ActivityManagerService

  1. ActivityManagerNative作為服務端的“樁(Stub)”,其主要職責就是對遠端傳遞過來的資料進行”反序列化(unparcel)”;
  2. ActivityManagerProxy作為服務的“代理(Proxy)”,執行在客戶端,其主要職責就是將資料進行“序列化(parcel)”,再傳遞給遠端的“樁(Stub)”,App使用AMS提供的功能,比如startActivity,是通過AMS在客戶端的代理ActivityManagerProxy發起的。
  3. 最下面一層是樁(Stub)的具體實現——AMS(ActivityManagerService),負責系統中四大元件的啟動、切換、排程及應用程式的管理和排程等工作,非常複雜。

AMS是一個系統服務,在SystemServer程式啟動後完成初始化。在應用啟動流程中,充當著服務端的角色。 App中Activity的生命週期由AMS管理,它決定著什麼時候該呼叫onCreate、onResume這些生命週期函式,把Activity放在哪個棧裡,上下文之間的關係是怎樣的等等。

比如:

  1. startActivity 最終呼叫了AMS的 startActivity 系列方法,實現了Activity的啟動;Activity的生命週期回撥,也在AMS中完成;
  2. startService,bindService 最終呼叫到AMS的startService和bindService方法;
  3. 動態廣播的註冊和接收在 AMS 中完成(靜態廣播在 PMS 中完成)
  4. getContentResolver 最終從 AMS 的 getContentProvider 獲取到ContentProvider

3.4.2 客戶端 IApplicationThread——ApplicationThreadNative——ActivityThread

  1. 樁(Stub):ApplicationThreadNative
  2. 代理(Proxy):ApplicationThreadProxy,App在客戶端程式中實現了例項化Activity、呼叫onCreate等生命週期函式的功能,因為跨程式也不能被AMS直接呼叫,而是AMS通過客戶端的代理ApplicationThreadProxy來處理。
  3. 最下面一層是樁(Stub)的具體實現——ApplicationThread,它是ActivityThread的一個內部類,ApplicationThread負責響應系統程式發起的請求,而實際觸發的業務邏輯是在ActivityThread中。與一般的代理模式不同,它不是直接持有ActivityThead的一個引用,而是把處理的請求發到ActivityThread內部的一個Handler上。

和服務端的AMS相對應,ActivityThread在應用啟動的Client/Server模式中,是作為客戶端那一邊的具體實現。它並不是一個執行緒,但它包含了一個應用程式的主執行緒運作的全部機制:

  1. 啟動應用的主執行緒,並開啟訊息迴圈
  2. 提供了一個IActivityThread介面作為與AMS的通訊介面,通過該介面AMS可以將Activity的狀態變化傳遞到客戶端的Activity物件

3.5 啟動一個Activity的通訊過程

我們已經知道應用程式建立以後,App程式的ActivityThread與SystemServer程式的AMS通過Binder進行通訊。 在文章前面【2 應用啟動流程】提到,在ActivityThread的main方法中,呼叫 ActivityThread#attach(false)方法進行 Binder 通訊,通知system_server程式執行 ActivityManagerService#attachApplication(mAppThread)方法,用於初始化Application和Activity。

可以結合原始碼流程,稍微總結一下這個通訊過程。

3.5.1 Application的初始化

從應用程式到系統程式

在ActivityThread建立的時候,會將自己的ApplicationThread繫結到AMS中:

ActivityThread.main()
└── ActivityThread.attach()
    └── IActivityManager.attachApplication(mAppThread)
        └── Binder.transact()
複製程式碼

應用程式作為客戶端,通過IAcitivtyManager介面發起了跨程式呼叫,跨程式傳遞的引數mAppThread就是IApplicationThread的例項,執行流程從應用程式進入到系統程式:

ActivityManagerService.onTransact()
└── ActivityManagerService.attachApplication(IApplicationThread thread)
複製程式碼

AMS作為IActivityManager介面的服務端實現,會響應客戶端的請求,最終AMS.attachApplication()函式會被執行,該函式接收跨程式傳遞過來的IApplicationThread例項,將存在系統程式維護的ProcessRecord中。 具體細節可以看AMS的原始碼,我們只需要知道AMS中維護了所有程式執行時的資訊(ProcessRecord),一旦發生了應用程式的繫結請求,ProcessRecord.thread就被賦值成應用程式的IApplicationThread例項,這樣一來,在AMS中就能通過該IApplicationThread例項發起嚮應用程式的呼叫

從系統程式到應用程式

在AMS.attachApplication()的過程中,會有一些資訊要傳遞給應用程式,以便應用程式的初始化,系統程式會發起如下函式呼叫:

ActivityManagerService.attachApplication()
└── ActivityManagerService.attachApplicationLocked()
    └── IApplicationThread.bindApplication(processName, appInfo ...)
        └── Binder.transact()
複製程式碼

此時,AMS會反轉角色,即系統程式作為客戶端,通過IApplicationThread介面嚮應用程式發起呼叫。

  1. AMS通過ProcessRecord來維護程式執行時的狀態資訊,需要將應用程式繫結到ProcessRecord才能開始一個Application的構建;
  2. AMS維護的ProcessRecord這個資料結構,包含了程式執行時的資訊,譬如應用程式的名稱processName、解析AndroidManifest.xml得到的資料結構ApplicationInfo等,其中,要傳遞給應用程式的資料都是Parcelable型別的例項。

應用程式響應請求的呼叫關係如下所示:

ApplicationThread.onTransact()
└── ApplicationThread.bindApplication()
    └── ActivityThread.H.handleMessage(BIND_APPLICATION)
        └── ActivityThread.handleBindApplication()
            └── Application.onCreate()
複製程式碼

ApplicationThread作為IApplicationThread介面的服務端實現,執行在應用程式中,然後ApplicationThread.bindApplication()會被執行,完成一些簡單的資料封裝(AppBindData)後,通過Handler丟擲BIND_APPLICATION訊息。這一拋,就拋到了主執行緒上,ActivityThread.handleBindApplication()會被執行,終於建立了Application 物件,然後呼叫 Application#attach(context) 來繫結 Context,並呼叫Application.onCreate()函式。歷經應用程式和系統程式之間的一個來回,總算是建立了一個應用程式。

Android原始碼裡有較統一的函式命名方式,在AMS中與Activity管理相關很多函式都會帶有Locked字尾,表示這些函式的呼叫都需要多執行緒同步操作(synchronized),它們會讀/寫一些多執行緒共享的資料

3.5.2 Activity的初始化

前面提到在system_server程式中,ActivityManagerService#attachApplication(mAppThread)裡依次初始化了Application和Activity,其中的mStackSupervisor#attachApplicationLocked(ProcessRecord)裡進行了Activity的初始化。

  1. AMS通過ActivityRecord來維護Activity執行時的狀態資訊,需要將Activity繫結到AMS中的ActivityRecord能開始Activity的生命週期。
  2. 在Activity類中有一個IBinder型別的屬性:private IBinder mToken;,IBinder型別表示這個屬性是一個遠端物件的引用,Token持有了一個ActivityRecord例項的弱引用。在建立一個ActivityRecord的時候,就會建立了一個Token型別的物件。

在啟動一個新的Activity時,AMS會將ActivityRecord的Token傳遞給應用程式,呼叫關係如下所示:

ActivityStackSupervisor.realStartActivityLocked(ActivityRecord, ...)
└── IApplicationThread.scheduleLaunchActivity(...token, ...)
    // 將ActivityRecord的Token跨程式傳遞給應用程式
    └── Binder.transact()
複製程式碼

ActivityStackSupervisor.realStartActivityLocked()表示要啟動一個Activity例項,ActivityRecord作為引數。從ActivityRecord中提取出Token物件,作為跨程式呼叫的引數,通過IApplicationThread.scheduleLaunchActivity()傳遞到應用程式。

在應用程式這一側,會收到啟動Activity的跨程式呼叫,觸發以下函式的呼叫:

ApplicationThread.onTransact()
└── ApplicationThread.scheduleLaunchActivity(...token, ...)
    // token將被封裝進ActivityClientRecord這個資料結構中
    └── ActivityThread.H.handleMessage()
        └── ActivityThread.handleLaunchActivity(LAUNCH_ACTIVITY)
            └── ActivityThread.performLaunchActivity(ActivityClientRecord, ...)
                // 從ActivityRecord取出token
                └── Activity.attch(...token, ...)
複製程式碼

標準的Binder服務端處理流程,收到AMS傳遞過來的Token物件,進行一下資料封裝(ActivityClientRecord),然後通過Handler丟擲一個LAUNCH_ACTIVITY訊息。這個訊息顯然也是拋到了應用程式的主執行緒去執行,所以ActivityThread.performLaunchActivity()函式會在主執行緒上執行,該函式從封裝的資料結構ActivityClientRecord中取出Token物件,呼叫Activity.attach()函式,將其繫結到Activity上,如此一來,就建立應用程式的Activity與系統程式中ActivityRecord的關聯。

系統程式維護的是ActivityRecord,應用程式維護的是Activity,兩者之間的對映關係就是利用Token來維繫的。應用程式的Activity在建立的時候,就被賦予了一個Token,拿著這個Token才能完成後續與系統程式的通訊。在發生Activity切換時,應用程式會將上一個Activity的Token(AMS.startActivity()的輸入引數resultTo)傳遞給系統程式,系統程式會根據這個Token找到ActivityRecord,對其完成排程後,再通知應用程式:Activity狀態發生了變化。

3.5.3 Instrumentation

每個Activity都持有Instrumentation物件的一個引用,但是整個應用程式程式只會存在一個Instrumentation物件。 Instrumentation可以理解為應用程式的管家,ActivityThread要建立或暫停某個Activity時,都需要通過Instrumentation來進行具體的操作。

Instrumentation這個類就是完成對Application和Activity初始化和生命週期的工具類。

前面提到,App和AMS是通過Binder傳遞資訊的,ActivityThread接受AMS的命令,然後就是通過Instrumentation真正地建立Activity以及呼叫Activity的生命週期函式。 比如ApplicationThread接受AMS命令建立Acitivity,最後執行到ActivityThread,通過Instrumentation建立Activity並呼叫onCreate()生命週期。

//
ActivityThread.performLaunchActivity()
└──  mInstrumentation.newActivity(appContext.getClassLoader(), component.getClassName(), activityClientRecord.intent)
	└── return (Activity)cl.loadClass(className).newInstance()
		
└── mInstrumentation.callActivityOnCreate(activity, r.state)
	└── activity.performCreate()
		└── activity.onCreate(Bundle)
複製程式碼

4 Activity的管理方式

前面知道應用啟動以及Activity啟動是一個跨程式通訊的過程,這是因為: 每個應用都是一個獨立的程式,Activity的生命週期本來就會在不同程式之間互相影響,所以需要一個系統程式對所有Activity進行統一管理。 在一個應用程式安裝時,系統會解析出APK中所有Activity的資訊,當顯示APK中的使用者介面時,就需要排程Activity的生命週期函式了。 系統程式(SystemServer)中維護了所有Activity的狀態,管理中樞就是ActivityManagerService。

系統程式怎麼管理Activity?

在應用程式這邊,Activity的生命週期都是發生在本程式的主執行緒裡,由ActivityThread呼叫Instrumentation完成。 而在系統程式(SystemServer)這頭,則是由AMS維護ActivityRecord等資料結構來進行排程。 Activity管理的資料結構包括: ActivityRecord TaskRecord ActivityStack ActivityDisplay ActivityStackSupervisor ProcessRecord 這些資料結構都屬於JAVA實體類,它們的構建和銷燬都在系統程式中完成。

它們之間的聯絡可以參考下圖:

出處見水印

圖中的方框可以理解為一個包含關係:譬如一個TaskRecord中包含多個ActivityRecord; 圖中的連線線可以理解為等價關係,譬如同一個ActivityRecord會被TaskRecord和ProcessRecord引用,兩者是從不同維度來管理ActivityRecord。

  1. ActivityRecord是Activity管理的最小單位,它對應著應用程式的一個Activity;
  2. TaskRecord也是一個棧式管理結構,每一個TaskRecord都可能存在一個或多個ActivityRecord,棧頂的ActivityRecord表示當前可見的介面;
  3. ActivityStack是一個棧式管理結構,每一個ActivityStack都可能存在一個或多個TaskRecord,棧頂的TaskRecord表示當前可見的任務;
  4. ActivityStackSupervisor管理著多個ActivityStack,但當前只會有一個獲取焦點(Focused)的ActivityStack;
  5. ProcessRecord記錄著屬於一個程式的所有ActivityRecord,執行在不同TaskRecord中的ActivityRecord可能是屬於同一個 ProcessRecord。

每種資料結構的屬性和行為

5 Binder運作機制

前面知道應用程式與AMS通過Binder進行了跨程式通訊,那麼Binder究竟是如何運作的:

  1. 一個程式空間分為 使用者空間 & 核心空間(Kernel),即把程式內 使用者 & 核心 隔離開來。
  2. 為了保證 安全性 & 獨立性,一個程式 不能直接操作或者訪問另一個程式,即Android的程式是相互獨立、隔離的。
  3. 跨程式間通訊的原理
    • 先通過 程式間 的核心空間進行 資料互動
    • 再通過 程式內 的使用者空間 & 核心空間進行 資料互動,從而實現 程式間的使用者空間 的資料互動
    • Binder,就是充當 連線 兩個程式(核心空間)的通道。

下面簡單說說Binder機制 在Android中的具體實現:

5.1 註冊服務

Server程式 建立 一個 Binder 物件,並向 ServiceManager 註冊服務。

5.2 獲取服務

Client程式 需要使用 Server程式的服務時,須 通過Binder驅動 向 ServiceManager程式 獲取相應的Service資訊。ServiceManager會返回Server程式的Binder代理物件

5.3 使用服務

  1. client程式的請求資料通過代理binder物件的transact方法,傳送到核心空間(Binder驅動?),當前執行緒被掛起
  2. Binder驅動通過client程式所使用的代理物件找到 對應server程式的真身binder物件,並將資料傳送到server程式
  3. server程式收到binder驅動通知,線上程池中進行資料反序列化&呼叫目標方法(onTransact),並將執行結果傳送到Binder驅動(核心空間?)
  4. Binder驅動將server程式的目標方法執行結果,拷貝到client程式的核心空間
  5. Binder驅動通知client程式,之前掛起的執行緒被喚醒,並收到返回結果

6 總結

  1. 在Android中,所有的應用都是一個獨立的程式。
  2. 每個應用程式都是由zygote程式fork出來的。
  3. 應用啟動是一個跨程式的複雜工作,應用啟動流程基本是圍繞著SystemServer的ActivityManagerService和應用程式的ActivityThread展開。

參考

  1. Android原始碼學習目錄
  2. 應用程式與系統程式的通訊(IActivityManager & IApplicationThread)
  3. 圖文詳解 Android Binder跨程式通訊機制 原理

相關文章