问题背景
在Android12平台上,恢复出厂设置后,已运用空间偏高,空间运用率为28/128=21.9%,产品需求控制在11%以内
源码剖析
当前页面的源码位于packages/apps/Settings/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
/**
* VolumeSizeCallbacks exists because StorageCategoryFragment already implements
* LoaderCallbacks for a different type.
*/
public final class VolumeSizeCallbacks
implements LoaderManager.LoaderCallbacks<PrivateStorageInfo> {
@Override
public Loader<PrivateStorageInfo> onCreateLoader(int id, Bundle args) {
final Context context = getContext();
final StorageManagerVolumeProvider smvp =
new StorageManagerVolumeProvider(mStorageManager);
final StorageStatsManager stats = context.getSystemService(StorageStatsManager.class);
//运用loader技术加载存储卷信息
return new VolumeSizesLoader(context, smvp, stats,
mSelectedStorageEntry.getVolumeInfo());
}
@Override
public void onLoaderReset(Loader<PrivateStorageInfo> loader) {
}
@Override
public void onLoadFinished(
Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo) {
if (privateStorageInfo == null) {
getActivity().finish();
return;
}
//loader加载完毕,返回存储卷信息
mStorageInfo = privateStorageInfo;
//更新UI
onReceivedSizes();
}
}
private void onReceivedSizes() {
if (mStorageInfo == null || mAppsResult == null) {
return;
}
if (getView().findViewById(R.id.loading_container).getVisibility() == View.VISIBLE) {
setLoading(false /* loading */, true /* animate */);
}
//已用空间=总空间-可用空间
final long privateUsedBytes = mStorageInfo.totalBytes - mStorageInfo.freeBytes;
//绑定ui
mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
mPreferenceController.setUsedSize(privateUsedBytes);
mPreferenceController.setTotalSize(mStorageInfo.totalBytes);
for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) {
final AbstractPreferenceController controller = mSecondaryUsers.get(i);
if (controller instanceof SecondaryUserController) {
SecondaryUserController userController = (SecondaryUserController) controller;
userController.setTotalSize(mStorageInfo.totalBytes);
}
}
//更新分类item
mPreferenceController.onLoadFinished(mAppsResult, mUserId);
updateSecondaryUserControllers(mSecondaryUsers, mAppsResult);
setSecondaryUsersVisible(true);
}
VolumeSizesLoader
继承自AsyncTaskLoader
,主要是要看VolumeSizesLoader
是怎么加载存储卷信息的
@Override
public PrivateStorageInfo loadInBackground() {
PrivateStorageInfo volumeSizes;
try {
//子线程加载
volumeSizes = getVolumeSize(mVolumeProvider, mStats, mVolume);
} catch (IOException e) {
return null;
}
return volumeSizes;
}
@VisibleForTesting
static PrivateStorageInfo getVolumeSize(
StorageVolumeProvider storageVolumeProvider, StorageStatsManager stats, VolumeInfo info)
throws IOException {
//运用StorageVolumeProvider直接获取
long privateTotalBytes = storageVolumeProvider.getTotalBytes(stats, info);
long privateFreeBytes = storageVolumeProvider.getFreeBytes(stats, info);
return new PrivateStorageInfo(privateFreeBytes, privateTotalBytes);
}
StorageVolumeProvider
是一个接口,主要效果便是提供对存储卷的拜访,其实现类为StorageManagerVolumeProvider
@Override
public long getTotalBytes(StorageStatsManager stats, VolumeInfo volume) throws IOException {
return stats.getTotalBytes(volume.getFsUuid());
}
@Override
public long getFreeBytes(StorageStatsManager stats, VolumeInfo volume) throws IOException {
return stats.getFreeBytes(volume.getFsUuid());
}
@WorkerThread
public @BytesLong long getTotalBytes(@NonNull UUID storageUuid) throws IOException {
try {
return mService.getTotalBytes(convert(storageUuid), mContext.getOpPackageName());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@WorkerThread
public @BytesLong long getFreeBytes(@NonNull UUID storageUuid) throws IOException {
try {
return mService.getFreeBytes(convert(storageUuid), mContext.getOpPackageName());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
能够看到终究调用了远程StorageStatsService
的功用
@Override
public long getTotalBytes(String volumeUuid, String callingPackage) {
// NOTE: No permissions required
if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
//代码1,获取基础存储巨细并格局化返回(经调试,执行这儿的逻辑)
return FileUtils.roundStorageSize(mStorage.getPrimaryStorageSize());
} else {
final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid);
if (vol == null) {
throw new ParcelableException(
new IOException("Failed to find storage device for UUID " + volumeUuid));
}
Log.d("jasonwan", "vol.disk.sysPath:"+vol.disk.sysPath+", vol.disk.size:"+vol.disk.size);
return FileUtils.roundStorageSize(vol.disk.size);
}
}
@Override
public long getFreeBytes(String volumeUuid, String callingPackage) {
// NOTE: No permissions required
final long token = Binder.clearCallingIdentity();
try {
final File path;
try {
//获取对应存储卷的途径
path = mStorage.findPathForUuid(volumeUuid);
Log.d("jasonwan", "free path: "+path.getAbsolutePath()+", and size is:"+path.getUsableSpace());
} catch (FileNotFoundException e) {
throw new ParcelableException(e);
}
// Free space is usable bytes plus any cached data that we're
// willing to automatically clear. To avoid user confusion, this
// logic should be kept in sync with getAllocatableBytes().
if (isQuotaSupported(volumeUuid, PLATFORM_PACKAGE_NAME)) {
final long cacheTotal = getCacheBytes(volumeUuid, PLATFORM_PACKAGE_NAME);
final long cacheReserved = mStorage.getStorageCacheBytes(path, 0);
final long cacheClearable = Math.max(0, cacheTotal - cacheReserved);
Log.d("jasonwan", "cacheClearable size: "+cacheClearable);
return path.getUsableSpace() + cacheClearable;
} else {
//返回途径可用空间巨细
return path.getUsableSpace();
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
看看mStorage.getPrimaryStorageSize()
的代码
/** {@hide} */
public long getPrimaryStorageSize() {
File dataDirectory = Environment.getDataDirectory();
File rootDirectory = Environment.getRootDirectory();
//代码2
long total = FileUtils.roundStorageSize(dataDirectory.getTotalSpace() + rootDirectory.getTotalSpace());
Log.d("jasonwan", "Environment.getDataDirectory():"+ dataDirectory +", totalSpace:"+ dataDirectory.getTotalSpace()+", freeSpace:"+dataDirectory.getFreeSpace()+", usedSpace:"+(dataDirectory.getTotalSpace()-dataDirectory.getFreeSpace()));
Log.d("jasonwan", "Environment.getRootDirectory():"+ rootDirectory+", totalSpace:"+rootDirectory.getTotalSpace()+", freeSpace:"+rootDirectory.getFreeSpace()+", usedSpace:"+(rootDirectory.getTotalSpace()-rootDirectory.getFreeSpace()));
Log.d("jasonwan","calculate total:"+total+", real total:"+(dataDirectory.getTotalSpace() + rootDirectory.getTotalSpace()));
Log.d("jasonwan","real free:"+(dataDirectory.getFreeSpace()+rootDirectory.getFreeSpace()));
return total;
}
总巨细便是获取的/data
目录和/system
目录的总巨细,这儿经过自定义log,打印出实践途径
05-06 00:48:54.353 1222 4361 D jasonwan: Environment.getDataDirectory():/data, totalSpace:101534478336, freeSpace:97527693312, usedSpace:4006785024
05-06 00:48:54.353 1222 4361 D jasonwan: Environment.getRootDirectory():/system, totalSpace:2145386496, freeSpace:1651765248, usedSpace:493621248
05-06 00:48:54.353 1222 4361 D jasonwan: calculate total:128000000000, real total:103679864832
05-06 00:48:54.353 1222 4361 D jasonwan: real free:99179458560
而可用空间的巨细便是获取的/data
目录的可用巨细,同样,这儿经过自定义log,打印出实践途径
05-06 00:48:54.358 1222 2079 D jasonwan: free path: /data, and size is:97393475584
05-06 00:48:54.481 1222 4361 D jasonwan: cacheClearable size: 0
这儿有个很奇怪的当地,便是核算出来的byte巨细经过FileUtils.roundStorageSize()
或者Formatter.formatBytes()
格局化后变大了,代码1处,mStorage.getPrimaryStorageSize()
为103679864832,经过FileUtils.roundStorageSize()
格局化后变成了128000000000,来看下FileUtils.roundStorageSize()
的核算原理
/**
* Round the given size of a storage device to a nice round power-of-two
* value, such as 256MB or 32GB. This avoids showing weird values like
* "29.5GB" in UI.
*
* @hide
*/
public static long roundStorageSize(long size) {
long val = 1;
long pow = 1;
while ((val * pow) < size) {
val <<= 1;
if (val > 512) {
val = 1;
pow *= 1000;
}
}
return val * pow;
}
依据办法注释阐明,该办法将经过四舍五入,将参数值变成接近的2的n次幂,比方:
- 100->128
- 129->256
- 257->512
- 513->1000
留意,终究不是1024,而是1000,这个跟咱们实践生活是很贴近的,比方咱们去买一个U盘,老板说有2G的,4G的,32G的,但绝不会说有3G的,3.5G的。可见此格局化办法终究会将实在数值变大。而Formatter.formatBytes()
在将Bytes变成KB、MB、GB时,会以1000作为计量单位,而不是1024
/** {@hide} */
@UnsupportedAppUsage
public static BytesResult formatBytes(Resources res, long sizeBytes, int flags) {
//这儿unit的值终究等于1000,不是1024
final int unit = ((flags & FLAG_IEC_UNITS) != 0) ? 1024 : 1000;
final boolean isNegative = (sizeBytes < 0);
float result = isNegative ? -sizeBytes : sizeBytes;
int suffix = com.android.internal.R.string.byteShort;
long mult = 1;
if (result > 900) {
suffix = com.android.internal.R.string.kilobyteShort;
mult = unit;
result = result / unit;
}
if (result > 900) {
suffix = com.android.internal.R.string.megabyteShort;
mult *= unit;
result = result / unit;
}
if (result > 900) {
suffix = com.android.internal.R.string.gigabyteShort;
mult *= unit;
result = result / unit;
}
if (result > 900) {
suffix = com.android.internal.R.string.terabyteShort;
mult *= unit;
result = result / unit;
}
if (result > 900) {
suffix = com.android.internal.R.string.petabyteShort;
mult *= unit;
result = result / unit;
}
// Note we calculate the rounded long by ourselves, but still let String.format()
// compute the rounded value. String.format("%f", 0.1) might not return "0.1" due to
// floating point errors.
final int roundFactor;
final String roundFormat;
if (mult == 1 || result >= 100) {
roundFactor = 1;
roundFormat = "%.0f";
} else if (result < 1) {
roundFactor = 100;
roundFormat = "%.2f";
} else if (result < 10) {
if ((flags & FLAG_SHORTER) != 0) {
roundFactor = 10;
roundFormat = "%.1f";
} else {
roundFactor = 100;
roundFormat = "%.2f";
}
} else { // 10 <= result < 100
if ((flags & FLAG_SHORTER) != 0) {
roundFactor = 1;
roundFormat = "%.0f";
} else {
roundFactor = 100;
roundFormat = "%.2f";
}
}
if (isNegative) {
result = -result;
}
final String roundedString = String.format(roundFormat, result);
// Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like 80PB so
// it's okay (for now)...
final long roundedBytes =
(flags & FLAG_CALCULATE_ROUNDED) == 0 ? 0
: (((long) Math.round(result * roundFactor)) * mult / roundFactor);
final String units = res.getString(suffix);
return new BytesResult(roundedString, units, roundedBytes);
}
综上,实在总巨细103679864832先换算为128000000000,然后格局为GB即为128GB,也便是文章最初咱们看到的总巨细128GB,已运用空间同理。
解决方案
那如果咱们运用实在的bytes巨细来核算空间运用率,则为1-97393475584/103679864832=6.1%,远低于界面显示的21.9%,同时也符合产品的需求。所以这只是核算误差问题,实践运用率并没有那么高。