概述安卓App的安裝和啟動(dòng)

Android的安裝和啟動(dòng)比較特別,很多機(jī)制和直觀感受并不一樣,如果這里出現(xiàn)誤解,就很難透徹理解App的運(yùn)行,這里把過(guò)去積累的問(wèn)題統(tǒng)一梳理了一下。

安裝

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

一、復(fù)制

安卓的程序目錄是/data/app/,所以安裝的第一步就是把a(bǔ)pk文件復(fù)制到這個(gè)目錄下。這里有四個(gè)問(wèn)題:

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

二、安裝

安卓系統(tǒng)開(kāi)機(jī)啟動(dòng)時(shí),會(huì)啟動(dòng)一個(gè)超級(jí)管理服務(wù)SystemServer,這個(gè)SystemServer會(huì)啟動(dòng)所有的系統(tǒng)核心服務(wù),其中就包括PackageManagerService,簡(jiǎn)稱(chēng)PMS,具體的apk安裝過(guò)程,就是由這個(gè)PMS操作的。
PMS會(huì)監(jiān)控/data/app/這個(gè)目錄,在上一步中,系統(tǒng)安裝程序向這個(gè)目錄復(fù)制了一個(gè)apk,PMS自己就會(huì)定期掃描這個(gè)目錄,找到后綴為apk的文件,如果這個(gè)apk沒(méi)有被安裝過(guò),它就會(huì)自動(dòng)開(kāi)始安裝,安裝時(shí)會(huì)做這么幾件事:

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

三、通知

apk安裝完成后,PMS會(huì)發(fā)一個(gè)ACTION_PACKAGE_ADDED廣播,如果是卸載,會(huì)發(fā)ACTION_PACKAGE_REMOVED廣播。
整個(gè)安裝過(guò)程大概是這樣的:


App安裝過(guò)程

啟動(dòng)

啟動(dòng)一個(gè)App,首先需要觸發(fā)啟動(dòng)過(guò)程,然后分配系統(tǒng)資源,最后才啟動(dòng)要打開(kāi)的App組件。

一、觸發(fā)啟動(dòng)過(guò)程

在安卓系統(tǒng)開(kāi)機(jī)啟動(dòng)時(shí),啟動(dòng)的超級(jí)管理服務(wù)SystemServer會(huì)啟動(dòng)所有的系統(tǒng)核心服務(wù),其中就包括ActivityManagerService,簡(jiǎn)稱(chēng)AMS,啟動(dòng)App具體都是AMS來(lái)負(fù)責(zé)的。
不過(guò),一般Java程序都有個(gè)main函數(shù)入口,啟動(dòng)Java程序其實(shí)就是執(zhí)行main函數(shù)去了。但是,安卓App不是這樣設(shè)計(jì)的,App并沒(méi)有統(tǒng)一的程序入口,一個(gè)App其實(shí)更像是一群組件的集合,啟動(dòng)App其實(shí)就是啟動(dòng)了某個(gè)組件,即便是從桌面點(diǎn)擊應(yīng)用圖標(biāo)打開(kāi)某個(gè)App,也是系統(tǒng)桌面Home根據(jù)安裝時(shí)注冊(cè)的組件信息,找到這個(gè)圖標(biāo)對(duì)應(yīng)的Activity信息,再由AMS去啟動(dòng)Activity組件。


觸發(fā)啟動(dòng)過(guò)程

二、分配系統(tǒng)資源

