当data存储空间充足,但是apk install 时报内存不足异常,以下分析需要多少空间才能进行安装一个应用。以下代码以Android q 进行分析
如异常:
Performing Streamed Install
adb: failed to install C:\Users\rechar\testapk: Failure [INSTALL_FAILED_INSUFFICIENT_STORAGE]
或者
Performing Streamed Install
adb: failed to install C:\Users\rechar\testapk:Exception occurred while executing:
android.os.ParcelableException: java.io.IOException: Requested internal only, but not enough space
at android.util.ExceptionUtils.wrap(ExceptionUtils.java:34)
at com.android.server.pm.PackageInstallerService.createSession(PackageInstallerService.java:467)
at com.android.server.pm.PackageManagerShellCommand.doCreateSession(PackageManagerShellCommand.java:2700)
at com.android.server.pm.PackageManagerShellCommand.runInstall(PackageManagerShellCommand.java:1061)
at com
涉及类:frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
此类进行apk copy以及解析计算
应用安装分为两个阶段
第一阶段把需要安装的应用复制到/data/~目录下
第二阶段是对apk文件扫描优化,装载到内存中。
/*
* 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() {
}
frameworks\base\core\java\com\android\internal\content\PackageHelper.java
/**
* 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 {
.........
//安装至内部存储
//如果fitsOnInternal为true,就会走到注释1处,返回RECOMMEND_INSTALL_INTERNAL
//如果fitsOnInternal为false,就会走到注释2处,返回RECOMMEND_FAILED_INSUFFICIENT_STORAGE。
boolean fitsOnInternal = false;
if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
fitsOnInternal = fitsOnInternal(context, params);
}
if (prefer == RECOMMEND_INSTALL_INTERNAL) {
// The ephemeral case will either fit and return EPHEMERAL, or will not fit
// and will fall through to return INSUFFICIENT_STORAGE
if (fitsOnInternal) {//1
return (ephemeral)
? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL
: PackageHelper.RECOMMEND_INSTALL_INTERNAL;
}
}
........
return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; //2
}
//此方法进行判断安装需要的空间大小是否小于系统分配的存储大小,如果小于则可安装
public static boolean fitsOnInternal(Context context, SessionParams params) throws IOException {
final StorageManager storage = context.getSystemService(StorageManager.class);
final UUID target = storage.getUuidForPath(Environment.getDataDirectory());
//params.sizeBytes 系统通过轻解析后计算出apk需要安装的大小值,大概为apk的两倍
return (params.sizeBytes <= storage.getAllocatableBytes(target,
translateAllocateFlags(params.installFlags)));
}
frameworks\base\services\core\java\com\android\server\StorageManagerService.java
//此方法计算系统分配空间
@Override
public long getAllocatableBytes(String volumeUuid, int flags, String callingPackage) {
flags = adjustAllocateFlags(flags, Binder.getCallingUid(), callingPackage);
final StorageManager storage = mContext.getSystemService(StorageManager.class);
final StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class);
final long token = Binder.clearCallingIdentity();
try {
// In general, apps can allocate as much space as they want, except
// we never let them eat into either the minimum cache space or into
// the low disk warning space. To avoid user confusion, this logic
// should be kept in sync with getFreeBytes().
final File path = storage.findPathForUuid(volumeUuid);
final long usable = path.getUsableSpace(); //1当前获取的实际可写入的空间
final long lowReserved = storage.getStorageLowBytes(path);//2系统预留的5%
final long fullReserved = storage.getStorageFullBytes(path);
if (stats.isQuotaSupported(volumeUuid)) {
final long cacheTotal = stats.getCacheBytes(volumeUuid);
final long cacheReserved = storage.getStorageCacheBytes(path, flags);
final long cacheClearable = Math.max(0, cacheTotal - cacheReserved);
if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
return Math.max(0, (usable + cacheClearable) - fullReserved);
} else {
//目前一般android10以上都有fast quota information。因此系统分配空间为
//(usable + cacheClearable) - lowReserved
//cacheClearable为可清除的缓存大小
//usable获取到的值是会减去apk copy的占用的一倍大小
return Math.max(0, (usable + cacheClearable) - lowReserved);
}
} else {
// When we don't have fast quota information, we ignore cached
// data and only consider unused bytes.
if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
return Math.max(0, usable - fullReserved);
} else {
return Math.max(0, usable - lowReserved);
}
}
} catch (IOException e) {
throw new ParcelableException(e);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/frameworks/base/core/java/android/os/storage/StorageManager.java
private static final int DEFAULT_THRESHOLD_PERCENTAGE = 5;
private static final long DEFAULT_THRESHOLD_MAX_BYTES = DataUnit.MEBIBYTES.toBytes(500);
private static final int DEFAULT_CACHE_PERCENTAGE = 10;
private static final long DEFAULT_CACHE_MAX_BYTES = DataUnit.GIBIBYTES.toBytes(5);
private static final long DEFAULT_FULL_THRESHOLD_BYTES = DataUnit.MEBIBYTES.toBytes(1);
/**
* Return the number of available bytes at which the given path is
* considered running low on storage.
*此方法是存储不足的限定,如果当前存储空间少于此大小则会报空间不足的提示
* @hide
*/
@UnsupportedAppUsage
public long getStorageLowBytes(File path) {
final long lowPercent = Settings.Global.getInt(mResolver,
Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
final long maxLowBytes = Settings.Global.getLong(mResolver,
Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
//系统设置的默认阈值是5%,最终返回值为getTotalSpace的5%和500M之间的最小值。
return Math.min(lowBytes, maxLowBytes);
}
计算安装空间为apk*2 <=
data实际可使用安装大小为:
可用大小(data当前显示可用-apk大小)-(Math.min(getTotalSpace*5%,500M)【预留空间】;