700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Android 9.0 PM机制系列(四) APK安装需要空间分析

Android 9.0 PM机制系列(四) APK安装需要空间分析

时间:2023-09-01 10:16:23

相关推荐

Android 9.0 PM机制系列(四) APK安装需要空间分析

前言

在PM机制系列前三篇,我们着重分析了安装的整个流程,没有具体到很多细节问题。

这一篇文章我们就会具体到很多细节问题。本篇主要就是围绕一个问题展开:

安装APK到底需要多少空间不会报错INSTALL_FAILED_INSUFFICIENT_STORAGE?可以提高我们的安装成功率。

1. 分析结果时序图

DCS:DefaultContainerService

SMS:StorageManagerService

SM: StorageManager

调用关系图如下所示,后面会进行具体分析。

2. 开始安装

经过前三篇的安装流程,可以清楚的知道安装核心代码开始处就在handleStartCopy方法,代码如下:

/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

public void handleStartCopy() throws RemoteException {...// If we're already staged, we've firmly committed to an install locationif (origin.staged) {//安装时候,传参进来的origin.staged是为true,file != nullif (origin.file != null) {installFlags |= PackageManager.INSTALL_INTERNAL; //这样就会走到这里给installFlags赋值,installFlags &= ~PackageManager.INSTALL_EXTERNAL;//走内部安装逻辑} else {throw new IllegalStateException("Invalid stage location");}}/*确定APK的安装位置。onSd:安装到SD卡, onInt:内部存储即Data分区,ephemeral:安装到临时存储(Instant Apps安装)这里根据上面installFlags的赋值可知onSd=false;onInt=true;ephemeral=false;*/final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;final boolean ephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;PackageInfoLite pkgLite = null;if (onInt && onSd) {// APK不能同时安装在SD卡和Data分区Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;//安装标志冲突,Instant Apps不能安装到SD卡中} else if (onSd && ephemeral) {Slog.w(TAG, "Conflicting flags specified for installing ephemeral on external");ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;} else {//最终会走到这里来//获取APK的少量的信息pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,packageAbiOverride);//1if (DEBUG_EPHEMERAL && ephemeral) {Slog.v(TAG, "pkgLite for install: " + pkgLite);}...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) {ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;//2} ...}else{loc = installLocationPolicy(pkgLite);...}}//根据InstallParams创建InstallArgs对象final InstallArgs args = createInstallArgs(this);mArgs = args;if (ret == PackageManager.INSTALL_SUCCEEDED) {...if (!origin.existing && requiredUid != -1&& isVerificationEnabled(verifierUser.getIdentifier(), installFlags, installerUid)) {...} else{ret = args.copyApk(mContainerService, true);}}mRet = ret;}

handleStartCopy方法的代码很多,这里截取关键的部分。

注释1处通过IMediaContainerService跨进程调用DefaultContainerService的getMinimalPackageInfo方法,该方法轻量解析APK并得到APK的少量信息,轻量解析的原因是这里不需要得到APK的全部信息,APK的少量信息会封装到PackageInfoLite中。在上一篇中我们一笔带过,这里信息量很多,里面就有我们这篇文章所涉及到的安装空间判断。注释2处,如果loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE,就会返回INSTALL_FAILED_INSUFFICIENT_STORAGE。所以要从loc的返回值分析。下面看一下getMinimalPackageInfo方法。

/frameworks/base/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java

/*** Parse given package and return minimal details.** @param packagePath absolute path to the package to be copied. Can be* a single monolithic APK file or a cluster directory* containing one or more APKs.*/@Overridepublic PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,String abiOverride) {final Context context = DefaultContainerService.this;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, 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;}final int recommendedInstallLocation;final long token = Binder.clearCallingIdentity();try {recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,pkg.packageName, pkg.installLocation, sizeBytes, flags);//2} finally {Binder.restoreCallingIdentity(token);}ret.packageName = pkg.packageName;ret.splitNames = pkg.splitNames;ret.versionCode = pkg.versionCode;ret.versionCodeMajor = pkg.versionCodeMajor;ret.baseRevisionCode = pkg.baseRevisionCode;ret.splitRevisionCodes = pkg.splitRevisionCodes;ret.installLocation = pkg.installLocation;ret.verifiers = pkg.verifiers;ret.recommendedInstallLocation = recommendedInstallLocation;ret.multiArch = pkg.multiArch;return ret;}

注释1处计算安装apk需要的空间大小。注释2处是核心代码,根据apk需要的空间大小计算推荐存储位置,参数flags就是就是我们之前赋值的内部存储。

下面看一下resolveInstallLocation方法

/frameworks/base/core/java/com/android/internal/content/PackageHelper.java

