概述安卓App的安裝和啟動

Android的安裝和啟動比較特別,很多機制和直觀感受并不一樣,如果這里出現誤解,就很難透徹理解App的運行,這里把過去積累的問題統一梳理了一下。

安裝

我們知道,Android的安裝包Apk其實就是個資源和組件的容器壓縮包,安裝的過程主要是復制和解析的過程,這個過程大概分這樣幾步:

一、復制

安卓的程序目錄是/data/app/,所以安裝的第一步就是把apk文件復制到這個目錄下。這里有四個問題:

  1. 安卓機有內部存儲和SD卡兩部分,很多安卓機的內存并不大,需要把apk安裝到SD卡上節省內存空間,所以程序目錄/data/app/實際上也是在內部存儲和SD卡上各一個。
  2. 系統自帶的App是安裝在/system/app/目錄下的,這個目錄只有root權限才能訪問,所以系統App在root之前是無法刪除和修改的,也就是說,系統App升級時,實際上是在/data/app/里重新安裝了一個App,這個路徑會重新注冊到系統那里,系統再打開App時,就會指向新App的地址。當然,這個新的App是可以卸載的,不過新的App卸載后,系統會把 /system/app/里那個舊的App提供給你,所以是卸掉新的,還你舊的。
  3. 還是系統App,在root后,我們可以操作/system/app/目錄,但是系統安裝Apk仍然會裝到/data/app/里,所以如果想修改/system/app/目錄里的app,必須自己手動push新的apk文件進去,這個新的apk文件不會自動被安裝,需要重啟設備,系統在重啟時檢查到apk被更新,才會去安裝apk。
  4. 系統目錄有個/system/priv-app/目錄,這里面放的是權限更高的系統核心應用,如開機launcher、系統UI、系統設置等,這個目錄我們最好不要動,保持系統干凈簡潔。

二、安裝

安卓系統開機啟動時,會啟動一個超級管理服務SystemServer,這個SystemServer會啟動所有的系統核心服務,其中就包括PackageManagerService,簡稱PMS,具體的apk安裝過程,就是由這個PMS操作的。
PMS會監控/data/app/這個目錄,在上一步中,系統安裝程序向這個目錄復制了一個apk,PMS自己就會定期掃描這個目錄,找到后綴為apk的文件,如果這個apk沒有被安裝過,它就會自動開始安裝,安裝時會做這么幾件事:

  1. 創建應用目錄,路徑為/data/data/your package(你的應用包名),App中使用的數據庫、so庫、xml文件等,都會放在這個目錄下。
  2. 提取dex文件,dex是App的可執行文件,系統解壓apk就能得到dex文件,然后把dex文件放到/data/dalvik-cache,這樣可以提前緩存dex到內存中,能加快啟動速度。系統還會把dex優化為odex,進一步加快啟動速度。
  3. 判斷是否可以安裝apk,如檢查apk簽名等。
  4. 為應用分配并保存一個UID,UID是來自Linux的用戶賬戶體系,不過在Android這種單用戶系統里,UID被用來與App對應,這也是安全機制的一部分,每個App都有自己對應的UID,這種對應關系是持久化保存的,App更新或卸載重裝后,系統還會給它分配原來那個UID。用adb pull /data/system/packages.list可以查看所有App的UID。GID(用戶組)一般等于UID。
  5. 利用AndroidManifest文件,注冊Apk的各項信息,包括但不限于:
    . 根據installLocation屬性(internalOnly、auto、preferExternal),選擇安裝在內部存儲器還是SD卡上。
    . 根據sharedUserId屬性,為App分配UID,如果兩個App使用同一個UID,打包時又使用了相同的簽名,它們就被視為同一個用戶,可以共享數據,甚至運行在同一個進程上。
    . 向/data/system/packages.xml文件中,記錄App的包名、權限、版本號、安裝路徑等;同時在/data/system/packages.list中,更新所有已安裝的app列表。
    . 注冊App中的的四大組件(Activity、Service、Broadcast Receiver和Content Provider),包括組件的intent-filter和permission等。
    . 在桌面上添加App的快捷方式,如果AndroidManifest文件中有多個Activity被標注為<action android:name="android.intent.action.MAIN" />和
    <category android:name="android.intent.category.LAUNCHER" />,系統就會向桌面添加多個App快捷方式,所以有時候在安裝一個App后,用戶可能會感覺安裝了多個App。

