PackageManagerService分析起来存在一些问题,由于我们对一些功能不甚了解,想一口吃一个胖子的方式分析下来还是比较困难的,所以我们采用庖丁解牛的方式,将Pms拆分成几个功能来进行分析。今天我们的入手点就是来分析下PackageManagerService如何确定应用安装的位置。
经过分析,我们发现这部分代码是非常混乱的,估计google的开发人员对这段代码也感到头疼,为了兼顾历史问题,代码写的有很多前后矛盾的地方。还有很多代码其实已经失去了作用。 我们这里不考虑MultiPackage情况的apk,以Android 10 代码为例进行分析。经过我分析后发现Android 10 安装应用要考虑的因素比较多,总结起来基本上有以下几点:
- 系统应用只允许安装到/data分区。
- 分区空间要足够。
- 覆盖安装要保持和原有安装在同一个分区。
- 是否允许第三方应用安装到/data分区。
- 是否强制允许安装应用到外置存储。
- AndroidManifest.xml里面的installLocation 标签。
auto, internalOnly,preferExternal
前边5条比较好理解,后面的installLocation参考又分为几种情况, 在Android10中,默认的installLocation是internalOnly, 也就是只允许应用安装到/data分区,但是需要考虑之前是否已经安装过这个应用,如果已经在其他分区安装过,则可能发生不兼容,auto 和 preferExternal都是参考值,目前Android已经不在允许将应用直接安装到主存储,只允许将应用安装到内置存储和内置扩展存储。这是基于安全考虑,另外其实目前的主存储也是从内置存储或者扩展的内置存储上使用fuse划分出来的一个目录,二者其实是共用存储空间的,所以安装到主存储上其实意义不大,所以preferExternal标签其实已经被架空。
我们分析安装代码有两个入口,1 是 应用商店程序, 2是pm install命令,由于pm install 更简单直接,可以省去一些策略的分析,所以我们从pm install命令入手, pm install 最终会调用frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java类的runInstall方法,该方法解析如下两个安装位置相关的参数。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
case "-f":
sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL;
case "--force-uuid":
sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
sessionParams.volumeUuid = getNextArg();
if ("internal".equals(sessionParams.volumeUuid)) {
sessionParams.volumeUuid = null;
}
-f 指定安装到内部存储(/data和/mnt/expand/${uuid})。
--force-uuid 指定强制安装的分区(这也是内置分区的卷)。
这里并没有安装到主存储的参数,所以要想安装到外置存储只能通过AndroidManifest.xml文件中的installLocation的preferExternal标志。我们来大体看下runInstall函数,知道安装应用的基本流程。
private int runInstall() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
// 1 准备安装参数
final InstallParams params = makeInstallParams();
final String inPath = getNextArg();
setParamsSize(params, inPath);
// 2 创建session
final int sessionId = doCreateSession(params.sessionParams,
params.installerPackageName, params.userId);
boolean abandonSession = true;
try {
if (inPath == null && params.sessionParams.sizeBytes == -1) {
pw.println("Error: must either specify a package size or an APK file");
return 1;
}
final boolean isApex =
(params.sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0;
String splitName = "base." + (isApex ? "apex" : "apk");
// 3 写入安装应用
if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
// 4 提交session
if (doCommitSession(sessionId, false /*logSuccess*/)
!= PackageInstaller.STATUS_SUCCESS) {
return 1;
}
abandonSession = false;
pw.println("Success");
return 0;
} finally {
if (abandonSession) {
try {
doAbandonSession(sessionId, false /*logSuccess*/);
} catch (Exception ignore) {
}
}
}
}
从上面代码我们可以知道,安装主要和Pms交互的三个步骤
- 创建session
- 写入apk
- 提交session
这里的session是只PackageInstallerSession, 创建PackageInstallerSession的服务为frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java类, 创建session的代码如下,我们截取部分和安装位置相关的代码。
frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java
private int createSessionInternal(SessionParams params, String installerPackageName, int userId) {
......
// If caller requested explicit location, sanity check it, otherwise
// resolve the best internal or adopted location.
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
if (!PackageHelper.fitsOnInternal(mContext, params)) {
throw new IOException("No suitable internal storage available");
}
} else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
// For now, installs to adopted media are treated as internal from
// an install flag point-of-view.
params.installFlags |= PackageManager.INSTALL_INTERNAL;
} else {
params.installFlags |= PackageManager.INSTALL_INTERNAL;
// Resolve best location for install, based on combination of
// requested install flags, delta size, and manifest settings.
final long ident = Binder.clearCallingIdentity();
try {
params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
......
......
}
这里的三种情况,铁了心的将params.installFlags都设置成了PackageManager.INSTALL_INTERNAL,也就是不在允许apk安装到主存储。这里的三个分支解析如下:
- 如果本来就指定了PackageManager.INSTALL_INTERNAL标志,则表示将应用安装到/data分区, 如果/data分区空间不满足,只能抛出异常给调用方处理。
- PackageManager.INSTALL_FORCE_VOLUME_UUID 标志,现在也会忽略该标志和指定的分区id, 使用/data分区来安装。
- 这种情况也就是没有使用PackageManager.INSTALL_INTERNAL和PackageManager.INSTALL_FORCE_VOLUME_UUID标志来安装,允许使用其他扩展的内置存储安装,所以调用PackageHelper.resolveInstallVolume(mContext, params)来进行解析。所以即使安装程序指定了volumeId也会被忽略。
我们展开来看下第三种情况,没有指定卷的情况下如何处理。
@VisibleForTesting
public static String resolveInstallVolume(Context context, SessionParams params,
TestableInterface testInterface) throws IOException {
final StorageManager storageManager = testInterface.getStorageManager(context);
final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context);
final boolean allow3rdPartyOnInternal =
testInterface.getAllow3rdPartyOnInternalConfig(context);
// TODO: handle existing apps installed in ASEC; currently assumes
// they'll end up back on internal storage
ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context,
params.appPackageName);
// Figure out best candidate volume, and also if we fit on internal
final ArraySet<String> allCandidates = new ArraySet<>();
boolean fitsOnInternal = false;
VolumeInfo bestCandidate = null;
long bestCandidateAvailBytes = Long.MIN_VALUE;
for (VolumeInfo vol : storageManager.getVolumes()) {
if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
final boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id);
final UUID target = storageManager.getUuidForPath(new File(vol.path));
final long availBytes = storageManager.getAllocatableBytes(target,
translateAllocateFlags(params.installFlags));
if (isInternalStorage) {
fitsOnInternal = (params.sizeBytes <= availBytes);
}
if (!isInternalStorage || allow3rdPartyOnInternal) {
if (availBytes >= params.sizeBytes) {
allCandidates.add(vol.fsUuid);
}
if (availBytes >= bestCandidateAvailBytes) {
bestCandidate = vol;
bestCandidateAvailBytes = availBytes;
}
}
}
}
// 1 上面allCandidates表示空间充足的PRIVATE分区, allCandidates表示空间充足的候选分区, bestCandidate表示空间最充足的候选分区,fitsOnInternal表示/data分区是否空间充足。注意这里如果第三方应用不允许安装到/data分区,allCandidates和bestCandidate不会包含/data分区。
// 2 系统应用只允许安装在/data分区,/data分区如果空间不足,抛出异常
// System apps always forced to internal storage
if (existingInfo != null && existingInfo.isSystemApp()) {
if (fitsOnInternal) {
return StorageManager.UUID_PRIVATE_INTERNAL;
} else {
throw new IOException("Not enough space on existing volume "
+ existingInfo.volumeUuid + " for system app " + params.appPackageName
+ " upgrade");
}
}
// 3 没有强制允许安装到扩展分区的情况,并且有INSTALL_LOCATION_INTERNAL_ONLY标志表示有强烈愿望安装到/data分区
// If app expresses strong desire for internal storage, honor it
if (!forceAllowOnExternal
&& params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
// 3.1 已经安装到了扩展分区,不允许move
if (existingInfo != null && !Objects.equals(existingInfo.volumeUuid,
StorageManager.UUID_PRIVATE_INTERNAL)) {
throw new IOException("Cannot automatically move " + params.appPackageName
+ " from " + existingInfo.volumeUuid + " to internal storage");
}
//3.2 不允许第三方应用安装到/data分区, 直接抛出异常。
if (!allow3rdPartyOnInternal) {
throw new IOException("Not allowed to install non-system apps on internal storage");
}
//3.3 /data分区存储空间满足,安装到/data分区。空间不满足抛出异常。
if (fitsOnInternal) {
return StorageManager.UUID_PRIVATE_INTERNAL;
} else {
throw new IOException("Requested internal only, but not enough space");
}
}
// 4 覆盖安装
// If app already exists somewhere, we must stay on that volume
if (existingInfo != null) {
// 4.1 原来也安装在/data分区,并且空间足够则允许覆盖安装到/data分区。非allow3rdPartyOnInternal的情况/data分区不在allCandidates中,所以有这个分支的存在。
if (Objects.equals(existingInfo.volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)
&& fitsOnInternal) {
return StorageManager.UUID_PRIVATE_INTERNAL;
// 4.2 如果原有安装的扩展分区空间满足,则使用扩展分区。
} else if (allCandidates.contains(existingInfo.volumeUuid)) {
return existingInfo.volumeUuid;
// 4.2 没有合适的分区,抛出异常。
} else {
throw new IOException("Not enough space on existing volume "
+ existingInfo.volumeUuid + " for " + params.appPackageName + " upgrade");
}
}
// 5 没有强烈要求安装到内部分区,也没有被安装过,也没有强制要求安装到扩展分区,则使用最合适的分区(也可能是/data分区)
// We're left with new installations with either preferring external or auto, so just pick
// volume with most space
if (bestCandidate != null) {
return bestCandidate.fsUuid;
} else {
throw new IOException("No special requests, but no room on allowed volumes. "
+ " allow3rdPartyOnInternal? " + allow3rdPartyOnInternal);
}
}
确定安装卷这里看起来比较复杂,其实有几个参考指标:
1、磁盘的空间要满足。
2、是否允许三方应用安装在/data分区(allow3rdPartyOnInternal)。
3、是否强制允许安装到扩展分区(forceAllowOnExternal)。
4、应用是否要求安装到/data分区。(PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY)
5、不支持move应用到其他盘。
6、系统应用只允许安装在/data分区。
7、是否强制安装到外置存储。
8、哪个磁盘剩余空间最大。
在分析代码之前先说明下,这里internal分区代表/data 目录所对应的分区。它对应的volume.id为ID_PRIVATE_INTERNAL,类型是VolumeInfo.TYPE_PRIVATE, 其他的类型为VolumeInfo.TYPE_PRIVATE的分区作为内置分区的扩展,被全盘加密后挂载到/mnt/expand/${volumeId}下面, 所以应用只能安装到VolumeInfo.TYPE_PRIVATE分区下面(早期Android应用可以安装到PUBLIC类型分区)。剩下的请参上面代码我添加的注释理解。到这里只参考了params.installLocation 的PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY标志。
回到createSessionInternal函数中。params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);这行代码没有抛出异常则选好了合适的卷。没有合适的安装位置则抛出异常,安装过程结束, createSessionInternal后面的流程有如下代码:
private int createSessionInternal(SessionParams params, String installerPackageName, int userId) {
......
File stageDir = null;
String stageCid = null;
if (!params.isMultiPackage) {
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
stageDir = buildSessionDir(sessionId, params);
} else {
stageCid = buildExternalStageCid(sessionId);
}
}
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId,
installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
false, false, null, SessionInfo.INVALID_ID, false, false, false,
SessionInfo.STAGED_SESSION_NO_ERROR, "");
......
}
buildSessionDir 这行代码会在选定的分区下创建临时目录,用于将apk文件拷贝过来,我们展开来看buildSessionDir函数:
private File buildSessionDir(int sessionId, SessionParams params) {
if (params.isStaged) {
final File sessionStagingDir = Environment.getDataStagingDirectory(params.volumeUuid);
return new File(sessionStagingDir, "session_" + sessionId);
}
return buildTmpSessionDir(sessionId, params.volumeUuid);
}
private File buildTmpSessionDir(int sessionId, String volumeUuid) {
final File sessionStagingDir = getTmpSessionDir(volumeUuid);
return new File(sessionStagingDir, "vmdl" + sessionId + ".tmp");
}
private File getTmpSessionDir(String volumeUuid) {
return Environment.getDataAppDirectory(volumeUuid);
}
public static File getDataAppDirectory(String volumeUuid) {
return new File(getDataDirectory(volumeUuid), "app");
}
public static File getDataDirectory(String volumeUuid) {
if (TextUtils.isEmpty(volumeUuid)) {
return DIR_ANDROID_DATA;
} else {
return new File("/mnt/expand/" + volumeUuid);
}
}
buildSessionDir 函数有两个分支,当params.isStaged为真的时候表示下次启动的时候才真正安装这个应用,一般用于apex的安装。我们不关系它。所以展开buildTmpSessionDir函数,其实stageDir是在/data/app/app/vmdl${serssionId}.tmp或者/mnt/expand/${volumeId}/app/vmdl${serssionId}.tmp。
public void open() throws IOException {
......
if (!mPrepared) {
if (stageDir != null) {
prepareStageDir(stageDir);
} else if (params.isMultiPackage) {
// it's all ok
} else {
throw new IllegalArgumentException("stageDir must be set");
}
mPrepared = true;
}
}
......
}
在打开session的时候会调用prepareStageDir(stageDir)来创建stageDir。后面应用商店程序或者adb程序会调用两个个可能的函数将安装到拷贝到stageDir文件夹下面。这里个函数我们就不分析了,他们分别是:
public void write(String name, long offsetBytes, long lengthBytes,
ParcelFileDescriptor fd)
public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes)
前者是外部传进来一个ParcelFileDescriptor,从里面读内容写到${stageDir}/PackageInstaller 文件,后者是打开${stageDir}/PackageInstaller文件,交由客户端去写。 ${stageDir}/PackageInstaller文件实际就是apk文件。后面的安装过程都是对该文件进行处理。安装完成后会将${stageDir}文件夹更名为应用报名, ${stageDir}/PackageInstaller会编程base.apk, 这其实就是我们比较常见的/data/app/com.xxx/ 文件夹,当然也可能是/mnt/expand/${volumeId}/app/com.xxxx。
makeSessionActiveLocked() 会选择abi进行解压到lib文件夹。
validateApkInstallLocked(@Nullable PackageInfo pkgInfo) 改名为base.apk。
/******************************被架空的部分开始********************************/
下面关于安装位置的确定就是在安装应用前的拷贝工作,这里面的带部分逻辑基本已经被前面的创建session时确定的位置架空。这里不详细分析,只是通过注释的方式写出。
/*
* Invoke remote method to get package information and install
* location values. Override install location based on default
* policy if needed and then create install arguments based
* on the install location.
*/
public void handleStartCopy() {
int ret = PackageManager.INSTALL_SUCCEEDED;
// 1 表示前边已经确定好了安装目录(\${stageDir}/PackageInstaller),确定文件是否存在,设置INSTALL_INTERNAL标志(double check)。
// If we're already staged, we've firmly committed to an install location
if (origin.staged) {
if (origin.file != null) {
installFlags |= PackageManager.INSTALL_INTERNAL;
} else {
throw new IllegalStateException("Invalid stage location");
}
}
......
// 2 简单的解析下安装包的AndroidManifest.xml文件,这里面会调用PackageHelper.resolveInstallVolume(mContext, params)函数确定推荐的安装位置。
pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
origin.resolvedPath, installFlags, packageAbiOverride);
if (DEBUG_INSTANT && ephemeral) {
Slog.v(TAG, "pkgLite for install: " + pkgLite);
}
/*
* If we have too little free space, try to free cache
* before giving up.
*/
// 3 空间不足使用mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);释放一些空间,
// 再次调用PackageManagerServiceUtils.getMinimalPackageInfo(
// mContext,origin.resolvedPath, installFlags, packageAbiOverride)
// 注意3 这种情况并没有使用PackageInstallerSession将安装文件拷贝到临时目录,如果已经拷贝,则并不需要关心空间问题
if (!origin.staged && pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
// TODO: focus freeing disk space on the target device
final StorageManager storage = StorageManager.from(mContext);
final long lowThreshold = storage.getStorageLowBytes(
Environment.getDataDirectory());
final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(
origin.resolvedPath, packageAbiOverride);
if (sizeBytes >= 0) {
try {
mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
origin.resolvedPath, installFlags, packageAbiOverride);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to free cache", e);
}
}
/*
* The cache free must have deleted the file we downloaded to install.
*
* TODO: fix the "freeCache" call to not delete the file we care about.
*/
// 3.1 freeCache把安装包删了, 设置PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE表示是由于空间问题安装失败的。
if (pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
pkgLite.recommendedInstallLocation
= PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
}
if (ret == PackageManager.INSTALL_SUCCEEDED) {
// 4.1 错误情况处理
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 if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
ret = PackageManager.INSTALL_FAILED_INVALID_APK;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
ret = PackageManager.INSTALL_FAILED_INVALID_URI;
} else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
} else {
// 4.2 前面都处理成功了,看下是否还需要更换安装位置
// Override with defaults if needed.
loc = installLocationPolicy(pkgLite);
if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) {
ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
} else if (loc == PackageHelper.RECOMMEND_FAILED_WRONG_INSTALLED_VERSION) {
ret = PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
} else if (!onInt) {
// Override install location with flags
if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
// Set the flag to install on external media.
installFlags &= ~PackageManager.INSTALL_INTERNAL;
} else if (loc == PackageHelper.RECOMMEND_INSTALL_EPHEMERAL) {
if (DEBUG_INSTANT) {
Slog.v(TAG, "...setting INSTALL_EPHEMERAL install flag");
}
installFlags |= PackageManager.INSTALL_INSTANT_APP;
installFlags &= ~PackageManager.INSTALL_INTERNAL;
} else {
// Make sure the flag for installing on external
// media is unset
installFlags |= PackageManager.INSTALL_INTERNAL;
}
}
}
}
......
}
/**
* Parse given package and return minimal details.
*/
public static PackageInfoLite getMinimalPackageInfo(Context context, String packagePath,
int flags, String abiOverride) {
final PackageInfoLite ret = new PackageInfoLite();
// 1 apk 路径为空返回 PackageHelper.RECOMMEND_FAILED_INVALID_APK
if (packagePath == null) {
Slog.i(TAG, "Invalid package file " + packagePath);
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
return ret;
}
final File packageFile = new File(packagePath);
final PackageParser.PackageLite pkg;
final long sizeBytes;
try {
pkg = PackageParser.parsePackageLite(packageFile, 0);
sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
} catch (PackageParserException | IOException e) {
Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
if (!packageFile.exists()) {
// 2 apk文件不存在返回PackageHelper.RECOMMEND_FAILED_INVALID_URI
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
} else {
//3 解析失败返回 PackageHelper.RECOMMEND_FAILED_INVALID_APK
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
}
return ret;
}
// 4 获取推荐安装位置
final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
pkg.packageName, pkg.installLocation, sizeBytes, flags);
......
ret.recommendedInstallLocation = recommendedInstallLocation;
ret.multiArch = pkg.multiArch;
return ret;
}
看注释
@Deprecated
public static int resolveInstallLocation(Context context, String packageName,
int installLocation, long sizeBytes, int installFlags) {
final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
params.appPackageName = packageName;
params.installLocation = installLocation;
params.sizeBytes = sizeBytes;
params.installFlags = installFlags;
try {
return resolveInstallLocation(context, params);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
出于空间考量
/**
* Given a requested {@link PackageInfo#installLocation} and calculated
* install size, pick the actual location to install the app.
*/
public static int resolveInstallLocation(Context context, SessionParams params)
throws IOException {
ApplicationInfo existingInfo = null;
try {
existingInfo = context.getPackageManager().getApplicationInfo(params.appPackageName,
PackageManager.MATCH_ANY_USER);
} catch (NameNotFoundException ignored) {
}
// 最合适的安装路径
final int prefer;
// 是否需要检查主存储和/data分区
final boolean checkBoth;
// 是否是短暂安装
boolean ephemeral = false;
if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
// 1 INSTALL_INSTANT_APP 推荐安装在/data分区,instant app为短暂安装的app, 不需要检查主存储
prefer = RECOMMEND_INSTALL_INTERNAL;
ephemeral = true;
checkBoth = false;
} else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
// 2 PackageManager.INSTALL_INTERNAL 标志表示安装在/data分区,不需要检查主存储。
prefer = RECOMMEND_INSTALL_INTERNAL;
checkBoth = false;
} else if (params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
// 3 只允许安装在/data分区,不需要检查主存储
prefer = RECOMMEND_INSTALL_INTERNAL;
checkBoth = false;
} else if (params.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
// 4 推荐安装到主存储上(隐含表示主存储和/data分区都可以安装),需要检查/data分区和主存储
prefer = RECOMMEND_INSTALL_EXTERNAL;
checkBoth = true;
} else if (params.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
// 5 自动选择安装分区,默认安装到/data分区,只有之前就安装在主存储,现在覆盖安装才推荐安装到外置存储。需要检查内置存储和主存储。隐含表示主存储和/data分区都可以安装)
// When app is already installed, prefer same medium
if (existingInfo != null) {
// TODO: distinguish if this is external ASEC
if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
prefer = RECOMMEND_INSTALL_EXTERNAL;
} else {
prefer = RECOMMEND_INSTALL_INTERNAL;
}
} else {
prefer = RECOMMEND_INSTALL_INTERNAL;
}
checkBoth = true;
} else {
// 6 未指定installLocation ,默认安装到内置存储。
prefer = RECOMMEND_INSTALL_INTERNAL;
checkBoth = false;
}
boolean fitsOnInternal = false;
// 7 检查internal分区空间是否充足
if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
fitsOnInternal = fitsOnInternal(context, params);
}
// 8 检查主存储空间是否充足
boolean fitsOnExternal = false;
if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
fitsOnExternal = fitsOnExternal(context, params);
}
if (prefer == RECOMMEND_INSTALL_INTERNAL) { // 9 推荐安装到/data分区,如果data分区足够则安装到/data分区
// The ephemeral case will either fit and return EPHEMERAL, or will not fit
// and will fall through to return INSUFFICIENT_STORAGE
if (fitsOnInternal) {
return (ephemeral)
? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL
: PackageHelper.RECOMMEND_INSTALL_INTERNAL;
}
} else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {// 9 推荐安装到主存储,如果主存储足够则安装到主存储
if (fitsOnExternal) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
}
if (checkBoth) { // 10 推荐位置空间没有满足,并且可以安装到主存储和/data分区,则找一个空间满足的分区作为安装分区
if (fitsOnInternal) {
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
} else if (fitsOnExternal) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
}
// 11 没有找到合适的安装分区,则返回失败
return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
这里并没有考虑其他Private 卷。只允许安装在/data分区和主存储。
出于之前安装的包考量
private int installLocationPolicy(PackageInfoLite pkgLite) {
String packageName = pkgLite.packageName;
int installLocation = pkgLite.installLocation;
// reader
synchronized (mPackages) {
// Currently installed package which the new package is attempting to replace or
// null if no such package is installed.
PackageParser.Package installedPkg = mPackages.get(packageName);
// Package which currently owns the data which the new package will own if installed.
// If an app is unstalled while keeping data (e.g., adb uninstall -k), installedPkg
// will be null whereas dataOwnerPkg will contain information about the package
// which was uninstalled while keeping its data.
PackageParser.Package dataOwnerPkg = installedPkg;
if (dataOwnerPkg == null) {
PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null) {
dataOwnerPkg = ps.pkg;
}
}
// 1 安装版本错误
if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
if (dataOwnerPkg == null) {
Slog.w(TAG, "Required installed version code was "
+ requiredInstalledVersionCode
+ " but package is not installed");
return PackageHelper.RECOMMEND_FAILED_WRONG_INSTALLED_VERSION;
}
if (dataOwnerPkg.getLongVersionCode() != requiredInstalledVersionCode) {
Slog.w(TAG, "Required installed version code was "
+ requiredInstalledVersionCode
+ " but actual installed version is "
+ dataOwnerPkg.getLongVersionCode());
return PackageHelper.RECOMMEND_FAILED_WRONG_INSTALLED_VERSION;
}
}
if (dataOwnerPkg != null) {
if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
dataOwnerPkg.applicationInfo.flags)) {
try {
checkDowngrade(dataOwnerPkg, pkgLite);
} catch (PackageManagerException e) {
Slog.w(TAG, "Downgrade detected: " + e.getMessage());
return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE;
}
}
}
if (installedPkg != null) {
// 2 覆盖安装情况
if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
// Check for updated system application.
//2.1 系统应用只允许安装在 /data分区
if ((installedPkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
} else {
// 2.2 升级版本只允许安装在内置存储,返回推荐安装到内置存储。
// If current upgrade specifies particular preference
if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
// Application explicitly specified internal.
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
// 2.3 推荐安装在外置存储,使用之前计算到的分区。
} else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
// App explictly prefers external. Let policy decide
} else {
//2.4 之前安装到了外置存储,继续使用爱之存储,否则使用内置存储。
// Prefer previous location
if (isExternal(installedPkg)) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
}
}
} else {
// Invalid install. Return error code
return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;
}
}
}
// 2.5 使用前面计算的推荐位置
return pkgLite.recommendedInstallLocation;
}
/******************************被架空的部分结束********************************/
PackageManagerService中真正执行拷贝的地方为doCopyApk()函数。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private int doCopyApk() {
if (origin.staged) { // 前边计算了一大通,最终还是以origin.staged为准
if (DEBUG_INSTALL) Slog.d(TAG, origin.file + " already staged; skipping copy");
codeFile = origin.file;
resourceFile = origin.file;
return PackageManager.INSTALL_SUCCEEDED;
}
try {// 2 instant app 和external 计算安装位置并拷贝
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 = PackageManagerServiceUtils.copyPackage(
origin.file.getAbsolutePath(), codeFile);
if (ret != PackageManager.INSTALL_SUCCEEDED) {
Slog.e(TAG, "Failed to copy package");
return ret;
}
//3 拷贝lib目录
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;
}
这里已经使用PackageInstallerSession将apk拷贝到了正确的位置,origin.staged就是对应的目录,所以这里直接返回拷贝成功。
@GuardedBy("mInstallLock")
private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
......
if (!args.doRename(res.returnCode, pkg)) {
throw new PrepareFailure(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
}
......
}
```java
class FileInstallArgs extends InstallArgs
boolean doRename(int status, PackageParser.Package pkg) {
if (status != PackageManager.INSTALL_SUCCEEDED) {
cleanUp();
return false;
}
final File targetDir = codeFile.getParentFile();
final File beforeCodeFile = codeFile;
final File afterCodeFile = getNextCodePath(targetDir, pkg.packageName);
if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
try {
Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to rename", e);
return false;
}
if (!SELinux.restoreconRecursive(afterCodeFile)) {
Slog.w(TAG, "Failed to restorecon");
return false;
}
// Reflect the rename internally
codeFile = afterCodeFile;
resourceFile = afterCodeFile;
// Reflect the rename in scanned details
try {
pkg.setCodePath(afterCodeFile.getCanonicalPath());
} catch (IOException e) {
Slog.e(TAG, "Failed to get path: " + afterCodeFile, e);
return false;
}
pkg.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile,
afterCodeFile, pkg.baseCodePath));
pkg.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
afterCodeFile, pkg.splitCodePaths));
// Reflect the rename in app info
pkg.setApplicationVolumeUuid(pkg.volumeUuid);
pkg.setApplicationInfoCodePath(pkg.codePath);
pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
pkg.setApplicationInfoResourcePath(pkg.codePath);
pkg.setApplicationInfoBaseResourcePath(pkg.baseCodePath);
pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
return true;
}
后面在安装之前preparePackageLI函数中将staged目录改名为应用包名,至此应用的安装位置就确定了。