本文是 Android 系統(tǒng)學(xué)習(xí)系列文章中的第一章節(jié)的內(nèi)容,介紹了 PackageManagerService 在啟動時如何去加載已安裝的應(yīng)用,通過一個新的應(yīng)用是如何在 PackageManagerService 的幫助下完成安裝過程的。對此系列感興趣的同學(xué),可以收藏這個鏈接 Android 系統(tǒng)學(xué)習(xí),也可以點(diǎn)擊 RSS訂閱 進(jìn)行訂閱。
接下來的分析,如果沒有特別提及,是基于 SDK-23 版本進(jìn)行的。
系統(tǒng)解析安裝包過程
自行解析 Android APK 信息該如何入手呢?從這里入手的話,就得完整地知道系統(tǒng)是如何完成這個過程的,那么自然而然地就能想到通過 PackageManageService 入手進(jìn)行分析,畢竟是掌管所有應(yīng)用的大執(zhí)行官。通過前面 Binder 系列的學(xué)習(xí),我們了解到 PackageManageService 是將自己注入到 SystemManager 中去的,其后其他應(yīng)用就可以通過 SystemServer 來訪問 PackageManageService 了。
在 SystemServer 啟動后,執(zhí)行了 PackageManagerService 的 main 方法,后面的 isFirstBoot 方法是避免 PackageManangerService 被重復(fù)啟動。
// Start the package manager.
Slog.i(TAG, "Package Manager");
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
mFirstBoot = mPackageManagerService.isFirstBoot();
mPackageManager = mSystemContext.getPackageManager();
main 方法中,調(diào)用了構(gòu)造函數(shù),并將自己注入到 SystemServer 去,看起來大部分邏輯都在構(gòu)造函數(shù)里,接著往下分析。
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
ServiceManager.addService("package", m);
return m;
}
構(gòu)造函數(shù)相當(dāng)復(fù)雜,這里只說明,對我們分析有用的東西。
// ... other code
// Collect vendor overlay packages.
// (Do this before scanning any apps.)
// For security and version matching reason, only consider
// overlay packages if they reside in VENDOR_OVERLAY_DIR.
File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
// Find base frameworks (resource packages without code).
scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED,
scanFlags | SCAN_NO_DEX, 0);
// Collected privileged system packages.
final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);
// Collect ordinary system packages.
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
// Collect all vendor packages.
File vendorAppDir = new File("/vendor/app");
try {
vendorAppDir = vendorAppDir.getCanonicalFile();
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
// Collect all OEM packages.
final File oemAppDir = new File(Environment.getOemDirectory(), "app");
scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
// ... other code
如上代碼,可以看到 PackageManagerService 在構(gòu)造函數(shù)的時候,會掃描對應(yīng)目錄下的 APK,保證這些 APK 的信息被預(yù)先加載進(jìn)來。我在 root 的手機(jī)下,在 /system/priv-app 目錄下截圖,這些就是系統(tǒng)高優(yōu)先級加載的系統(tǒng)應(yīng)用。
接下來看看 scanDirLI 這個函數(shù),看看這里面進(jìn)行了什么勾當(dāng)。
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = dir.listFiles();
// ...
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
try {
scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
} catch (PackageManagerException e) {
// ...
}
}
}
看起來,這里面只是做了對 APK 文件的過濾,并沒有實(shí)際對于 APK 解析的代碼,那么接著看看 scanPackageLI 的實(shí)現(xiàn)。
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
long currentTime, UserHandle user) throws PackageManagerException {
// ...
final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(scanFile, parseFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
}
// ...
}
scanPackageLI 的實(shí)現(xiàn)也相對復(fù)雜,比如設(shè)計對供應(yīng)商內(nèi)置 APP 的更新邏輯等等,這里只關(guān)心是如何解析 APK 的,那么看看 parsePackage
是怎么實(shí)現(xiàn)的,我們能否通過 parsePackage
來達(dá)到我們自動解析 APK 的目的了?
parsePackage
方法,在不同 SDK 的版本里面實(shí)現(xiàn)各不一樣,有興趣的讀者可以從這個鏈接 http://grepcode.com/search?query=packageParser 里面查看各個版本的實(shí)現(xiàn),這里只看 SDK 23 版本的實(shí)現(xiàn),這里針對 APK 目錄和單一的 APK 文件分別進(jìn)行處理。
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
} else {
return parseMonolithicPackage(packageFile, flags);
}
}
接著看看 parseMonolithicPackage 的實(shí)現(xiàn),前面的 mOnlyCoreApps 參數(shù)是對 Core APP 進(jìn)行的處理,這邊可以不看,主要看 parseBaseApk 方法的實(shí)現(xiàn)。
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
if (mOnlyCoreApps) {
final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (!lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Not a coreApp: " + apkFile);
}
}
final AssetManager assets = new AssetManager();
try {
final Package pkg = parseBaseApk(apkFile, assets, flags);
pkg.codePath = apkFile.getAbsolutePath();
return pkg;
} finally {
IoUtils.closeQuietly(assets);
}
}
在 parseBaseApk 中開始根據(jù)各個具體的節(jié)點(diǎn)(如 Application、Activity 等等),進(jìn)行解析,最后得到整個 Package 的信息。
private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {
// ...
if (tagName.equals("application")) {
if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) {
return null;
}
} else if (tagName.equals("overlay")) {
// ...
} else if (tagName.equals("permission")) {
if (parsePermission(pkg, res, parser, attrs, outError) == null) {
return null;
}
}
// ...
return pkg;
}
這些得到的各種信息,最后都會存儲在 PackageManagerService 中,這些信息就此保留下來,可用用來響應(yīng)各種 IntentFilter ,等待啟動命令。
經(jīng)過上面的代碼分析,可以看出,如果想要獲取相應(yīng)的包信息,可以調(diào)用 Package.parsePackage 方法來進(jìn)行解析,這里唯一需要注意的地方在于這個方法的簽名在不同 SDK 版本是不同的,需要針對不同版本做處理。
APK 包安裝過程
我們知道怎么獲取包信息了,但這還不夠,我們在安裝應(yīng)用的時候,還彈出了一個安裝界面,這個安裝界面背后有什么邏輯了?這些邏輯應(yīng)該也對我們實(shí)現(xiàn)加載插件很有幫助吧,我們來仔細(xì)地分析下。
實(shí)際在處理安裝應(yīng)用 Intent 的是 PackageInstallerActivity,但這個類廠商可以隨意修改,這個類也并沒有在 android.jar 中,這里就不做分析了。PackageInstallerActivity 在安裝過程中,實(shí)際調(diào)用的是 ApplicationPackageManager 里面的代碼。
private void installCommon(Uri packageURI,
PackageInstallObserver observer, int flags, String installerPackageName,
VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
if (!"file".equals(packageURI.getScheme())) {
throw new UnsupportedOperationException("Only file:// URIs are supported");
}
if (encryptionParams != null) {
throw new UnsupportedOperationException("ContainerEncryptionParams not supported");
}
final String originPath = packageURI.getPath();
try {
mPM.installPackage(originPath, observer.getBinder(), flags, installerPackageName,
verificationParams, null);
} catch (RemoteException ignored) {
}
}
在 ApplicationPackageManager 中是通過 Binder 機(jī)制調(diào)用了 PackageManagerService 中的 installPackage 方法,讓我們一探究竟。
@Override
public void installPackage(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, VerificationParams verificationParams,
String packageAbiOverride) {
installPackageAsUser(originPath, observer, installFlags, installerPackageName,
verificationParams, packageAbiOverride, UserHandle.getCallingUserId());
}
接著調(diào)用了 installPackageAsUser 方法。
public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, VerificationParams verificationParams,
String packageAbiOverride, int userId) {
final File originFile = new File(originPath);
// 權(quán)限校驗
final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(origin, null, observer, installFlags, installerPackageName,
null, verificationParams, user, packageAbiOverride, null);
mHandler.sendMessage(msg);
}
原來是通過 Handler 方式發(fā)送消息的,那么看看 PackageHandler 是如何處理這個消息的。在接受到 INIT_COPY 消息后,將要安裝的參數(shù)信息加入到 PendingInstalls 中去,如果是第一個安裝,還需要發(fā)送 MCS_BOUND 消息,用于觸發(fā)實(shí)際安裝過程。
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;
}
在 MCS_BOUND 消息中取出第一個安裝請求,并調(diào)用 startCopy 方法。
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
if (params.startCopy()) {
// ...
}
// ...
}
在 startCopy 中調(diào)用 handleStartCopy 方法,由于這個類,需要與 MCS (MediaContainerService) 進(jìn)行通信,有可能發(fā)生異常,因而這里設(shè)置了重試機(jī)制。
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;
}
handleReturnCode();
接著看看 handleStartCopy 里面是如何進(jìn)行的,為啥還有可能失敗?
public void handleStartCopy() throws RemoteException {
// 安裝在哪里?
final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
packageAbiOverride);
// 如果安裝空間不夠了,嘗試釋放一些空間
// 校驗等邏輯
final InstallArgs args = createInstallArgs(this);
// ...
args.copyApk();
}
private InstallArgs createInstallArgs(InstallParams params) {
if (params.move != null) {
return new MoveInstallArgs(params);
} else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
return new AsecInstallArgs(params);
} else {
return new FileInstallArgs(params);
}
}
看起來失敗的可能性,大多來自于 Media 服務(wù),空間不足,當(dāng)時不能寫等等都可能導(dǎo)致失敗。在空間判斷、校驗通過后,根據(jù)不同的情況創(chuàng)建不同的 InstallArgs,這里只看 FileInstallArgs。copyApk 的主要任務(wù)是拷貝 APK 文件和對應(yīng)的 lib 文件到 /data/app/{packageName}
目錄下。
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
// ...
final IParcelFileDescriptorFactory target = new IParcelFileDescriptorFactory.Stub() {
@Override
public ParcelFileDescriptor open(String name, int mode) throws RemoteException {
if (!FileUtils.isValidExtFilename(name)) {
throw new IllegalArgumentException("Invalid filename: " + name);
}
try {
final File file = new File(codeFile, name);
final FileDescriptor fd = Os.open(file.getAbsolutePath(),
O_RDWR | O_CREAT, 0644);
Os.chmod(file.getAbsolutePath(), 0644);
return new ParcelFileDescriptor(fd);
} catch (ErrnoException e) {
throw new RemoteException("Failed to open: " + e.getMessage());
}
}
};
ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
// ...
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);
}
}
在 handleStartCopy 后,就可以繼續(xù)執(zhí)行 handleReturnCode 后的代碼。這部分會不會和前面 PackageManageService 在啟動時候掃描系統(tǒng) APK 的邏輯相同了?讓我們拭目以待。
@Override
void handleReturnCode() {
// If mArgs is null, then MCS couldn't be reached. When it
// reconnects, it will try again to install. At that point, this
// will succeed.
if (mArgs != null) {
processPendingInstall(mArgs, mRet);
}
}
接著看 processPendingInstall 的實(shí)現(xiàn)。
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
args.doPreInstall(res.returnCode);
synchronized (mInstallLock) {
installPackageLI(args, res);
}
args.doPostInstall(res.returnCode, res.uid);
}
installPackageLI 中會一些列復(fù)雜的邏輯,這里只看針對新安裝包的邏輯。
if (replace) {
replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
installerPackageName, volumeUuid, res);
} else {
installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
args.user, installerPackageName, volumeUuid, res);
}
在 installNewPackageLI 中會繼續(xù)調(diào)用 scanPackageLI 方法,這和第一章節(jié)講述的一直,這里就不再贅述了。
try {
PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags,
System.currentTimeMillis(), user);
updateSettingsLI(newPackage, installerPackageName, volumeUuid, null, null, res, user);
// 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);
}
可能讀者會想安裝包的資源怎么處理的?總不會只處理 lib 文件和 dex 文件吧,這是前面一個疏漏的地方,在文末進(jìn)行下簡單的補(bǔ)充。
在 Packageparser 進(jìn)行解析的時候,會通過 AssetMananger 進(jìn)行資源的加載。
XmlResourceParser parser = null;
AssetManager assmgr = null;
Resources res = null;
boolean assetError = true;
try {
assmgr = new AssetManager();
int cookie = assmgr.addAssetPath(mArchiveSourcePath);
if (cookie != 0) {
res = new Resources(assmgr, metrics, null);
assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Build.VERSION.RESOURCES_SDK_INT);
parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
assetError = false;
} else {
VLog.w(TAG, "Failed adding asset path:" + mArchiveSourcePath);
}
} catch (Exception e) {
VLog.w(TAG, "Unable to read AndroidManifest.xml of " + mArchiveSourcePath, e);
}
文檔信息
- 版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名(創(chuàng)意共享3.0許可證)
- 發(fā)表日期:2016年8月5日
- 社交媒體:weibo.com/woaitqs
- Feed訂閱:www.woaitqs.cc/feed.xml