三、通知

apk安裝完成后,PMS會發一個ACTION_PACKAGE_ADDED廣播,如果是卸載,會發ACTION_PACKAGE_REMOVED廣播。
整個安裝過程大概是這樣的:


App安裝過程

啟動

啟動一個App,首先需要觸發啟動過程,然后分配系統資源,最后才啟動要打開的App組件。

一、觸發啟動過程

在安卓系統開機啟動時,啟動的超級管理服務SystemServer會啟動所有的系統核心服務,其中就包括ActivityManagerService,簡稱AMS,啟動App具體都是AMS來負責的。
不過,一般Java程序都有個main函數入口,啟動Java程序其實就是執行main函數去了。但是,安卓App不是這樣設計的,App并沒有統一的程序入口,一個App其實更像是一群組件的集合,啟動App其實就是啟動了某個組件,即便是從桌面點擊應用圖標打開某個App,也是系統桌面Home根據安裝時注冊的組件信息,找到這個圖標對應的Activity信息,再由AMS去啟動Activity組件。


觸發啟動過程

二、分配系統資源

在安卓系統里,除非人為設置為多進程(Activity的android:process屬性),否則默認每個App都有1個獨立的進程和虛擬機,所以在系統啟動時,系統會建立一個Linux進程(Process),在這個進程里放一個虛擬機VM,在這個VM里,運行你的App。
在系統層面,它其實要做這么幾件事:

  1. 分配UID,App要有UID才能有自己的系統資源,UID是在安裝App時由系統分配的,一般每個App都有自己的UID,App的資源不能共享,因為它們不屬于同一個用戶。
  2. 分配進程Process,系統會給App一個進程,每個App的都有自己的進程,進程的PID是系統即時生成的,用完銷毀。
    如果要讓兩個App共用進程,除了需要設置同一個進程(android:process),還需要分配同一個UID(android:sharedUserId)來共享系統資源,使用同一個應用簽名(同一個簽名證書才可以視為同一個程序)。
    有時候,如果某些業務特別消耗內存或特別耗時,還可以把1個App分成多個進程,讓某些組件在獨立的進程中工作,銷毀該組建時,把整個進程一起用system.exit來銷毀掉。
  3. 提供虛擬機VM,安卓App是java程序,需要在java虛擬機上運行,這個虛擬機需要由系統在分配進程時,和進程一起提供。
  4. 除非做了跨進程跨用戶的配置,否則App之間是隔離的,不能直接互相訪問,也不能直接共享資源。
  5. AMS管理啟動過程,啟動App的工作都是AMS統一負責的,AMS里保存了App對應的系統進程ID(PID),在啟動App時,AMS會去找App對應的PID,如果找不到PID,說明需要創建進程,就會要求系統為App提供進程和VM。
  6. 提供進程和VM,創建VM是非常耗時的,為了加快App啟動速度,安卓系統采用了復制的方式:系統開機啟動時會啟動一個Zygote進程,這個進程會初始化系統的第一個VM, 并預加載framework等app通用資源,當安卓要啟動某個App時,Zygote通過fork復制Zygote的VM,就可以快速創建一個帶VM的進程,為App運行提供載體。系統開機啟動時的第一個進程一般是桌面Home進程。

提供系統資源的過程大概如下圖:


提供系統資源

三、啟動要打開的App組件

