其他
Apk安装之谜
https://mp.weixin.qq.com/s/kCsIVgorKOp-ensI27VMMQ
“大家好,我是今天的主角apk,今天的给大家带来的主题是apk安装之谜,我请到了PackageManagerService、Settings、PackageInstallerSession、PackageInstallerService、InstallPackageHelper、Installer作为嘉宾,那就让嘉宾先做下自我介绍吧。”
Settings:“大家好啊,看到我的名字有可能会有人认为我是为设置app服务的,其实不然,我是为apk的安装服务的,哪些apk安装了、安装的时间信息等等我都保存着,并且会持久化到内部存储空间。”
PackageInstallerService:“大家好,我和PMS一样也是运行于systemserver进程的,一看我的名字就能知道我是和apk安装有关系的,如果谁有安装apk的需求,可以直接通过binder的方式'呼我哦',可以称呼我PIS哦。”
PackageInstallerSession:“大家好,我的名字和PIS是不是很像啊,可别听PIS忽悠啊,真正进行apk安装的工作都是由我完成的,可以称呼我为Session哦。”
InstallPackageHelper:“大家好,我的主要工作是负责apk安装中期的工作,后面到了我的工作内容的时候,会着重在介绍。”
Installer:“大家好,我是installd进程的代理,在java世界如果需要使用installd的能力的话,直接调用我即可,我会把相关的请求‘转告’给installd,我在apk的安装中也起了很大的作用。”
apk它是 Android Package 的缩写。我是一个zip格式的压缩文件,只不过为了能让大家从文件名上一眼认出我来,我的文件后缀是 .apk。
我的“归宿”就是成功的安装到各种安卓设备上某个目录,只有在那里我才能在这台设备上充分的发挥我的价值。
drwxr-xr-x 24 root root 4096 2023-08-20 21:25 system/app
Session:“安装apk第一步是需要把apk(不管apk来源于哪)进行拷贝,拷贝到 /data/app/xxxx.tmp(xxxx是一个随机的字符串)目录下面,拷贝的apk的名字一般被命名为:base.apk,拷贝完后的apk文件的路径是 /data/app/xxxx.tmp/base.apk 这样的。”
apk:“一上来就要拷贝,这一下子把我搞懵逼了,能说说为啥要拷贝吗?我的理解是拷贝会增加apk的安装时长,如果apk特别大,安装时长更会加长,不拷贝不行吗?”
Session:“第二步是对 /data/app/xxxx.tmp/base.apk 进行完整性验证。完整性验证用一句话概括就是:验证apk有没有被改过。这一步肯定是要最先进行的,只有我先确认apk是一个完整的apk才有必要进行后面的安装流程。“
apk迷惑的问到:“摘要算法,这又是啥子嘛?”
可以使用摘要算法对apk的各种文件生成摘要,这些摘要信息会写入apk。验证apk完整性的进一步思路是这样的:使用摘要算法对apk的各种文件生成摘要,如果生成的摘要与apk内存的摘要信息一致则证明apk是没有被修改过的。
从apk中拿到证书信息,拿到加密的摘要信息 从证书中用公钥对加密的摘要信息解密,解密出摘要信息 对apk的各文件用摘要算法生成摘要,并与解密出的摘要信息进行对比,如若一致则证明没有被改动,否则发生了改动。
除了验证apk的完整性外,还会从apk中的提取签名信息,签名信息保存在SigningDetails对象中,在后面的安装流程中是要用到SigningDetails信息的,如果apk没有获取到签名信息,则会停止安装(正常咱们开发的debug版的app是已经默认进行了签名)。
//文件路径: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);
}
......省略代码
}
Session:“有没有发现,前两步我对于安装的apk是知之甚少的,我不知道安装apk的包名、它的名字、版本号等基础信息。但是这些信息是非常非常重要的,因此这一步需要把这些信息解析出来,为后面的安装流程做准备。我把这一步称为解析apk,解析apk说的更具体点就是解析apk中的AndroidManifest(清单文件),从AndroidManifest文件中把包名、版本号、安装路径、是否是debug、是否是多架构、是否提取native libs等信息提取出来放入PackageLite对象。并不会提取四大组件信息、权限等信息,因为还暂时用不到这些信息,多解析这些信息就需要多花时间,我秉持一个用时才去解析的原则。”
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;
......省略其他属性
}
Session:“这一步所要做的事情是提取native libs(native libs指的是apk中的so库),提取native libs:也就是把apk中的so库提取到 /data/app/xxxx.tmp/lib/cpuabi/ (cpuabi是当前设备的cpu架构比如arm、arm64)目录下面。abi是Application Binary Interface的缩写,应用程序二进制接口。但是并不是所有的apk都包含了so,如果没有包含则不会执行此步。提取native libs会用到解析apk这一步解析出的PackageLite信息。”
lib/arm/xx.so
lib/arm/xxx.so
lib/arm/xxxx.so
lib/arm64/xx.so
lib/arm64/xxx.so
lib/arm64/xxxx.so
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());
}
}
}
}
Session:“到了版本号验证这一步了,但是这步不是必须,如果设备上已经安装了相同包名的apk,则该步是必须的,版本号验证所要做的事情非常简单:正在安装的apk的版本号是否比已经安装的apk的版本号小,小的话就停止安装。正在安装的apk的版本号从解析apk中的PackageLite拿到。”
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);
}
前期准备阶段又划分为拷贝、完整性验证、解析apk、提取native libs、版本号验证五步,每一步都在为后一步做准备。
解析apk会从/data/app/xxxx.tmp/base.apk的AndroidManifest中把包名、版本号、安装路径、是否是debug等信息提取出来放入PackageLite对象,若解析中发生错误也会停止安装。
提取native libs的时候会用到 PackageLite对象,会把/data/app/xxxx.tmp/base.apk中的so库提取到 /data/app/xxxx.tmp/lib/cpuabi/ 目录(若apk存在so库),若发生错误则也会停止安装。
版本号验证的工作内容是正在安装的apk的版本号是否比已经安装的apk的版本号小,小的话就停止安装。这步不是必须的,只有设备上已经安装了相同包名的apk才执行。
InstallPackageHelper:“大家好啊,终于轮到我出场了,安装阶段也可以称为正式安装,在这阶段才真正开始apk的安装工作。那我就来介绍下吧。”
完全解析apk
还记得解析apk那步会把apk的基础信息存放到PackageLite对象吗,这只是解析了比较少的基础信息。完全解析apk就是从/data/app/xxxx.tmp/base.apk的AndroidManifest中把所有的信息都解析出来,包含了声明的四大组件、权限、meta-data、shareLibs等,这些信息会存放在ParsedPackage对象中,如果解析发生错误,则停止安装。解析出ParsedPackage后,后面的工作都是围绕ParsedPackage展开的。
在完整性验证那步是保存了签名信息到SigningDetails对象的,如果SigningDetails不为null的话会把SigningDetails存入ParsedPackage中;否则从apk中解析出SigningDetails存入ParsedPackage。
签名验证的工作内容是对正在安装的apk的证书信息与设备上已经安装的相同包名的apk的证书信息进行对比,如果不一致,则停止安装。如果设备上不存在相同包名的apk则这一步是不会进行的。比如设备上安装了微信,如果有一个apk它的包名与微信一样,签名肯定不一样的情况下。这时候往设备上安装此apk肯定是安装不上的。
权限验证就是根据ParsedPackage里的getPermissions()方法获取的权限,来判断哪些权限是存在问题的,比如声明了只有系统app才能使用的权限,如果存在问题则停止安装。
还记得拷贝第一步的时候生成的临时目录 /data/app/xxxx.tmp/ 吗?这毕竟是个临时目录,是有必要给它一个正式的名字的,那重命名所做的事情就是把 /data/app/xxxx.tmp/ 重命名为 /data/app/~~[randomStrA]/[packageName]-[randomStrB](其中randomStrA、randomStrB是随机生成的字符串,packageName是包名),这个名字看上去确实不是很正规,但是它确实是一个非常正式的名字。
apk:“那我有个问题啊,为什么重命名的名字没有用包名,而是用一个随机字符串呢?”
/**
* 返回的目录结构样子: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;
}
当然除了上面的这些工作外,还做了尝试杀死当前同包名的app进程(如果设备上已经有相同包名的apk并且处于运行状态),构造需要移除的信息PackageRemovedInfo对象(如果设备上已经有相同包名的apk,则需要把它的信息在后面的流程中移除掉,因为这些信息毕竟是老apk的信息)。
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());
}
省略代码......
}
InstallPackageHelper:“扫描这步主要的作用就是完善ParsedPackage的信息,同时用ParsedPackage的信息创建或者更新已有的PackageSetting。关于PackageSetting还需要有请Settings来介绍下。”
每个被安装的apk都会有自己的appId,appId它是一个整数,如果在AndroidManifest中配置了android:sharedUserId则配置了相同sharedUserId的apk的appId是一样的。扫描的最后一步是为apk生成它的appId,这样被安装的apk就有了“正式身份”。
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;
}
调和这步主要是利用准备、扫描的产物来验证当前apk使用到的shared libs是否存在、真实有效、是否重复申请等,如果验证失败则停止安装,比如在apk的AndroidManifest文件中用申明了一个不存在的lib则肯定是不能安装的;同时还会创建DeletePackageAction(它会把设备上相同包名的apk(称老apk)信息包含进来)如果设备上存在老apk,创建DeletePackageAction的目的是为了在后面的安装阶段可以把老apk的信息删除。
public static Map<String, ReconciledPackage> reconcilePackages(
final ReconcileRequest request, SharedLibrariesImpl sharedLibraries,
KeySetManagerService ksms, Settings settings)
throws ReconcileFailure {
省略代码......
}
InstallPackageHelper:“提交是安装的最后一步了,提交的主要工作内容就是对上面准备、扫描、调和的产物PackageSetting和ParsedPackage提交给Settings和PMS,让它们把各自更新自己的状态。那就由它们来介绍吧。”
InstallPackageHelper郑重的对apk说:“恭喜你啊,经过安装阶段,你终于找到了你的‘归宿’ /data/app/~~[randomStrA]/[packageName]-[randomStrB] 目录,从此你就可以在这台设备上发挥你的价值了。这个目录它的user和group都是system,也就是说只有systemserver进程才有权读写执行该目录,而其他用户只能读的权限,这样就可以保证该目录的安全性。这也就是为啥在apk运行时候,是可以把该目录下的apk文件和lib下的各种so文件加载到自己进程的ClassLoader的原因。”
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 {
省略代码......
}
}
终于到了后期收尾阶段,为啥要叫后期收尾呢?是因为这一阶段所做的事情即使出现了错误也不会影响上面apk安装成功的结果,那就来看下后期收尾都做了哪些事情。
关于为什么创建app data根目录以及都创建了哪些目录可以参考installd进程,在这篇就不赘述了。创建app data根目录是委托了Installer,Installer在通过binder通信的方式让installd进程帮忙创建的。只有创建app data根目录成功后,apk才可以运行起来。
关于dex优化可以参考installd进程,同样dex优化也是委托Installer实现的,最终也是转交由installd进程帮忙实现的。dex优化即使不成功也不会影响apk的运行,但是会影响apk的运行速度。
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(称它为老apk),则在新apk安装成功后是需要把老apk删除的,删除过程也同样是委托Installer,最终转交由installd进程来实现。即使老apk删除失败也不会影响新apk。
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);
}
}
既然一个apk安装成功了,那肯定是需要通知关注者的,采用的方式是发广播,比如桌面在收到安装成功的广播后,修改正在安装apk的状态。
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运行性能上的提升。 发送安装成功广播。