@Deprecatedpublic 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); //1} 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;final boolean checkBoth;boolean ephemeral = false;if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {prefer = RECOMMEND_INSTALL_INTERNAL;ephemeral = true;checkBoth = false;} else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {//2prefer = RECOMMEND_INSTALL_INTERNAL;checkBoth = false;} else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {prefer = RECOMMEND_INSTALL_EXTERNAL;checkBoth = false;} else if (params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {prefer = RECOMMEND_INSTALL_INTERNAL;checkBoth = false;} else if (params.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {prefer = RECOMMEND_INSTALL_EXTERNAL;checkBoth = true;} else if (params.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {// When app is already installed, prefer same mediumif (existingInfo != null) {// TODO: distinguish if this is external ASECif ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {prefer = RECOMMEND_INSTALL_EXTERNAL;} else {prefer = RECOMMEND_INSTALL_INTERNAL;}} else {prefer = RECOMMEND_INSTALL_INTERNAL;}checkBoth = true;} else {prefer = RECOMMEND_INSTALL_INTERNAL;checkBoth = false;}boolean fitsOnInternal = false;if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {fitsOnInternal = fitsOnInternal(context, params); //3}boolean fitsOnExternal = false;if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {fitsOnExternal = fitsOnExternal(context, params);}if (prefer == RECOMMEND_INSTALL_INTERNAL) {//4// The ephemeral case will either fit and return EPHEMERAL, or will not fit// and will fall through to return INSUFFICIENT_STORAGEif (fitsOnInternal) {return (ephemeral)? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL: PackageHelper.RECOMMEND_INSTALL_INTERNAL;//5}} 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; //6}

注释1处将我们传入的参数全部都放入SessionParams内,执行resolveInstallLocation根据之前传入的flags值代码会运营到注释2处。此处赋值:

prefer = RECOMMEND_INSTALL_INTERNAL;

checkBoth = false;根据之前的赋值,会走到注释3处,调用fitsOnInternal()方法。

如果fitsOnInternal为true,就会走到注释5处,返回RECOMMEND_INSTALL_INTERNAL

如果fitsOnInternal为false,就会走到注释6处,返回RECOMMEND_FAILED_INSUFFICIENT_STORAGE。

下面我们看一下fitsOnInternal()方法

/frameworks/base/core/java/com/android/internal/content/PackageHelper.java

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}

就是判断安装需要的空间大小是否小于系统能分配的存储大小。translateAllocateFlags方法如下,由于installFlags并没有设置INSTALL_ALLOCATE_AGGRESSIVE标识符,所以返回0。

public static int translateAllocateFlags(int installFlags) {if ((installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0) {return StorageManager.FLAG_ALLOCATE_AGGRESSIVE;} else {return 0;}}

下面就会调用到StorageManager的getAllocatableBytes方法

/frameworks/base/core/java/android/os/storage/StorageManager.java

public long getAllocatableBytes(@NonNull UUID storageUuid,@RequiresPermission @AllocateFlags int flags) throws IOException {try {return mStorageManager.getAllocatableBytes(convert(storageUuid), flags,mContext.getOpPackageName());//1} catch (ParcelableException e) {e.maybeRethrow(IOException.class);throw new RuntimeException(e);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}

注释1继续调用StorageManagerService的getAllocatableBytes方法

/frameworks/base/services/core/java/com/android/server/StorageManagerService.java

@Overridepublic 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();//1final long lowReserved = storage.getStorageLowBytes(path);//2final long fullReserved = storage.getStorageFullBytes(path);if (stats.isQuotaSupported(volumeUuid)) {//3final 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 {return Math.max(0, (usable + cacheClearable) - lowReserved); //4}} 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);//5}}} catch (IOException e) {throw new ParcelableException(e);} finally {Binder.restoreCallingIdentity(token);}}

注释1处会先求出分区可用的空间注释2处会求出系统运行需要的最低存储注释3处会 判断是否支持Quota,在9.0虚拟机上和真机上测试都是支持的。注释4如果支持,就会计算cacheTotal(总共的cache大小),cacheReserved(需要保留的cache大小),求出可以清理的cache大小。返回getTotalSpace + 可清理cache大小-需要保留的注释5如果不支持返回getTotalSpace的5%和500M之间的最小值

下面我们看一下getStorageLowBytes方法

/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);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);return Math.min(lowBytes, maxLowBytes);}

我们发现。系统设置的默认阈值是5%,最终返回值为getTotalSpace的5%和500M之间的最小值。

总结

至此,算是全部结束了,我们发现只要系统空间小于Math.min(getTotalSpace的5%,500M)+ PackageHelper.calculateInstalledSize(pkg, abiOverride),系统就会报空间不足。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。