Android應用安裝流程分析
這段時間在研究插件化相關的技術,追根溯源,所以干脆把Apk的安裝流程梳理了一遍,與大家共享,望指正!
本文基于Android 5.1的源碼,分析Apk安裝流程。
Apk是Android Pakage的縮寫,即Android安裝包,Apk文件其實是zip格式,一般包含一個或多個dex文件、resources.arsc、AndroidManifest.xml、res目錄、META-INF目錄及包含so庫的lib目錄,這里就不在啰嗦。
1、安裝流程圖
先來看一張整體的流程圖:
安裝過程:復制apk安裝包到/data/app目錄下,解壓并掃描安裝包,向資源管理器注入apk資源,解析AndroidManifest文件,并在/data/data目錄下創建對應的應用數據目錄,然后針對dalvik/art環境優化dex文件,保存到dalvik-cache目錄,將AndroidManifest文件解析出的組件、權限注冊到PackageManagerService,完成后發送廣播。
2、安裝時序圖
上圖太過籠統,不利于了解細節,這里整理出一張時序圖,以便于分析。
說明:時序圖中劃分為三個部分:PackageInstaller進程、System進程、DefaultContainerService進程,重點關注System進程中的PackageManagerService。
PackageManagerService:PMS是Android中最核心的服務之一,主要負責對系統的apk進行管理,以及對四大組件的管理。
3、流程分析
用戶安裝Apk時,如從廠商官方應用市場下載,一般無安裝頁面,這里以用戶安裝第三方安裝包為例進行分析,PackageInstaller應用負責安裝及卸載過程與用戶交互(見時序圖PackageInstaller進程)。這里著重介紹System進程PMS安裝流程。
3.1 將apk文件復制至/data/app目錄
此流程在時序圖中過程還是挺繁復的,中間涉及到比較多的跳轉,我們挑重點看:
public class PackageManagerService extends IPackageManager.Stub {
@Override
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, observer, installFlags,
installerPackageName, verificationParams, user, packageAbiOverride);
mHandler.sendMessage(msg);
}
}
此處,通過PackageHandler發送INIT_COPY消息準備復制apk,我們再來接著看,
class PackageHandler extends Handler {
...
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 a bind was already initiated we dont really
// need to do anything. The pending install
// will be processed later on.
if (!mBound) {
// If this is the only one pending we might
// have to bind to the service again.
if (!connectToService()) {
Slog.e(TAG, "Failed to bind to media container service");
params.serviceError();
return;
} else {
// Once we bind to the service, the first
// pending request will be processed.
mPendingInstalls.add(idx, params);
}
} else {
mPendingInstalls.add(idx, params);
// Already bound to the service. Just make
// sure we trigger off processing the first request.
if (idx == 0) {
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
break;
}
case MCS_BOUND: {
if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
if (msg.obj != null) {
mContainerService = (IMediaContainerService) msg.obj;
}
if (mContainerService == null) {
...
} else if (mPendingInstalls.size() > 0) {
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
if (params.startCopy()) {
// We are done... look for more work or to
// go idle.
// Delete pending install
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);
// Unbind after a little delay, to avoid
// continual thrashing.
sendMessageDelayed(ubmsg, 10000);
}
} else {
// There are more pending requests in queue.
// Just post MCS_BOUND message to trigger processing
// of next pending install.
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Posting MCS_BOUND for next work");
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
}
} else {
// Should never happen ideally.
Slog.w(TAG, "Empty queue");
}
break;
}
}
...
}
...
}
INIT_COPY這條case主要是確保 DefaultContainerService 已經綁定,DefaultContainerService是一個單獨的apk進程,主要提供檢查和復制設備上的文件的服務。MCS_BOUND這條case中最關鍵的是執行 params.startCopy() 開始拷貝工作。
class InstallParams extends HandlerParams {
...
public void handleStartCopy() throws RemoteException {
...
if (onInt && onSd) {
...
} else {
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags, packageAbiOverride);
...
}
final InstallArgs args = createInstallArgs(this);
mArgs = args;
if (ret == PackageManager.INSTALL_SUCCEEDED) {
...
if (!origin.existing && requiredUid != -1
&& isVerificationEnabled(userIdentifier, installFlags)) {
...
if (ret == PackageManager.INSTALL_SUCCEEDED
&& mRequiredVerifierPackage != null) {
/*
* Send the intent to the required verification agent,
* but only start the verification timeout after the
* target BroadcastReceivers have run.
*/
verification.setComponent(requiredVerifierComponent);
mContext.sendOrderedBroadcastAsUser(verification, getUser(),
android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final Message msg = mHandler
.obtainMessage(CHECK_PENDING_VERIFICATION);
msg.arg1 = verificationId;
mHandler.sendMessageDelayed(msg, getVerificationTimeout());
}
}, null, 0, null, null);
/*
* We don't want the copy to proceed until verification
* succeeds, so null out this field.
*/
mArgs = null;
}
} else {
/*
* No package verification is enabled, so immediately start
* the remote call to initiate copy using temporary file.
*/
ret = args.copyApk(mContainerService, true);
}
}
}
...
}
這部分主要是檢查存儲空間,權限等,若已有軟件包驗證程序,則需要等待驗證程序檢驗安裝包,否則可直接安裝。這里我們直接來看 args.copyApk 。這里需要提到的是 createInstallArgs(this) 會根據InstallParams來判斷安裝位置,這里以內部存儲安裝為例。
class FileInstallArgs extends InstallArgs {
...
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
...
try {
final File tempDir = mInstallerService.allocateInternalStageDirLegacy();
codeFile = tempDir;
resourceFile = tempDir;
} catch (IOException e) {
Slog.w(TAG, "Failed to create copy file: " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
final IParcelFileDescriptorFactory target = new IParcelFileDescriptorFactory.Stub() {
@Override
public ParcelFileDescriptor open(String name, int mode) throws RemoteException {
...
}
};
int ret = PackageManager.INSTALL_SUCCEEDED;
ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
if (ret != PackageManager.INSTALL_SUCCEEDED) {
Slog.e(TAG, "Failed to copy package");
return ret;
}
final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
NativeLibraryHelper.Handle handle = null;
try {
handle = NativeLibraryHelper.Handle.create(codeFile);
ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
abiOverride);
} catch (IOException e) {
Slog.e(TAG, "Copying native libraries failed", e);
ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
} finally {
IoUtils.closeQuietly(handle);
}
return ret;
}
...
}
這里完成了apk拷貝及so庫的拷貝。
3.2 解析安裝包
public class PackageManagerService extends IPackageManager.Stub {
...
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
// Queue up an async operation since the package installation may take a little while.
mHandler.post(new Runnable() {
public void run() {
...
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
args.doPreInstall(res.returnCode);
synchronized (mInstallLock) {
installPackageLI(args, res);
}
args.doPostInstall(res.returnCode, res.uid);
}
...
}
}
}
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
...
PackageParser pp = new PackageParser();
...
final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(tmpPackageFile, parseFlags);
} catch (PackageParserException e) {
res.setError("Failed parse during installPackageLI", e);
return;
}
...
if (replace) {
replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
installerPackageName, res);
} else {
installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
args.user, installerPackageName, res);
}
...
}
...
}
此處對于安裝包進行解析,包括解析 AndroidManifest 版本、權限、組件等,詳見 PackageParser::parsePackage(tmpPackageFile, parseFlags) ,這部分代碼量較大但流程清晰,這里簡單看一下
public class PackageParser {
...
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
} else {
return parseMonolithicPackage(packageFile, flags);
}
}
...
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
...
final AssetManager assets = new AssetManager();
try {
final Package pkg = parseBaseApk(apkFile, assets, flags);
pkg.codePath = apkFile.getAbsolutePath();
return pkg;
} finally {
IoUtils.closeQuietly(assets);
}
}
...
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
throws PackageParserException {
...
//將資源添加進資源管理
final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
...
try {
res = new Resources(assets, mMetrics, null);
...
final String[] outError = new String[1];
//這里解析manifest文件,具體就不在展開,詳看請移步源碼
final Package pkg = parseBaseApk(res, parser, flags, outError);
...
return pkg;
} catch (PackageParserException e) {
throw e;
} catch (Exception e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to read manifest from " + apkPath, e);
} finally {
IoUtils.closeQuietly(parser);
}
}
...
}
3.3 檢測權限、注冊組件
接下來看執行 installNewPackageLI 函數,這部分代碼核心為 scanPackageLI 、 updateSettingsLI :
public class PackageManagerService extends IPackageManager.Stub {
...
private void installNewPackageLI(PackageParser.Package pkg,
int parseFlags, int scanFlags, UserHandle user,
String installerPackageName, PackageInstalledInfo res) {
...
try {
PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags,
System.currentTimeMillis(), user);
updateSettingsLI(newPackage, installerPackageName, null, null, res);
// delete the partially installed application. the data directory will have to be
// restored if it was already existing
if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
// remove package from internal structures. Note that we want deletePackageX to
// delete the package data and cache directories that it created in
// scanPackageLocked, unless those directories existed before we even tried to
// install.
deletePackageLI(pkgName, UserHandle.ALL, false, null, null,
dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0,
res.removedInfo, true);
}
} catch (PackageManagerException e) {
res.setError("Package couldn't be installed in " + pkg.codePath, e);
}
}
...
}
scanPackageLI負責安裝,而updateSettingLI則是完成安裝后的設置信息更新。如果安裝失敗則會刪除安裝包。
我們來看 scanPackageLI 這部分代碼
public class PackageManagerService extends IPackageManager.Stub {
...
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
boolean success = false;
try {
final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,
currentTime, user);
success = true;
return res;
} finally {
if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
removeDataDirsLI(pkg.packageName);
}
}
}
...
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
...
// writer
synchronized (mPackages) {
// 驗證已注冊的ContentProvider是否有其他同名
if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
final int N = pkg.providers.size();
int i;
for (i=0; i<N; i++) {
PackageParser.Provider p = pkg.providers.get(i);
if (p.info.authority != null) {
String names[] = p.info.authority.split(";");
for (int j = 0; j < names.length; j++) {
if (mProvidersByAuthority.containsKey(names[j])) {
PackageParser.Provider other = mProvidersByAuthority.get(names[j]);
final String otherPackageName =
((other != null && other.getComponentName() != null) ?
other.getComponentName().getPackageName() : "?");
throw new PackageManagerException(
INSTALL_FAILED_CONFLICTING_PROVIDER,
"Can't install because provider name " + names[j]
+ " (in package " + pkg.applicationInfo.packageName
+ ") is already used by " + otherPackageName);
}
}
}
}
}
}
if (mPlatformPackage == pkg) {
...
} else {
// This is a normal package, need to make its data directory.
dataPath = getDataPathForPackage(pkg.packageName, 0);
if (dataPath.exists()) {
...
} else {
//invoke installer to do the actual installation
//這里創建了應用數據目錄,用于存放用戶數據
int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
pkg.applicationInfo.seinfo);
...
}
}
// We also need to dexopt any apps that are dependent on this library. Note that
// if these fail, we should abort the install since installing the library will
// result in some apps being broken.
if (clientLibPkgs != null) {
if ((scanFlags & SCAN_NO_DEX) == 0) {
for (int i = 0; i < clientLibPkgs.size(); i++) {
PackageParser.Package clientPkg = clientLibPkgs.get(i);
if (performDexOptLI(clientPkg, null /* instruction sets */, forceDex,
(scanFlags & SCAN_DEFER_DEX) != 0, false) == DEX_OPT_FAILED) {
throw new PackageManagerException(INSTALL_FAILED_DEXOPT,
"scanPackageLI failed to dexopt clientLibPkgs");
}
}
}
}
// writer
synchronized (mPackages) {
...
// 以下對四大組件進行注冊
int N = pkg.providers.size();
StringBuilder r = null;
int i;
for (i=0; i<N; i++) {
PackageParser.Provider p = pkg.providers.get(i);
p.info.processName = fixProcessName(pkg.applicationInfo.processName,
p.info.processName, pkg.applicationInfo.uid);
mProviders.addProvider(p);
...
}
...
}
}
...
}
scanPackageLI()方法主要邏輯是由 scanPackageDirtyLI() 實現。這里主要對provider沖突檢測,創建應用數據目錄,dexopt操作,四大組件注冊,權限注冊等。
3.4 安裝完成
繼續看 processPendingInstall ,安裝成功后如需要備份則會通過 BackupManagerService 進行備份:
public class PackageManagerService extends IPackageManager.Stub {
...
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
// Queue up an async operation since the package installation may take a little while.
mHandler.post(new Runnable() {
public void run() {
...
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
IBackupManager bm = IBackupManager.Stub.asInterface(
ServiceManager.getService(Context.BACKUP_SERVICE));
if (bm != null) {
if (DEBUG_INSTALL) Log.v(TAG, "token " + token
+ " to BM for possible restore");
try {
if (bm.isBackupServiceActive(UserHandle.USER_OWNER)) {
bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token);
} else {
doRestore = false;
}
} catch (RemoteException e) {
// can't happen; the backup manager is local
} catch (Exception e) {
Slog.e(TAG, "Exception trying to enqueue restore", e);
doRestore = false;
}
} else {
Slog.e(TAG, "Backup Manager not found!");
doRestore = false;
}
}
if (!doRestore) {
// No restore possible, or the Backup Manager was mysteriously not
// available -- just fire the post-install work request directly.
if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
mHandler.sendMessage(msg);
}
...
}
}
}
...
}
無論備份與否,最終則會通過 PackageHandler 發送POST_INSTALL消息,最終通過發送 Intent.ACTION_PACKAGE_ADDED 廣播,apk的安裝流程就到此結束了。
來自:http://solart.cc/2016/10/30/install_apk/