在上一篇文章Android包管理機制之PackageInstaller安裝APK中,我們學習了PackageInstaller是如何安裝APK的,最後會將APK的信息交由PMS處理。那麼PMS是如何處理的呢?主要是APK的複製和安裝,由於公號文章字數的限制,這篇文章只能介紹 PMS處理APK的複製,APK安裝過程會在後續文章講解。
1.PackageHandler處理安裝消息APK的信息交由PMS後,PMS通過向PackageHandler發送消息來驅動APK的複製和安裝工作。
先來查看PackageHandler處理安裝消息的調用時序圖。
接著上一篇文章的代碼邏輯來查看PMS的installStage方法。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
void installStage(String packageName, File stagedDir, String stagedCid,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
String installerPackageName, int installerUid, UserHandle user,
Certificate[][] certificates) {
...
final Message msg = mHandler.obtainMessage(INIT_COPY);
final int installReason = fixUpInstallReason(installerPackageName, installerUid,
sessionParams.installReason);
final InstallParams params = new InstallParams(origin, null, observer,
sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
verificationInfo, user, sessionParams.abiOverride,
sessionParams.grantedRuntimePermissions, certificates, installReason);
params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
msg.obj = params;
...
mHandler.sendMessage(msg);
}
注釋2處創建InstallParams,它對應於包的安裝數據。注釋1處創建了類型為INIT_COPY的消息,在注釋3處將InstallParams通過消息發送出去。
1.1 對INIT_COPY的消息的處理
處理INIT_COPY類型的消息的代碼如下所示。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#PackageHandler
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
int idx = mPendingInstalls.size();
if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
if (!mBound) {
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
System.identityHashCode(mHandler));
if (!connectToService()) {
Slog.e(TAG, "Failed to bind to media container service");
params.serviceError();
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
System.identityHashCode(mHandler));
if (params.traceMethod != null) {
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, params.traceMethod,
params.traceCookie);
}
return;
} else {
mPendingInstalls.add(idx, params);
}
} else {
mPendingInstalls.add(idx, params);
if (idx == 0) {
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
break;
}
....
}
}
}
PackageHandler繼承自Handler,它被定義在PMS中,doHandleMessage方法用於處理各個類型的消息,來查看對INIT_COPY類型消息的處理。注釋1處的mBound用於標識是否綁定了DefaultContainerService,默認值為false。DefaultContainerService是用於檢查和複製可移動文件的服務,這是一個比較耗時的操作,因此DefaultContainerService沒有和PMS運行在同一進程中,它運行在com.android.defcontainer進程,通過IMediaContainerService和PMS進行IPC通信,如下圖所示。
注釋2處的connectToService方法用來綁定DefaultContainerService,注釋3處發送MCS_BOUND類型的消息,觸發處理第一個安裝請求。
查看注釋2處的connectToService方法:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#PackageHandler
private boolean connectToService() {
if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +
" DefaultContainerService");
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
if (mContext.bindServiceAsUser(service, mDefContainerConn,
Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mBound = true;
return true;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return false;
}
注釋2處如果綁定DefaultContainerService成功,mBound會置為ture 。注釋1處的bindServiceAsUser方法會傳入mDefContainerConn,bindServiceAsUser方法的處理邏輯和我們調用bindService是類似的,服務建立連接後,會調用onServiceConnected方法:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
class DefaultContainerConnection implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
final IMediaContainerService imcs = IMediaContainerService.Stub
.asInterface(Binder.allowBlocking(service));
mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, Object));
}
public void onServiceDisconnected(ComponentName name) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected");
}
}
注釋1處發送了MCS_BOUND類型的消息,與PackageHandler.doHandleMessage方法的注釋3處不同的是,這裡發送消息帶了Object類型的參數,這裡會對這兩種情況來進行講解,一種是消息不帶Object類型的參數,一種是消息帶Object類型的參數。
1.2 對MCS_BOUND類型的消息的處理
消息不帶Object類型的參數
查看對MCS_BOUND類型消息的處理:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
case MCS_BOUND: {
if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
if (msg.obj != null) {
mContainerService = (IMediaContainerService) msg.obj;
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
System.identityHashCode(mHandler));
}
if (mContainerService == null) {
if (!mBound) {
Slog.e(TAG, "Cannot bind to media container service");
for (HandlerParams params : mPendingInstalls) {
params.serviceError();
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(params));
if (params.traceMethod != null) {
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER,
params.traceMethod, params.traceCookie);
}
return;
}
mPendingInstalls.clear();
} else {
Slog.w(TAG, "Waiting to connect to media container service");
}
} else if (mPendingInstalls.size() > 0) {
...
else {
Slog.w(TAG, "Empty queue");
}
break;
}
如果消息不帶Object類型的參數,就無法滿足注釋1處的條件,注釋2處的IMediaContainerService類型的mContainerService也無法被賦值,這樣就滿足了注釋3處的條件。
如果滿足注釋4處的條件,說明還沒有綁定服務,而此前已經在PackageHandler.doHandleMessage方法的注釋2處調用綁定服務的方法了,這顯然是不正常的,因此在注釋5處負責處理服務發生錯誤的情況。如果不滿足注釋4處的條件,說明已經綁定服務了,就會列印出系統log,告知用戶等待系統綁定服務。
消息帶Object類型的參數
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
case MCS_BOUND: {
if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
if (msg.obj != null) {
...
}
if (mContainerService == null) {
...
} else if (mPendingInstalls.size() > 0) {
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(params));
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startCopy");
if (params.startCopy()) {
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Checking for more work or unbind...");
if (mPendingInstalls.size() > 0) {
mPendingInstalls.remove(0);
}
if (mPendingInstalls.size() == 0) {
if (mBound) {
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Posting delayed MCS_UNBIND");
removeMessages(MCS_UNBIND);
Message ubmsg = obtainMessage(MCS_UNBIND);
sendMessageDelayed(ubmsg, 10000);
}
} else {
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Posting MCS_BOUND for next work");
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}else {
Slog.w(TAG, "Empty queue");
}
break;
}
如果MCS_BOUND類型消息帶Object類型的參數就不會滿足注釋1處的條件,就會調用注釋2處的判斷,如果安裝請求數不大於0就會列印出注釋6處的log,說明安裝請求隊列是空的。安裝完一個APK後,就會在注釋5處發出MSC_BOUND消息,繼續處理剩下的安裝請求直到安裝請求隊列為空。
注釋3處得到安裝請求隊列第一個請求HandlerParams ,如果HandlerParams 不為null就會調用注釋4處的HandlerParams的startCopy方法,用於開始複製APK的流程。
先來查看複製APK的時序圖。
HandlerParams是PMS中的抽象類,它的實現類為PMS的內部類InstallParams。HandlerParams的startCopy方法如下所示。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#HandlerParams
final boolean startCopy() {
boolean res;
try {
if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
if (++mRetries > MAX_RETRIES) {
Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
mHandler.sendEmptyMessage(MCS_GIVE_UP);
handleServiceError();
return false;
} else {
handleStartCopy();
res = true;
}
} catch (RemoteException e) {
if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
mHandler.sendEmptyMessage(MCS_RECONNECT);
res = false;
}
handleReturnCode();
return res;
}
注釋1處的mRetries用於記錄startCopy方法調用的次數,調用startCopy方法時會先自動加1,如果次數大於4次就放棄這個安裝請求:在注釋2處發送MCS_GIVE_UP類型消息,將第一個安裝請求(本次安裝請求)從安裝請求隊列mPendingInstalls中移除掉。注釋4處用於處理複製APK後的安裝APK邏輯,第3小節中會再次提到它。注釋3處調用了抽象方法handleStartCopy,它的實現在InstallParams中,如下所示。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#InstallParams
public void handleStartCopy() throws RemoteException {
...
final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
final boolean ephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
PackageInfoLite pkgLite = null;
if (onInt && onSd) {
Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else if (onSd && ephemeral) {
Slog.w(TAG, "Conflicting flags specified for installing ephemeral on external");
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else {
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
packageAbiOverride);
if (DEBUG_EPHEMERAL && ephemeral) {
Slog.v(TAG, "pkgLite for install: " + pkgLite);
}
...
if (ret == PackageManager.INSTALL_SUCCEEDED) {
int loc = pkgLite.recommendedInstallLocation;
if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
}
...
}else{
loc = installLocationPolicy(pkgLite);
...
}
}
final InstallArgs args = createInstallArgs(this);
mArgs = args;
if (ret == PackageManager.INSTALL_SUCCEEDED) {
...
if (!origin.existing && requiredUid != -1
&& isVerificationEnabled(
verifierUser.getIdentifier(), installFlags, installerUid)) {
...
} else{
ret = args.copyApk(mContainerService, true);
}
}
mRet = ret;
}
handleStartCopy方法的代碼很多,這裡截取關鍵的部分。
注釋1處通過IMediaContainerService跨進程調用DefaultContainerService的getMinimalPackageInfo方法,該方法輕量解析APK並得到APK的少量信息,輕量解析的原因是這裡不需要得到APK的全部信息,APK的少量信息會封裝到PackageInfoLite中。接著在注釋2處確定APK的安裝位置。注釋3處創建了InstallArgs,InstallArgs 是一個抽象類,定義了APK的安裝邏輯,比如複製和重命名APK等,它有3個子類,都被定義在PMS中,如下圖所示。
private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
...
try {
final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
final File tempDir =
mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
codeFile = tempDir;
resourceFile = tempDir;
} catch (IOException e) {
Slog.w(TAG, "Failed to create copy file: " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
...
int ret = PackageManager.INSTALL_SUCCEEDED;
ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
...
return ret;
}
注釋1處用於創建臨時存儲目錄,比如/data/app/vmdl18300388.tmp,其中18300388是安裝的sessionId。注釋2處通過IMediaContainerService跨進程調用DefaultContainerService的copyPackage方法,這個方法會在DefaultContainerService所在的進程中將APK複製到臨時存儲目錄,比如/data/app/vmdl18300388.tmp/base.apk。目前為止APK的複製工作就完成了,接著就是APK的安裝過程了。
3.總結本文主要講解了PMS是如何處理APK複製的,主要有兩個步驟:
PackageInstaller安裝APK時會將APK的信息交由PMS處理,PMS通過向PackageHandler發送消息來驅動APK的複製和安裝工作。
PMS發送INIT_COPY和MCS_BOUND類型的消息,控制PackageHandler來綁定DefaultContainerService,完成複製APK等工作。