应用安装策略之可用存储空间阈值
应用安装策略之可用存储空间阈值
对于从来没有深入看过源码的我来说,峰哥真的给我安排了一个棘手且完全不熟悉的任务。但是我也只好硬着头皮往下做,期间参考了网上的一些文章,启硕哥也指点了我不少,最终还是找到了规定的阈值。
期间代码不需要全看,只看画出来的重点部分即可。贴出源码是为了方便查漏补缺。
-
参考打包安装程序
- 发现基本都是调用android.content.pm.PackageManager
-
网上查询各种安装方式
-
通过shell命令的
pm install <packgename>
-
通过调用android.content.pm.PackageManager
-
-
以上两种方式发现无论怎么样最终都会调用 installer.java
-
installer.java 最终回到PackageManagerService
-
在满负荷调试中出现
INSTALL_FAILED_INSUFFICIENT_STORAGE
- 通过AndroidXRef 查询结果定位到PackageManagerService.java
-
-
根据上述两步跟踪问题,找到 PackageManagerService.java
frameworks\base\services\java\com\android\server\pm\PackageManagerService.java
-
找到 InstallParams 类
- 这一整段基本就是安装所需的各个流程,但是我们只看其中和存储空间相关的策略
class InstallParams extends HandlerParams {
//......
private InstallArgs mArgs;
private int mRet;
private File mTempPackage;
//.......
private int installLocationPolicy(PackageInfoLite pkgLite, int flags) {
String packageName = pkgLite.packageName;
int installLocation = pkgLite.installLocation;
boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;
// reader
synchronized (mPackages) {
PackageParser.Package pkg = mPackages.get(packageName);
if (pkg != null) {
if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
// Check for downgrading.
if ((flags & PackageManager.INSTALL_ALLOW_DOWNGRADE) == 0) {
if (pkgLite.versionCode < pkg.mVersionCode) {
Slog.w(TAG, "Can't install update of " + packageName
+ " update version " + pkgLite.versionCode
+ " is older than installed version "
+ pkg.mVersionCode);
return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE;
}
}
// Check for updated system application.
if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
if (onSd) {
Slog.w(TAG, "Cannot install update to system app on sdcard");
return PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION;
}
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
} else {
if (onSd) {
// Install flag overrides everything.
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
// If current upgrade specifies particular preference
if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
// Application explicitly specified internal.
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
} else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
// App explictly prefers external. Let policy decide
} else {
// Prefer previous location
if (isExternal(pkg)) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
}
}
} else {
// Invalid install. Return error code
return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;
}
}
}
// All the special cases have been taken care of.
// Return result based on recommended install location.
if (onSd) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
return pkgLite.recommendedInstallLocation;
}
/*
* 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() 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();
}
try {
//......
if (packageFile != null) {
// Remote call to find out default install location
final String packageFilePath = packageFile.getAbsolutePath();
pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags,
lowThreshold);
/*
* 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) {
pkgLite = 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) {
pkgLite.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) {
//......
} else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
//......
}
}
final InstallArgs args = createInstallArgs(this);
mArgs = args;
//......
}
//......
}
-
找到重点 handleStartCopy 函数
- 出现了前面提到的
INSTALL_FAILED_INSUFFICIENT_STORAGE
- 出现了前面提到的
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();
}
try {
//......
if (packageFile != null) {
// Remote call to find out default install location
final String packageFilePath = packageFile.getAbsolutePath();
pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags,
lowThreshold);
/*
* 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) {
pkgLite = 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) {
pkgLite.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) {
//......
} else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
//......
}
}
final InstallArgs args = createInstallArgs(this);
mArgs = args;
//......
}
-
其中有获取最低存储空间的信息 lowThreshold
- dsm是 DeviceStorageMonitorService 的实体
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();
}
-
找到 DeviceStorageMonitorService 类
frameworks\base\services\java\com\android\server\DeviceStorageMonitorService.java
-
找到 getMemoryLowThreshold
public long getMemoryLowThreshold() {
return mMemLowThreshold;
}
-
找到 mMemLowThreshold
final StorageManager sm = StorageManager.from(context);
mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH);
mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH);
mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4;
mMemCacheTrimToThreshold = mMemLowThreshold
+ ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2);
mFreeMemAfterLastCacheClear = mTotalMemory;
checkMemory(true);
-
根据 checkMemory 确认 mMemLowThreshold 是我们要找的阈值目标
private final void checkMemory(boolean checkCache) {
//if the thread that was started to clear cache is still running do nothing till its
//finished clearing cache. Ideally this flag could be modified by clearCache
// and should be accessed via a lock but even if it does this test will fail now and
//hopefully the next time this flag will be set to the correct value.
if(mClearingCache) {
//......
} else {
restatDataDir();
if (localLOGV) Slog.v(TAG, "freeMemory="+mFreeMem);
//post intent to NotificationManager to display icon if necessary
if (mFreeMem < mMemLowThreshold) {
if (checkCache) {
// We are allowed to clear cache files at this point to
// try to get down below the limit, because this is not
// the initial call after a cache clear has been attempted.
// In this case we will try a cache clear if our free
// space has gone below the cache clear limit.
if (mFreeMem < mMemCacheStartTrimThreshold) {
// We only clear the cache if the free storage has changed
// a significant amount since the last time.
if ((mFreeMemAfterLastCacheClear-mFreeMem)
>= ((mMemLowThreshold-mMemCacheStartTrimThreshold)/4)) {
// See if clearing cache helps
// Note that clearing cache is asynchronous and so we do a
// memory check again once the cache has been cleared.
mThreadStartTime = System.currentTimeMillis();
mClearSucceeded = false;
clearCache();
}
}
} else {
// This is a call from after clearing the cache. Note
// the amount of free storage at this point.
mFreeMemAfterLastCacheClear = mFreeMem;
if (!mLowMemFlag) {
// We tried to clear the cache, but that didn't get us
// below the low storage limit. Tell the user.
Slog.i(TAG, "Running low on memory. Sending notification");
sendNotification();
mLowMemFlag = true;
} else {
if (localLOGV) Slog.v(TAG, "Running low on memory " +
"notification already sent. do nothing");
}
}
} else {
//......
}
if(localLOGV) Slog.i(TAG, "Posting Message again");
//keep posting messages to itself periodically
postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
}
-
找到 StorageManager.java
\frameworks\base\core\java\android\os\storage\StorageManager.java
-
找到 getStorageLowBytes 函数
private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
private static final long DEFAULT_FULL_THRESHOLD_BYTES = 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);
}
-
确认目标
- 总存储空间的10%或500M中较小的那一个
-
找到系统判定空间可用空间较低的阈值后,根据之前的错误提示信息 INSTALL_FAILED_INSUFFICIENT_STORAGE ,定位问题
if (ret == PackageManager.INSTALL_SUCCEEDED) {
int loc = pkgLite.recommendedInstallLocation;
if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
//......
} else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
//......
}
}
-
再找到loc的声明
-
pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags, lowThreshold)
-
int loc = pkgLite.recommendedInstallLocation
-
可以看出下一步需要找到 getMinimalPackageInfo 的声明
-
-
经过一番查找,在 DefaultContainerService.java 中找到其声明
frameworks\base\packages\DefaultContainerService\src\com\android\defcontainer\DefaultContainerService.java
public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
long threshold) {
PackageInfoLite ret = new PackageInfoLite();
//...
ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
packagePath, flags, threshold);
return ret;
}
-
找到 recommendedInstallLocation 的声明
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;
//......
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);
} 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;
}
}
-
loc的关键返回判断信息语句如下
if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)
&& !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
} else {
return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
-
联系前文,依据判断条件,找到关键语句
fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold)
-
寻找其声明 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;
}
-
从上面我们可以看出返回值为 可用空间—安装大小>最低阈值
结论
- 我们终于做完了所有的事了,其结论就是:当满足 可用空间的大小 减去 安装包需要的大小 大于 最低阈值(500M或存储空间总大小的10%中的较小值) 的条件时,安装才能进行。
后记
为了找出这个规定的阈值,我查了不少文章,几乎把安装流程完全走了一遍,可以说工作量非常巨大了。并且一开始还成功地掉入了坑里,差点就把错误的结论拿出来讲了。后来想想看好像不太对劲,重新梳理了一遍逻辑才发现确实做错了。不过走这一遍,虽然不知道有什么用,但是相信也是积淀的一部分,希望在未来的日子里能够让我少踩一点坑。
以下为有帮助的工具和参考文章
AndroidXRef 查源码巨好用的网站
Android7.0 PackageManagerService
APK安装流程详解14——PMS中的新安装流程上(拷贝)补充