Apk安装之谜
The following article is from 牛晓伟 Author 牛晓伟
Android系统进程系列的前五篇文章如下:
Android系统native进程之我是init进程
Android系统native进程之属性能力的设计魅力
Android系统native进程之进程杀手--lmkd
Android系统native进程之日志系统--logd、logcat
Android系统native进程之我是installd进程
PackageManagerService:“大家对我一定非常的了解了,我是一个服务管理所有的已经安装的apk,运行于systemserver进程,可以称呼我PMS哦。”
Settings:“大家好啊,看到我的名字有可能会有人认为我是为设置app服务的,其实不然,我是为apk的安装服务的,哪些apk安装了、安装的时间信息等等我都保存着,并且会持久化到内部存储空间。”
PackageInstallerService:“大家好,我和PMS一样也是运行于systemserver进程的,一看我的名字就能知道我是和apk安装有关系的,如果谁有安装apk的需求,可以直接通过binder的方式'呼我哦',可以称呼我PIS哦。”
PackageInstallerSession:“大家好,我的名字和PIS是不是很像啊,可别听PIS忽悠啊,真正进行apk安装的工作都是由我完成的,可以称呼我为Session哦。”
InstallPackageHelper:“大家好,我的主要工作是负责apk安装中期的工作,后面到了我的工作内容的时候,会着重在介绍。”
Installer:“大家好,我是installd进程的代理,在java世界如果需要使用installd的能力的话,直接调用我即可,我会把相关的请求‘转告’给installd,我在apk的安装中也起了很大的作用。”
既然嘉宾都介绍完自己了,因为今天我是主角,我有主角光环,那我非常有必要隆重、浓墨重彩的介绍下我自己,让大家对我有一个非常深刻的了解。
drwxr-xr-x 39 root root 4096 2023-08-20 21:26 system/priv-app
drwxr-xr-x 24 root root 4096 2023-08-20 21:25 system/app
关于我的介绍就到此吧,进入咱们今天的正题吧。
那我们就按上面的三阶段来给大家揭开apk安装的谜底吧。
拷贝
完整性验证
从apk中拿到证书信息,拿到加密的摘要信息。 从证书中用公钥对加密的摘要信息解密,解密出摘要信息。 对apk的各文件用摘要算法生成摘要,并与解密出的摘要信息进行对比,如若一致则证明没有被改动,否则发生了改动。
//文件路径:frameworks/base/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
//获取签名信息,签名信息存储在SigningDetails
public static ParseResult<SigningDetails> getSigningDetails(ParseInput input,
String baseCodePath, boolean skipVerify, boolean isStaticSharedLibrary,
@NonNull SigningDetails existingSigningDetails, int targetSdk) {
省略代码......
//跳过验证,走这
if (skipVerify) {
省略代码......
} else {
//验证并且返回签名信息,会对apk的完整性进行校验,并且返回签名信息
verified = ApkSignatureVerifier.verify(input, baseCodePath, minSignatureScheme);
}
......省略代码
}
解析apk
//文件路径: frameworks/base/core/java/android/content/pm/parsing/PackageLite.java
public class PackageLite {
//包名
private final @NonNull String mPackageName;
//base apk的路径
private final @NonNull String mBaseApkPath;
//版本号
private final int mVersionCode;
//app是否是debug版本
private final boolean mDebuggable;
//是否是多架构(32位和64位)
private final boolean mMultiArch;
//是否提取native libs
private final boolean mExtractNativeLibs;
......省略其他属性
}
提取native libs
//base.apk,该apk中包含了两个abi:arm、arm64,(为了减小apk的大小,现在的apk都只保留一个abi)
lib/arm/xx.so
lib/arm/xxx.so
lib/arm/xxxx.so
lib/arm64/xx.so
lib/arm64/xxx.so
lib/arm64/xxxx.so
//文件路径:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java
private void parseApkAndExtractNativeLibraries() throws PackageManagerException {
synchronized (mLock) {
省略代码......
final PackageLite result;
if (!isApexSession()) {
//走这,解析apk信息
result = getOrParsePackageLiteLocked(stageDir, /* flags */ 0);
} else {
result = getOrParsePackageLiteLocked(mResolvedBaseFile, /* flags */ 0);
}
if (result != null) {
mPackageLite = result;
if (!isApexSession()) {
省略代码......
//提取so库
extractNativeLibraries(
mPackageLite, stageDir, params.abiOverride, mayInheritNativeLibs());
}
}
}
}
版本号验证
//文件路径:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
Pair<Integer, String> verifyReplacingVersionCode(PackageInfoLite pkgLite,
long requiredInstalledVersionCode, int installFlags) {
if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
return verifyReplacingVersionCodeForApex(
pkgLite, requiredInstalledVersionCode, installFlags);
}
String packageName = pkgLite.packageName;
synchronized (mPm.mLock) {
省略代码......
//dataOwnerPkg代表设备已经安装对应的apk了
if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) {
//只有debug版本才允许版本降级
if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
dataOwnerPkg.isDebuggable())) {
try {
//检测是否存在版本降级,是的话会报错
PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
} catch (PackageManagerException e) {
String errorMsg = "Downgrade detected: " + e.getMessage();
Slog.w(TAG, errorMsg);
return Pair.create(
PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
}
}
}
}
return Pair.create(PackageManager.INSTALL_SUCCEEDED, null);
}
总结
前期准备的各步都能正常执行的话,就进入正式的安装阶段,那我们来看下安装阶段的内容。
准备 (Prepare)
保存签名
签名验证
权限验证
重命名
//文件路径:services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
/**
* 返回的目录结构样子:targetDir/~~[randomStrA]/[packageName]-[randomStrB]
*/
public static File getNextCodePath(File targetDir, String packageName) {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[16];
File firstLevelDir;
do {
random.nextBytes(bytes);
String firstLevelDirName = RANDOM_DIR_PREFIX
+ Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
firstLevelDir = new File(targetDir, firstLevelDirName);
} while (firstLevelDir.exists());
random.nextBytes(bytes);
String dirName = packageName + RANDOM_CODEPATH_PREFIX + Base64.encodeToString(bytes,
Base64.URL_SAFE | Base64.NO_WRAP);
final File result = new File(firstLevelDir, dirName);
if (DEBUG && !Objects.equals(tryParsePackageName(result.getName()), packageName)) {
throw new RuntimeException(
"codepath is off: " + result.getName() + " (" + packageName + ")");
}
return result;
}
总结
private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
throws PrepareFailure {
省略代码......
final ParsedPackage parsedPackage;
try (PackageParser2 pp = mPm.mInjector.getPreparingPackageParser()) {
//完全解析apk
parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false); //niu 解析apk中更具体的信息 放入ParsedPackage
AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
} catch (PackageManagerException e) {
throw new PrepareFailure("Failed parse during installPackageLI", e);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
省略代码......
//设置签名信息
if (args.mSigningDetails != SigningDetails.UNKNOWN) {
parsedPackage.setSigningDetails(args.mSigningDetails);
} else {
final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
input, parsedPackage, false /*skipVerify*/);
if (result.isError()) {
throw new PrepareFailure("Failed collect during installPackageLI",
result.getException());
}
parsedPackage.setSigningDetails(result.getResult());
}
省略代码......
}
//文件路径:Settings.java
boolean registerAppIdLPw(PackageSetting p, boolean forceNew) throws PackageManagerException {
final boolean createdNew;
Slog.i(TAG, "niulog install registerAppIdLPw p:" + p + " forceNew:" + forceNew + " appid:" + p.getAppId());
if (p.getAppId() == 0 || forceNew) {
// Assign new user ID
p.setAppId(mAppIds.acquireAndRegisterNewAppId(p));
createdNew = true;
} else {
// Add new setting to list of user IDs
createdNew = mAppIds.registerExistingAppId(p.getAppId(), p, p.getPackageName());
}
if (p.getAppId() < 0) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Package " + p.getPackageName() + " could not be assigned a valid UID");
throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
"Package " + p.getPackageName() + " could not be assigned a valid UID");
}
return createdNew;
}
调和 (Reconcile)
//文件名:ReconcilePackageUtils.java
public static Map<String, ReconciledPackage> reconcilePackages(
final ReconcileRequest request, SharedLibrariesImpl sharedLibraries,
KeySetManagerService ksms, Settings settings)
throws ReconcileFailure {
省略代码......
}
提交 (Commit)
总结
//文件:InstallPackageHelper.java
private void installPackagesLI(List<InstallRequest> requests) {
final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size());
final Map<String, InstallArgs> installArgs = new ArrayMap<>(requests.size());
final Map<String, PackageInstalledInfo> installResults = new ArrayMap<>(requests.size());
final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size());
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
boolean success = false;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");
for (InstallRequest request : requests) {
// TODO(b/109941548): remove this once we've pulled everything from it and into
// scan, reconcile or commit.
final PrepareResult prepareResult;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
//1.prepare阶段,会解析apk中的信息主要是AndroidManifest,解析出来的实体是ParsedPackage(解析的信息更全包含了四大组件等),若不是一个正确的apk则不会继续下面的步骤;若是正确的apk,则会对apk的签名、shareuserid以及是替换老apk还是新apk做处理
prepareResult =
preparePackageLI(request.mArgs, request.mInstallResult);
} catch (PrepareFailure prepareFailure) {
request.mInstallResult.setError(prepareFailure.error,
prepareFailure.getMessage());
request.mInstallResult.mOrigPackage = prepareFailure.mConflictingPackage;
request.mInstallResult.mOrigPermission = prepareFailure.mConflictingPermission;
return;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
request.mInstallResult.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
request.mInstallResult.mInstallerPackageName =
request.mArgs.mInstallSource.installerPackageName;
final String packageName = prepareResult.mPackageToScan.getPackageName();
Slog.i(TAG,"niulog install installPackagesLI prepare request = "+request+" packageName = "+packageName);
prepareResults.put(packageName, prepareResult);
installResults.put(packageName, request.mInstallResult);
installArgs.put(packageName, request.mArgs);
try {
// 2.扫描阶段,扫描阶段主要是构造或者使用原有的PkgSetting
final ScanResult result = scanPackageTracedLI(
prepareResult.mPackageToScan, prepareResult.mParseFlags,
prepareResult.mScanFlags, System.currentTimeMillis(),
request.mArgs.mUser, request.mArgs.mAbiOverride);
if (null != preparedScans.put(result.mPkgSetting.getPkg().getPackageName(),
result)) {
request.mInstallResult.setError(
PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
"Duplicate package "
+ result.mPkgSetting.getPkg().getPackageName()
+ " in multi-package install request.");
return;
}
if (!checkNoAppStorageIsConsistent(
result.mRequest.mOldPkg, result.mPkgSetting.getPkg())) {
// TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible
// signatures. Is there a better error code?
request.mInstallResult.setError(
INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Update attempted to change value of "
+ PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
return;
}
createdAppId.put(packageName, optimisticallyRegisterAppId(result)); //niu 生成或者使用原有appid
versionInfos.put(result.mPkgSetting.getPkg().getPackageName(),
mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg()));
Slog.i(TAG,"niulog install installPackagesLI scan request = "+request+" ScanResult.result = "+result+" versionInfo:"+mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg()));
} catch (PackageManagerException e) {
request.mInstallResult.setError("Scanning Failed.", e);
return;
}
}
ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs,
installResults, prepareResults,
Collections.unmodifiableMap(mPm.mPackages), versionInfos); //niu 用prepare和scan阶段的数据构造ReconcileRequest
CommitRequest commitRequest = null;
synchronized (mPm.mLock) {
Map<String, ReconciledPackage> reconciledPackages;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
// 调和阶段
reconciledPackages = ReconcilePackageUtils.reconcilePackages(
reconcileRequest, mSharedLibraries,
mPm.mSettings.getKeySetManagerService(), mPm.mSettings);
printPkg(reconciledPackages,"niulog install installPackagesLI reconcile");
} catch (ReconcileFailure e) {
for (InstallRequest request : requests) {
request.mInstallResult.setError("Reconciliation failed...", e);
}
return;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "commitPackages");
commitRequest = new CommitRequest(reconciledPackages,
mPm.mUserManager.getUserIds()); //niu 构建CommitRequest(把前面各种阶段的信息都收集起来)
//进入commit阶段
commitPackagesLocked(commitRequest);
success = true;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
} finally {
省略代码......
}
}
创建app data根目录
dex优化
//文件:InstallPackageHelper.java
private void executePostCommitSteps(CommitRequest commitRequest) {
final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
for (ReconciledPackage reconciledPkg : commitRequest.mReconciledPackages.values()) {
final boolean instantApp = ((reconciledPkg.mScanResult.mRequest.mScanFlags
& SCAN_AS_INSTANT_APP) != 0);
final AndroidPackage pkg = reconciledPkg.mPkgSetting.getPkg();
final String packageName = pkg.getPackageName();
final String codePath = pkg.getPath();
final boolean onIncremental = mIncrementalManager != null
&& isIncrementalPath(codePath);
省略代码......
//创建app data根目录
mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0); //niu 创建 data目录
省略代码......
final boolean performDexopt =
(!instantApp || android.provider.Settings.Global.getInt(
mContext.getContentResolver(),
android.provider.Settings.Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
&& !pkg.isDebuggable()
&& (!onIncremental)
&& dexoptOptions.isCompilationEnabled();
//并不是所有的apk都需要dex优化,如果需要优化,进入下面逻辑
if (performDexopt) {
省略代码......
//开始优化
mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
null /* instructionSets */,
mPm.getOrCreateCompilerPackageStats(pkg),
mDexManager.getPackageUseInfoOrDefault(packageName),
dexoptOptions);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
省略代码......
}
PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
incrementalStorages);
}
移除已有apk
//文件:Installer.java
public void rmPackageDir(String packageName, String packageDir) throws InstallerException {
if (!checkBeforeRemote()) return;
BlockGuard.getVmPolicy().onPathAccess(packageDir);
try {
mInstalld.rmPackageDir(packageName, packageDir);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
发送安装成功广播
//文件:PackageInstallerSession.java
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
sendUpdateToRemoteStatusReceiver(returnCode, msg, extras);
synchronized (mLock) {
mFinalStatus = returnCode;
mFinalMessage = msg;
}
final boolean success = (returnCode == INSTALL_SUCCEEDED);
final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING);
if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts()) {
//收集apk的信息,把这些信息通过广播发送出去
mPm.sendSessionCommitBroadcast(generateInfoScrubbed(true /*icon*/), userId);
}
mCallback.onSessionFinished(this, success);
if (isDataLoaderInstallation()) {
logDataLoaderInstallationSession(returnCode);
}
}
不管apk是通过adb安装的(apk存储于PC的磁盘)还是应用市场安装的(apk存储于设备),首先apk会被拷贝到 /data/app/xxx.tmp目录下面(xxx是一个随机生成的字符串)。 在经过重重的验证、校验(签名、版本号),/data/app/xxx.tmp 目录会重命名为 /data/app/[randomStrA]/[packageName]-[randomStrB] 目录,也就是被拷贝的apk最终路径是 /data/app/[randomStrA]/[packageName]-[randomStrB]/base.apk 。同时会为apk生成一个唯一的id又称appid。 解析apk的AndroidManifest中的内容为ParsedPackage,ParsedPackage中的权限等信息经过验证通过后,ParsedPackage传递给PMS,这样其他使用者比如ActivityManagerService就可以从PMS获取刚安装apk的信息了。 刚安装的apk的安装信息比如包名、版本、签名证书、安装时间等会存储到PackageSetting,PackageSetting会传递给Settings,Settings会把它持久化到packages.xml文件。 创建app data根目录,app data根目录是apk运行期间数据存储的根目录,并且app data根目录只有当前apk程序有读写执行权,其他不用没有任何权限。 对apk的dex进行优化,优化即使不成功也不影响apk的安装,dex优化可以保证app运行性能上的提升。 发送安装成功广播。
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!