前言
在 Android 9.0 PM机制系列(四) APK安装需要空间分析以及 Android 6.0 PM机制系列(四) APK安装需要空间分析两篇文章中,我们重点分析了Android9.0以及Android6.0所需要的最小APK安装存储空间大小。本篇我们要分析Android4.4安装APK所需要空间大小。
1. 开始安装
分析源码,一直跟到PMS里面,发现和Android9.0,6.0一样,安装核心代码开始处就在handleStartCopy方法,代码如下:
/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public void handleStartCopy() throws RemoteException {int ret = PackageManager.INSTALL_SUCCEEDED;final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;final boolean onInt = (flags & 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 {final long lowThreshold;final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager.getService(DeviceStorageMonitorService.SERVICE);if (dsm == null) {Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");lowThreshold = 0L;} else {lowThreshold = dsm.getMemoryLowThreshold();//1}try {mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, mPackageURI,Intent.FLAG_GRANT_READ_URI_PERMISSION);final File packageFile;if (encryptionParams != null || !"file".equals(mPackageURI.getScheme())) {//2mTempPackage = createTempPackageFile(mDrmAppPrivateInstallDir);if (mTempPackage != null) {ParcelFileDescriptor out;try {out = ParcelFileDescriptor.open(mTempPackage,ParcelFileDescriptor.MODE_READ_WRITE);} catch (FileNotFoundException e) {out = null;Slog.e(TAG, "Failed to create temporary file for : " + mPackageURI);}// Make a temporary file for decryption.ret = mContainerService.copyResource(mPackageURI, encryptionParams, out);IoUtils.closeQuietly(out);packageFile = mTempPackage;FileUtils.setPermissions(packageFile.getAbsolutePath(),FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP| FileUtils.S_IROTH,-1, -1);} else {packageFile = null;}} else {packageFile = new File(mPackageURI.getPath());}if (packageFile != null) {// Remote call to find out default install locationfinal String packageFilePath = packageFile.getAbsolutePath();pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags,lowThreshold);//3/** If we have too little free space, try to free cache* before giving up.*/if (pkgLite.recommendedInstallLocation== PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {final long size = mContainerService.calculateInstalledSize(packageFilePath, isForwardLocked());if (mInstaller.freeCache(size + lowThreshold) >= 0) {//4pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath,flags, lowThreshold); } /** 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) {//5pkgLite.recommendedInstallLocation= PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;}}}} finally {mContext.revokeUriPermission(mPackageURI,Intent.FLAG_GRANT_READ_URI_PERMISSION);}}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) {//6ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;} ...} else {// Override with defaults if needed.loc = installLocationPolicy(pkgLite, flags);if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) {ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;} else if (!onSd && !onInt) {// Override install location with flagsif (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {// Set the flag to install on external media.flags |= PackageManager.INSTALL_EXTERNAL;flags &= ~PackageManager.INSTALL_INTERNAL;} else {// Make sure the flag for installing on external// media is unsetflags |= PackageManager.INSTALL_INTERNAL;flags &= ~PackageManager.INSTALL_EXTERNAL;}}}}final InstallArgs args = createInstallArgs(this);mArgs = args;if (ret == PackageManager.INSTALL_SUCCEEDED) {/** ADB installs appear as UserHandle.USER_ALL, and can only be performed by* UserHandle.USER_OWNER, so use the package verifier for UserHandle.USER_OWNER.*/int userIdentifier = getUser().getIdentifier();if (userIdentifier == UserHandle.USER_ALL&& ((flags & PackageManager.INSTALL_FROM_ADB) != 0)) {userIdentifier = UserHandle.USER_OWNER;}/** Determine if we have any installed package verifiers. If we* do, then we'll defer to them to verify the packages.*/final int requiredUid = mRequiredVerifierPackage == null ? -1: getPackageUid(mRequiredVerifierPackage, userIdentifier);if (requiredUid != -1 && isVerificationEnabled(flags)) {...} else {/** No package verification is enabled, so immediately start* the remote call to initiate copy using temporary file.*/ret = args.copyApk(mContainerService, true); //6}}mRet = ret;}
注释1首先会获取系统运行预留最低存储空间lowThreshold。注释2由于在PI传参时会为Null,所以不会执行此处代码
pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,installerPackageName, verificationParams, null);
注释3根据lowThreshold获取最小包信息,在这里面就会给安装位置进行赋值。注释4,如果空间不足就会freeCache释放cache,然后继续获取最小包信,看一下是否可以安装注释5,如果安装位置是RECOMMEND_FAILED_INVALID_URI就返回INSTALL_FAILED_INSUFFICIENT_STORAGE注释6,如果还是空间不足就返回INSTALL_FAILED_INSUFFICIENT_STORAGE
首先我们先看一下获取lowThreshold的值
/frameworks/base/services/java/com/android/server/DeviceStorageMonitorService.java
public long getMemoryLowThreshold() {return mMemLowThreshold;}private static final File DATA_PATH = Environment.getDataDirectory();public DeviceStorageMonitorService(Context context) {...mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH);...}
/frameworks/base/core/java/android/os/storage/StorageManager.java
private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;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);}
通过代码发现,我们得到的mMemLowThreshold阈值就是用户可用总存储的10%和500M的最小值。
其次,我们看一下getMinimalPackageInfo干了什么事?
/frameworks/base/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,long threshold) {PackageInfoLite ret = new PackageInfoLite();...PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packagePath, 0);if (pkg == null) {Slog.w(TAG, "Failed to parse package");final File apkFile = new File(packagePath);if (!apkFile.exists()) {ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;} else {ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;}return ret;}ret.packageName = pkg.packageName;ret.versionCode = pkg.versionCode;ret.installLocation = pkg.installLocation;ret.verifiers = pkg.verifiers;ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,packagePath, flags, threshold); //1return ret;}private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags,long threshold) {int prefer;boolean checkBoth = false;final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;check_inner : {/** Explicit install flags should override the manifest settings.*/if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {prefer = PREFER_INTERNAL;break check_inner;} else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) {prefer = PREFER_EXTERNAL;break check_inner;}/* No install flags. Check for manifest option. */if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {prefer = PREFER_INTERNAL;break check_inner;} else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {prefer = PREFER_EXTERNAL;checkBoth = true;break check_inner;} else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {// We default to preferring internal storage.prefer = PREFER_INTERNAL;checkBoth = true;break check_inner;}// Pick user preferenceint installPreference = Settings.Global.getInt(getApplicationContext().getContentResolver(),Settings.Global.DEFAULT_INSTALL_LOCATION,PackageHelper.APP_INSTALL_AUTO);if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) {prefer = PREFER_INTERNAL;break check_inner;} else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) {prefer = PREFER_EXTERNAL;break check_inner;}/** Fall back to default policy of internal-only if nothing else is* specified.*/prefer = PREFER_INTERNAL;}final boolean emulated = Environment.isExternalStorageEmulated();final File apkFile = new File(archiveFilePath);boolean fitsOnInternal = false;if (checkBoth || prefer == PREFER_INTERNAL) {try {fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold); //2} catch (IOException e) {return PackageHelper.RECOMMEND_FAILED_INVALID_URI;}}boolean fitsOnSd = false;if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) {try {fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked);} catch (IOException e) {return PackageHelper.RECOMMEND_FAILED_INVALID_URI;}}if (prefer == PREFER_INTERNAL) {if (fitsOnInternal) {return PackageHelper.RECOMMEND_INSTALL_INTERNAL;}} else if (!emulated && prefer == PREFER_EXTERNAL) {if (fitsOnSd) {return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;}}if (checkBoth) {if (fitsOnInternal) {return PackageHelper.RECOMMEND_INSTALL_INTERNAL;} else if (!emulated && fitsOnSd) {return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;}}/** If they requested to be on the external media by default, return that* the media was unavailable. Otherwise, indicate there was insufficient* storage space available.*/if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)&& !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;} else {return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;}}
注释2会调用isUnderInternalThreshold方法判断内部存储空间是否可以提供足够的安装空间大小。
private boolean isUnderInternalThreshold(File apkFile, boolean isForwardLocked, long threshold)throws IOException {long size = apkFile.length();if (size == 0 && !apkFile.exists()) {throw new FileNotFoundException();}if (isForwardLocked) {size += PackageHelper.extractPublicFiles(apkFile.getAbsolutePath(), null);}final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());final long availInternalSize = (long) internalStats.getAvailableBlocks()* (long) internalStats.getBlockSize();return (availInternalSize - size) > threshold;//3}
注释3可以看到内部可用空间 > apk大小 + 阈值才可以。
结论
Android4.4 APK安装需要空间为apk大小 + 预留阈值(用户可用总存储的10%和500M的最小值)