App本身沒有main函數入口,但是系統在啟動進程時,會創建一個主線程ActivityThread對象(Process.start("android.app.ActivityThread",...)),這個ActivityThread是一個final類,雖然不是線程,但是管理者主線程,它是有main函數入口的(Java終于找到組織了),ActivityThread有這么幾個作用:

  1. 管理App的主線程,也就是UI線程,啟動主線程的Looper,只有主線程可以調用View的onDraw函數來刷新界面(為了線程安全)。
    至于視頻類控件,都不是View而是SurfaceView,所以可以用子線程刷新,而且SurfaceView是直接繪制到屏幕上的,和View是分開管理的。
    另外,BroadCast消息也是主線程處理的,主線程創建BroadCastReceiver對象,并調用其onReceive()函數,處理完就銷毀,所以它的生命周期很短(10秒,超時就ANR)。
  2. 負責管理調度進程資源、Application和App四大組件中的三個(Activity,Service,ContentProvider),列表中的組件用Token區分,至于BroadCastReceiver,因為是隨用隨造,用完銷毀,所以不需要保存和管理。
  3. 構建Context和Application,這個任務包括檢查和加載LoadedApk對象、設置分辨率密度、是否高耗內存、獲取package和component等啟動信息、獲取ClassLoader把類加載到內存等,最后,先創建一個context對象contextimpl.createAppContext(this,getSystemContext().mPackageInfo;),再用context去創建Application對象context.mPackageInfo.makeApplication(true,null)。
  4. 對接AMS,AMS自己有專門的系統進程,ActivityThread把一個ApplicationThread(一個Bindler對象)作為自己的Proxy交給AMS,以便由AMS來調度管理ActivityThread中的Activity。
  5. 處理消息,ActivityThread是通過消息機制來啟動App組件的,ActivityThread有Message隊列、Handler和Looper,在AMS啟動Activity時,AMS會向ActivityThread發送LAUNCH_ACTIVITY消息啟動Activity,ActivityThread收到這個消息后啟動Activity,然后,就進入我們熟悉的組件onCreate生命周期了。
  6. 重要系統服務如SystemServer也是App,也有ActivityThread,也可能出現ANR之類的異常,為了避免系統“跑飛”,這些應用都有Watchdog看護,出現問題會重啟設備。

啟動過程大概是這樣的:


啟動過程

AMS是通過IPC向ActivityThread傳遞消息的。

另外,在創建組件時,組件之間有這樣幾個區別:

  1. Application和四大組件的啟動時機:
    Application是主線程啟動時創建的,這是應用程序運行的第一個類、
    ContentProvider是主線程啟動時創建的(并發布到AMS)、
    BroadCastReceiver是主線程收到廣播時創建的(前臺10秒/后臺60秒ANR)、
    Activity是AMS發消息讓主線程創建的(5秒ANR)、
    Service是AMS通過ApplicationThread接口,讓主線程創建的(并運行在主線程上,前臺20秒/后臺200秒ANR)。
  2. 關于Context:
    App里依靠context來提供資源和上下文,所以Application、Activity和Service有context(它們都extends Context)、
    BroadCastReceiver沒有context,主線程在調用onReceive時會把Application的context作為參數傳進去、
    ContentProvider也沒有context、
    雖然組件有各自的context,但它們指向同一塊資源,因為實現ContextImpl時,獲取資源的ResourcesManager采用單例模式,所以同一個App的不同context都指向同一個Resource對象
    Activity的context多了主題Theme,而Application的context生命周期最長。
    這些context之間的關系如下:


    context關系圖
  3. 關于對Application的共享:
    App中的Application是一個單例,整個App是共享同一個Application對象的、
    Activity和Service都有getApplication函數、這個Application是在創建組件時賦給組件的,比如Activity就是ActivityThread在performLaunchActivity時,把Application實體賦給Application的。
    組件有getApplication和getApplicationContext兩個函數,這兩個函數一個是組件本身的,一個contextwrapper要求實現的,很多情況下他們返回的是一個對象,但是官方并不建議把兩者混淆。

引用

Android應用程序啟動過程源代碼分析
Android應用程序安裝過程解析(源碼角度)
android系統Context初始化過程
Android Application啟動流程分析
ActivityThread的main方法究竟做了什么
深入理解ActivityManagerService
Android ActivityThread(主線程或UI線程)簡介
到底getApplicationContext和getApplication是不是返回同一個對象
Activity的啟動和創建
Android應用啟動、退出分析
一次搞定Process和Task
startService源碼從AMS進程到service的新進程啟動過程分析

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容