Android系統APP安裝流程
更多內容,歡迎關注公眾號:tmac_lover
這篇文章介紹一下Android裡安裝一個apk檔案的完整流程,我們以pm install安裝一個新的app為例介紹。
1. pm命令
當我們使用
pm install -r /sdcard/test.apk
這樣的pm命令來安裝app的時候, 最終呼叫的是Pm.java的runInstall()方法
private int runInstall() {
... ...
mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags,
installerPackageName, verificationParams, abi, userId);
... ...
}
最後通過binder呼叫PackageManagerService.java的installPackageAsUser()方法,然後就開始真正的進行apk的安裝工作。可以看到,pm install命令進行app安裝的時候,
是不會經過PackageInstaller的。
2. PackageManagerService
installPackageAsUser()方法裡實現apk的安裝主要可以分三個階段。我們先看下總體流程圖:
2.1 準備階段
在進行app安裝之前,需要先做一些準備工作。
public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, VerificationParams verificationParams,
String packageAbiOverride, int userId) {
... ...
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(origin, null, observer, installFlags, installerPackageName,
null , verificationParams, user, packageAbiOverride, null);
mHandler.sendMessage(msg);
}
這裡先構造了一個InstallParams物件,這裡存放一些app檔案安裝需要的資訊,比如apk檔案的路徑,安裝時的flags, 以及安裝完成之後的回撥等等。然後通過Handler傳送一條INIT_COPY訊息。
class PackageHandler extends Handler {
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
if (!mBound) {
// 第一次安裝app, 會連線DefaultContainerService
if (!connectToService()) {
params.serviceError();
return;
} else {
// 然後將app安裝請求放到mPendingInstalls裡
mPendingInstalls.add(idx, params);
}
} else {
// 如果不是第一次安裝app, DefaultContainerService已經是連線狀態,
// 直接將app安裝請求放到mPendingInstalls裡
mPendingInstalls.add(idx, params);
if (idx == 0) {
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
break;
}
case MCS_BOUND: {
// 不管是哪種情況,INIT_COPY之後,一定會再發一個MSC_BOUND的訊息
// 這裡開始呼叫InstallParams類的startCopy()方法
if (params.startCopy()) {
if (mPendingInstalls.size() > 0) {
mPendingInstalls.remove(0);
}
// 如果mPendingInstalls裡還有等待安裝的app, 依次執行
if (mPendingInstalls.size() == 0) {
if (mBound) {
removeMessages(MCS_UNBIND);
Message ubmsg = obtainMessage(MCS_UNBIND);
sendMessageDelayed(ubmsg, 10000);
}
} else {
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
}
}
}
}
總結一下,在安裝app之前的準備工作主要有:
1. 構造一個InstallParams物件,這個物件裡包含了apk檔案的資訊和一些安裝相關的引數
2. 傳送INIT_COPY訊息,這時如果沒有連線DefaultContainerService, 先連線這個service;還有就是將apk安裝請求放入mPendingInstalls裡
3. 再發送MCS_BOUND訊息,呼叫InstallParams物件的startCopy()方法開始安裝app;如果有多個app等待安裝,迴圈發MSC_BOUND訊息,執行安裝操作
2.2 handleStartCopy
前面的準備工作最後,呼叫InstallParams物件的startCopy()方法開始app的安裝工作,startCopy()裡分為兩部分,handleStartCopy()和handleReturnCode()。先看下handleStartCopy()流程圖:
- 圖中第2步獲取app安裝的基本資訊;先通過parsePackageLite()提取apk包中的資訊,然後依據AndroidManifest.xml裡的”android:installLocation”屬性和apk安裝需要的大小以及目標分割槽的剩餘空間大小來決定app最終是安裝在data分割槽,還是sdcard上。
- 第6步installLocationPolicy()方法對第2步返回的apk資訊做進一步確認。如果系統中已經安裝過此app, 需要確認新安裝的app版本號是否比之前的高;同時還會根據上一步獲取的安裝位置資訊設定app安裝需要的Flag
- 第7步copyApk()方法完成將需要安裝的apk檔案拷貝到要安裝的分割槽的一個零時資料夾裡,並重命名為base.apk,比如/data/app/vmdl1309479077.tmp/base.apk下。同時還會將.apk包裡的.so庫拷貝到/data/app/vmdl1309479077.tmp/libs下
所以handleStartCopy()主要的工作就是將要安裝的apk檔案和使用的.so庫拷貝到要安裝的分割槽的零時目錄下
2.3 handleReturnCode
handleReturnCode裡會完成app安裝的剩餘動作,併發送安裝成功的廣播。
- 第4步installPackageLI()方法裡完成了安裝app的核心工作,後面專門分析。
- 第8步通過Handler傳送POST_INSTALL訊息,這個訊息的處理函式如下:
class PackageHandler extends Handler {
void doHandleMessage(Message msg) {
switch (msg.what) {
... ...
case POST_INSTALL: {
... ...
// 傳送ACTION_PACKAGE_ADDED這條廣播
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
packageName, extras, null, null, firstUsers);
if (args.observer != null) {
// 如果有回撥的話,呼叫回撥方法, 通知app安裝完成
args.observer.onPackageInstalled(res.name, res.returnCode,
res.returnMsg, extras);
}
... ...
break;
}
}
}
}
在收到POST_INSTALL訊息後,主要完成兩條操作:
- 傳送ACTION_PACKAGE_ADDED廣播
- 如果在呼叫installPackageAsUser()方法時有傳入IPackageInstallObserver2作為回撥,則呼叫回撥方法報告安裝狀態
接下來看下第4步installPackageLI()方法:
- 第2步parsePackage()通過解析apk包和apk包裡的AndroidManifest.xml,解析出所有apk的資訊,放到Package物件中。
- 第3步collectCertificates()方法對apk檔案進行簽收校驗,確保apk檔案沒有被更改過以及簽名正確。第4步通過collectManifestDigest()方法獲取AndroidManifest.xml的摘要值。
- 第5步derivePackageAbi()方法通過apk包裡使用的so庫確定app程序的abi,關於abi,可以檢視之前的一篇文章:
- 第6步performDexOpt()通過socket和installd通訊,將apk包裡的classes.dex轉化為虛擬機器可以直接執行的檔案格式,關於dex優化,可以檢視之前的一篇文章:
- 第7步doRename()方法將前面handleStartCopy()裡的零時資料夾/data/app/vmdl1309479077更改成/data/app/$(package_name),這個新的資料夾將是app安裝後的最終目錄
- 第8步installNewPackageLI()將完成一個新的app的安裝,如果是安裝之前已經存在的apk, 則這裡呼叫的是replacePackageLI()
接下來看看installNewPackageLI()方法的流程:
- verifySignaturesLP()使用上一步中collectCertificates()裡獲取的簽名信息進行驗證,主要分兩步:一是如果是安裝一個系統中已經存在的同名app, 則和已經存在的app簽名進行對比,保證兩者簽名相同;二是如果系統中已經存在和正在安裝的app有相同UID的app,
則需要保證安裝app的簽名和已經存在的使用相同UID的app簽名相同。 - createDataDirsLI()方法建立app的資料目錄,例如: /data/data/${package_name}/
- adjustCpuAbisForSharedUserLPw()方法保證如果新安裝的app使用了”android:sharedUID”, 它會將它的abi調整成和系統中使用相同uid的其它app一樣的abi
- 接下來就會在mSettings中寫入正在安裝app的資訊,比如安裝路徑,資料目錄路徑,簽名信息,abi資訊等。最終將這些資訊存在/data/system/packages.xml中。同時還會將AndroidManifest.xml中的四大元件以及許可權等資訊,存到PackageManagerService的對應數組裡,
這樣以後通過startActivity之類的方法開啟相應的元件時,就可以找到相應的元件是否存在。需要注意的是,這些四大元件以及許可權資訊,只是儲存在記憶體中,每次系統開機時,都會重新掃描apk檔案,然後將它們再次存到記憶體中使用。
3. 小結
關於新安裝一個apk的大概流程就介紹完了;當然Android系統因為要相容各種各樣的情況,關於安裝apk的程式碼實際上比上面分析的更加的複雜,但是無論有多少if else,核心原理和流程還是和上面分析的一樣。希望大家能夠對著程式碼自己弄解清楚。