在安卓系統(tǒng)里,除非人為設(shè)置為多進(jìn)程(Activity的android:process屬性),否則默認(rèn)每個(gè)App都有1個(gè)獨(dú)立的進(jìn)程和虛擬機(jī),所以在系統(tǒng)啟動(dòng)時(shí),系統(tǒng)會(huì)建立一個(gè)Linux進(jìn)程(Process),在這個(gè)進(jìn)程里放一個(gè)虛擬機(jī)VM,在這個(gè)VM里,運(yùn)行你的App。
在系統(tǒng)層面,它其實(shí)要做這么幾件事:

  1. 分配UID,App要有UID才能有自己的系統(tǒng)資源,UID是在安裝App時(shí)由系統(tǒng)分配的,一般每個(gè)App都有自己的UID,App的資源不能共享,因?yàn)樗鼈儾粚儆谕粋€(gè)用戶(hù)。
  2. 分配進(jìn)程Process,系統(tǒng)會(huì)給App一個(gè)進(jìn)程,每個(gè)App的都有自己的進(jìn)程,進(jìn)程的PID是系統(tǒng)即時(shí)生成的,用完銷(xiāo)毀。
    如果要讓兩個(gè)App共用進(jìn)程,除了需要設(shè)置同一個(gè)進(jìn)程(android:process),還需要分配同一個(gè)UID(android:sharedUserId)來(lái)共享系統(tǒng)資源,使用同一個(gè)應(yīng)用簽名(同一個(gè)簽名證書(shū)才可以視為同一個(gè)程序)。
    有時(shí)候,如果某些業(yè)務(wù)特別消耗內(nèi)存或特別耗時(shí),還可以把1個(gè)App分成多個(gè)進(jìn)程,讓某些組件在獨(dú)立的進(jìn)程中工作,銷(xiāo)毀該組建時(shí),把整個(gè)進(jìn)程一起用system.exit來(lái)銷(xiāo)毀掉。
  3. 提供虛擬機(jī)VM,安卓App是java程序,需要在java虛擬機(jī)上運(yùn)行,這個(gè)虛擬機(jī)需要由系統(tǒng)在分配進(jìn)程時(shí),和進(jìn)程一起提供。
  4. 除非做了跨進(jìn)程跨用戶(hù)的配置,否則App之間是隔離的,不能直接互相訪問(wèn),也不能直接共享資源。
  5. AMS管理啟動(dòng)過(guò)程,啟動(dòng)App的工作都是AMS統(tǒng)一負(fù)責(zé)的,AMS里保存了App對(duì)應(yīng)的系統(tǒng)進(jìn)程ID(PID),在啟動(dòng)App時(shí),AMS會(huì)去找App對(duì)應(yīng)的PID,如果找不到PID,說(shuō)明需要?jiǎng)?chuàng)建進(jìn)程,就會(huì)要求系統(tǒng)為App提供進(jìn)程和VM。
  6. 提供進(jìn)程和VM,創(chuàng)建VM是非常耗時(shí)的,為了加快App啟動(dòng)速度,安卓系統(tǒng)采用了復(fù)制的方式:系統(tǒng)開(kāi)機(jī)啟動(dòng)時(shí)會(huì)啟動(dòng)一個(gè)Zygote進(jìn)程,這個(gè)進(jìn)程會(huì)初始化系統(tǒng)的第一個(gè)VM, 并預(yù)加載framework等app通用資源,當(dāng)安卓要啟動(dòng)某個(gè)App時(shí),Zygote通過(guò)fork復(fù)制Zygote的VM,就可以快速創(chuàng)建一個(gè)帶VM的進(jìn)程,為App運(yùn)行提供載體。系統(tǒng)開(kāi)機(jī)啟動(dòng)時(shí)的第一個(gè)進(jìn)程一般是桌面Home進(jìn)程。

提供系統(tǒng)資源的過(guò)程大概如下圖:


提供系統(tǒng)資源

三、啟動(dòng)要打開(kāi)的App組件

App本身沒(méi)有main函數(shù)入口,但是系統(tǒng)在啟動(dòng)進(jìn)程時(shí),會(huì)創(chuàng)建一個(gè)主線程ActivityThread對(duì)象(Process.start("android.app.ActivityThread",...)),這個(gè)ActivityThread是一個(gè)final類(lèi),雖然不是線程,但是管理者主線程,它是有main函數(shù)入口的(Java終于找到組織了),ActivityThread有這么幾個(gè)作用:

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

啟動(dòng)過(guò)程大概是這樣的:


啟動(dòng)過(guò)程

AMS是通過(guò)IPC向ActivityThread傳遞消息的。

另外,在創(chuàng)建組件時(shí),組件之間有這樣幾個(gè)區(qū)別:

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


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

引用

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容