Android的安裝和啟動比較特別,很多機制和直觀感受并不一樣,如果這里出現誤解,就很難透徹理解App的運行,這里把過去積累的問題統一梳理了一下。
安裝
我們知道,Android的安裝包Apk其實就是個資源和組件的容器壓縮包,安裝的過程主要是復制和解析的過程,這個過程大概分這樣幾步:
一、復制
安卓的程序目錄是/data/app/,所以安裝的第一步就是把apk文件復制到這個目錄下。這里有四個問題:
- 安卓機有內部存儲和SD卡兩部分,很多安卓機的內存并不大,需要把apk安裝到SD卡上節省內存空間,所以程序目錄/data/app/實際上也是在內部存儲和SD卡上各一個。
- 系統自帶的App是安裝在/system/app/目錄下的,這個目錄只有root權限才能訪問,所以系統App在root之前是無法刪除和修改的,也就是說,系統App升級時,實際上是在/data/app/里重新安裝了一個App,這個路徑會重新注冊到系統那里,系統再打開App時,就會指向新App的地址。當然,這個新的App是可以卸載的,不過新的App卸載后,系統會把 /system/app/里那個舊的App提供給你,所以是卸掉新的,還你舊的。
- 還是系統App,在root后,我們可以操作/system/app/目錄,但是系統安裝Apk仍然會裝到/data/app/里,所以如果想修改/system/app/目錄里的app,必須自己手動push新的apk文件進去,這個新的apk文件不會自動被安裝,需要重啟設備,系統在重啟時檢查到apk被更新,才會去安裝apk。
- 系統目錄有個/system/priv-app/目錄,這里面放的是權限更高的系統核心應用,如開機launcher、系統UI、系統設置等,這個目錄我們最好不要動,保持系統干凈簡潔。
二、安裝
安卓系統開機啟動時,會啟動一個超級管理服務SystemServer,這個SystemServer會啟動所有的系統核心服務,其中就包括PackageManagerService,簡稱PMS,具體的apk安裝過程,就是由這個PMS操作的。
PMS會監控/data/app/這個目錄,在上一步中,系統安裝程序向這個目錄復制了一個apk,PMS自己就會定期掃描這個目錄,找到后綴為apk的文件,如果這個apk沒有被安裝過,它就會自動開始安裝,安裝時會做這么幾件事:
- 創建應用目錄,路徑為/data/data/your package(你的應用包名),App中使用的數據庫、so庫、xml文件等,都會放在這個目錄下。
- 提取dex文件,dex是App的可執行文件,系統解壓apk就能得到dex文件,然后把dex文件放到/data/dalvik-cache,這樣可以提前緩存dex到內存中,能加快啟動速度。系統還會把dex優化為odex,進一步加快啟動速度。
- 判斷是否可以安裝apk,如檢查apk簽名等。
- 為應用分配并保存一個UID,UID是來自Linux的用戶賬戶體系,不過在Android這種單用戶系統里,UID被用來與App對應,這也是安全機制的一部分,每個App都有自己對應的UID,這種對應關系是持久化保存的,App更新或卸載重裝后,系統還會給它分配原來那個UID。用adb pull /data/system/packages.list可以查看所有App的UID。GID(用戶組)一般等于UID。
- 利用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組件。
一、觸發啟動過程
在安卓系統開機啟動時,啟動的超級管理服務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。
在系統層面,它其實要做這么幾件事:
- 分配UID,App要有UID才能有自己的系統資源,UID是在安裝App時由系統分配的,一般每個App都有自己的UID,App的資源不能共享,因為它們不屬于同一個用戶。
- 分配進程Process,系統會給App一個進程,每個App的都有自己的進程,進程的PID是系統即時生成的,用完銷毀。
如果要讓兩個App共用進程,除了需要設置同一個進程(android:process),還需要分配同一個UID(android:sharedUserId)來共享系統資源,使用同一個應用簽名(同一個簽名證書才可以視為同一個程序)。
有時候,如果某些業務特別消耗內存或特別耗時,還可以把1個App分成多個進程,讓某些組件在獨立的進程中工作,銷毀該組建時,把整個進程一起用system.exit來銷毀掉。 - 提供虛擬機VM,安卓App是java程序,需要在java虛擬機上運行,這個虛擬機需要由系統在分配進程時,和進程一起提供。
- 除非做了跨進程跨用戶的配置,否則App之間是隔離的,不能直接互相訪問,也不能直接共享資源。
- AMS管理啟動過程,啟動App的工作都是AMS統一負責的,AMS里保存了App對應的系統進程ID(PID),在啟動App時,AMS會去找App對應的PID,如果找不到PID,說明需要創建進程,就會要求系統為App提供進程和VM。
- 提供進程和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有這么幾個作用:
- 管理App的主線程,也就是UI線程,啟動主線程的Looper,只有主線程可以調用View的onDraw函數來刷新界面(為了線程安全)。
至于視頻類控件,都不是View而是SurfaceView,所以可以用子線程刷新,而且SurfaceView是直接繪制到屏幕上的,和View是分開管理的。
另外,BroadCast消息也是主線程處理的,主線程創建BroadCastReceiver對象,并調用其onReceive()函數,處理完就銷毀,所以它的生命周期很短(10秒,超時就ANR)。 - 負責管理調度進程資源、Application和App四大組件中的三個(Activity,Service,ContentProvider),列表中的組件用Token區分,至于BroadCastReceiver,因為是隨用隨造,用完銷毀,所以不需要保存和管理。
- 構建Context和Application,這個任務包括檢查和加載LoadedApk對象、設置分辨率密度、是否高耗內存、獲取package和component等啟動信息、獲取ClassLoader把類加載到內存等,最后,先創建一個context對象contextimpl.createAppContext(this,getSystemContext().mPackageInfo;),再用context去創建Application對象context.mPackageInfo.makeApplication(true,null)。
- 對接AMS,AMS自己有專門的系統進程,ActivityThread把一個ApplicationThread(一個Bindler對象)作為自己的Proxy交給AMS,以便由AMS來調度管理ActivityThread中的Activity。
- 處理消息,ActivityThread是通過消息機制來啟動App組件的,ActivityThread有Message隊列、Handler和Looper,在AMS啟動Activity時,AMS會向ActivityThread發送LAUNCH_ACTIVITY消息啟動Activity,ActivityThread收到這個消息后啟動Activity,然后,就進入我們熟悉的組件onCreate生命周期了。
- 重要系統服務如SystemServer也是App,也有ActivityThread,也可能出現ANR之類的異常,為了避免系統“跑飛”,這些應用都有Watchdog看護,出現問題會重啟設備。
啟動過程大概是這樣的:
AMS是通過IPC向ActivityThread傳遞消息的。
另外,在創建組件時,組件之間有這樣幾個區別:
- Application和四大組件的啟動時機:
Application是主線程啟動時創建的,這是應用程序運行的第一個類、
ContentProvider是主線程啟動時創建的(并發布到AMS)、
BroadCastReceiver是主線程收到廣播時創建的(前臺10秒/后臺60秒ANR)、
Activity是AMS發消息讓主線程創建的(5秒ANR)、
Service是AMS通過ApplicationThread接口,讓主線程創建的(并運行在主線程上,前臺20秒/后臺200秒ANR)。 -
關于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關系圖 - 關于對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的新進程啟動過程分析