概述
前言
在Android 9.0 PM机制系列(四) APK安装需要空间分析文章中,我们重点分析了Android9.0需要的最小APK安装存储空间大小。结论就是:只要系统空间小于Math.min(getTotalSpace的5%,500M)+ PackageHelper.calculateInstalledSize(pkg, abiOverride),系统就会报空间不足。
正当以为所有的系统都大抵如此,但是没想到在Android6.0上面,当空间存储只有30M的时候,依旧可以安装10M的应用。这到底是怎么回事,唯有读源码能一解我的困惑!
1.开始安装
分析源码,一直跟到PMS里面,发现和Android9.0一样,安装核心代码开始处就在handleStartCopy方法,代码如下:
/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public void handleStartCopy() throws RemoteException {
int ret = PackageManager.INSTALL_SUCCEEDED;
// If we're already staged, we've firmly committed to an install location
if (origin.staged) {//1
if (origin.file != null) {//2
installFlags |= PackageManager.INSTALL_INTERNAL;//3
installFlags &= ~PackageManager.INSTALL_EXTERNAL;
}
...
}
final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
PackageInfoLite pkgLite = null;
if (onInt && onSd) {
// Check if both bits are set.
Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else {
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
packageAbiOverride);//4
/*
* If we have too little free space, try to free cache
* before giving up.
*/
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 = mContainerService.calculateInstalledSize(
origin.resolvedPath, isForwardLocked(), packageAbiOverride);
if (mInstaller.freeCache(null, sizeBytes + lowThreshold) >= 0) {
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath,
installFlags, packageAbiOverride);
}
/*
* 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.
*/
if (pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INVALID_URI) {//5
pkgLite.recommendedInstallLocation
= PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
}
}
if (ret == PackageManager.INSTALL_SUCCEEDED) {
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) {//6
ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
...
} else {
// Override with defaults if needed.
loc = installLocationPolicy(pkgLite);
if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) {
ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
} else if (!onSd && !onInt) {
// Override install location with flags
if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
// Set the flag to install on external media.
installFlags |= PackageManager.INSTALL_EXTERNAL;
installFlags &= ~PackageManager.INSTALL_INTERNAL;
} else {
// Make sure the flag for installing on external
// media is unset
installFlags |= PackageManager.INSTALL_INTERNAL;
installFlags &= ~PackageManager.INSTALL_EXTERNAL;
}
}
}
}
final InstallArgs args = createInstallArgs(this);
mArgs = args;
if (ret == PackageManager.INSTALL_SUCCEEDED) {
...
if (!origin.existing && requiredUid != -1
&& isVerificationEnabled(userIdentifier, installFlags)) {
...
} else {
/*
* No package verification is enabled, so immediately start
* the remote call to initiate copy using temporary file.
*/
ret = args.copyApk(mContainerService, true);
}
}
mRet = ret;
}
.handleStartCopy方法的代码很多,这里截取关键的部分。
-
注释1、注释2处由于传参origin满足注释1和注释2的条件,所以会执行到注释3处给installFlags赋值。
void installStage(String packageName, File stagedDir, String stagedCid, IPackageInstallObserver2 observer, PackageInstaller.SessionParams params, String installerPackageName, int installerUid, UserHandle user) { final VerificationParams verifParams = new VerificationParams(null, params.originatingUri, params.referrerUri, installerUid, null); final OriginInfo origin; if (stagedDir != null) {//由于传参stagedDir!=null 所以会执行到这里狗仔origin origin = OriginInfo.fromStagedFile(stagedDir); } else { origin = OriginInfo.fromStagedContainer(stagedCid); } final Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(origin, observer, params.installFlags, installerPackageName, verifParams, user, params.abiOverride); mHandler.sendMessage(msg); } static OriginInfo fromStagedFile(File file) { //四个参数(File file, String cid, boolean staged, boolean existing return new OriginInfo(file, null, true, false); }
-
注释4处通过IMediaContainerService跨进程调用DefaultContainerService的getMinimalPackageInfo方法,该方法轻量解析APK并得到APK的少量信息,轻量解析的原因是这里不需要得到APK的全部信息,APK的少量信息会封装到PackageInfoLite中。在上一篇中我们一笔带过,这里信息量很多,里面就有我们这篇文章所涉及到的安装空间判断。
-
注释5处如果推荐位置为RECOMMEND_FAILED_INVALID_URI则赋值RECOMMEND_FAILED_INSUFFICIENT_STORAGE
-
注释6处,如果loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE,就会返回INSTALL_FAILED_INSUFFICIENT_STORAGE。所以要从loc的返回值分析。下面看一下getMinimalPackageInfo方法。
/frameworks/base/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@Override
public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, String abiOverride) {
final Context context = DefaultContainerService.this;
final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
PackageInfoLite ret = new PackageInfoLite();
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, isForwardLocked, abiOverride);//1
} catch (PackageParserException | IOException e) {
Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
if (!packageFile.exists()) {
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
} else {
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
}
return ret;
}
ret.packageName = pkg.packageName;
ret.splitNames = pkg.splitNames;
ret.versionCode = pkg.versionCode;
ret.baseRevisionCode = pkg.baseRevisionCode;
ret.splitRevisionCodes = pkg.splitRevisionCodes;
ret.installLocation = pkg.installLocation;
ret.verifiers = pkg.verifiers;
ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
pkg.packageName, pkg.installLocation, sizeBytes, flags);//2
ret.multiArch = pkg.multiArch;
return ret;
}
- 注释1处计算安装apk需要的空间大小。
- 注释2处是核心代码,根据apk需要的空间大小计算推荐存储位置,参数flags就是就是我们之前赋值的内部存储。
下面看一下resolveInstallLocation方法
/frameworks/base/core/java/com/android/internal/content/PackageHelper.java
public static int resolveInstallLocation(Context context, String packageName,
int installLocation, long sizeBytes, int installFlags) {
ApplicationInfo existingInfo = null;
try {
existingInfo = context.getPackageManager().getApplicationInfo(packageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException ignored) {
}
final int prefer;
final boolean checkBoth;
if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) { //1
prefer = RECOMMEND_INSTALL_INTERNAL;
checkBoth = false;
} else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
prefer = RECOMMEND_INSTALL_EXTERNAL;
checkBoth = false;
}
...
} else {
prefer = RECOMMEND_INSTALL_INTERNAL;
checkBoth = false;
}
boolean fitsOnInternal = false;
if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
fitsOnInternal = fitsOnInternal(context, sizeBytes); //2
}
boolean fitsOnExternal = false;
if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
fitsOnExternal = fitsOnExternal(context, sizeBytes);
}
if (prefer == RECOMMEND_INSTALL_INTERNAL) {
if (fitsOnInternal) {
return PackageHelper.RECOMMEND_INSTALL_INTERNAL; //3
}
} else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
if (fitsOnExternal) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
}
if (checkBoth) {
if (fitsOnInternal) {
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
} else if (fitsOnExternal) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
}
return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; //4
}
- 根据之前传入的flags值代码会运营到注释1处。此处赋值:
prefer = RECOMMEND_INSTALL_INTERNAL;
checkBoth = false; - 根据之前的赋值,会走到注释2处,调用fitsOnInternal()方法。
如果fitsOnInternal为true,就会走到注释3处,返回RECOMMEND_INSTALL_INTERNAL
如果fitsOnInternal为false,就会走到注释4处,返回RECOMMEND_FAILED_INSUFFICIENT_STORAGE。
下面我们看一下fitsOnInternal()方法
/frameworks/base/core/java/com/android/internal/content/PackageHelper.java
public static boolean fitsOnInternal(Context context, long sizeBytes) {
final StorageManager storage = context.getSystemService(StorageManager.class);
final File target = Environment.getDataDirectory();
return (sizeBytes <= storage.getStorageBytesUntilLow(target));
}
贴一下9.0这块的源码:
public static boolean fitsOnInternal(Context context, SessionParams params) throws IOException {
final StorageManager storage = context.getSystemService(StorageManager.class);
final UUID target = storage.getUuidForPath(Environment.getDataDirectory());
return (params.sizeBytes <= storage.getAllocatableBytes(target,
translateAllocateFlags(params.installFlags))); //1
}
看到和9.0的区别了没有
- 6.0是和storage.getStorageBytesUntilLow() 比较,经查看源码,7.0也是如此。
- 9.0是和storage.getAllocatableBytes() 比较,经查看源码,8.0也是如此。
继续分析,看一下6.0的storage.getStorageBytesUntilLow()是什么东东
/frameworks/base/core/java/android/os/storage/StorageManager.java
private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
public long getStorageBytesUntilLow(File path) {
return path.getUsableSpace() - getStorageFullBytes(path);
}
public long getStorageFullBytes(File path) {
return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
DEFAULT_FULL_THRESHOLD_BYTES);
}
看明白没有,getStorageFullBytes默认值1M,getStorageBytesUntilLow返回的是可用空间-1M。。。
总结
6.0安装所需空间就是PackageHelper.calculateInstalledSize(pkg, abiOverride) + 1M。基本上2倍应用大小空间就可以安装。
5.1源码和6.0一样,所以结论也是一样
最后
以上就是直率小馒头为你收集整理的Android 6.0 PM机制系列(四) APK安装需要空间分析前言1.开始安装总结的全部内容,希望文章能够帮你解决Android 6.0 PM机制系列(四) APK安装需要空间分析前言1.开始安装总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复