声明:原创文章,禁止转载!
Android9~Android13 某些容量SD卡被格式化为内部存储时容量显示错误问题的研究与解决方案
分析Android11 系统对于EMMC/UFS作为内部存储、SD卡被格式化为内部存储、SD卡/U盘被格式化为便携式存储的不同处理
一.现象描述
实测Android9 Android10 Android11 Android12 Android13系统中某些容量的SD卡在被格式化为内部存储时,在设置中的显示容量与实际容量不符,比如某些16GB容量的SD卡在设置->存储中显示为32GB,但是如果选择“格式化为便携式存储设备”的话可以正常显示容量为16GB。
在Android11系统格式化为内部存储设备和便携式存储设备
同一个SD卡在Android7系统上作为内部存储和便携式存储空间时显示如下
二.源码分析
Android11系统
packages/apps/Settings/src/com/android/settings/deviceinfo/StorageSettings.java
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
final Context context = getActivity();
mStorageManager = context.getSystemService(StorageManager.class);
if (sTotalInternalStorage <= 0) {
sTotalInternalStorage = mStorageManager.getPrimaryStorageSize();
}
addPreferencesFromResource(R.xml.device_info_storage);
mInternalCategory = (PreferenceCategory) findPreference("storage_internal");
mExternalCategory = (PreferenceCategory) findPreference("storage_external");
mInternalSummary = new StorageSummaryPreference(getPrefContext());
setHasOptionsMenu(true);
}
private synchronized void refresh() {
final Context context = getPrefContext();
getPreferenceScreen().removeAll();
mInternalCategory.removeAll();
mExternalCategory.removeAll();
mInternalCategory.addPreference(mInternalSummary);
final StorageManagerVolumeProvider smvp = new StorageManagerVolumeProvider(mStorageManager);
final PrivateStorageInfo info = PrivateStorageInfo.getPrivateStorageInfo(smvp);
final long privateTotalBytes = info.totalBytes;
final long privateUsedBytes = info.totalBytes - info.freeBytes;
final List<VolumeInfo> volumes = mStorageManager.getVolumes();
Collections.sort(volumes, VolumeInfo.getDescriptionComparator());
for (VolumeInfo vol : volumes) {
if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
if (vol.getState() == VolumeInfo.STATE_UNMOUNTABLE) {
mInternalCategory.addPreference(
new StorageVolumePreference(context, vol, 0));
} else {
final long volumeTotalBytes = PrivateStorageInfo.getTotalSize(vol,
sTotalInternalStorage);
mInternalCategory.addPreference(
new StorageVolumePreference(context, vol, volumeTotalBytes));
}
} else if (vol.getType() == VolumeInfo.TYPE_PUBLIC
|| vol.getType() == VolumeInfo.TYPE_STUB) {
mExternalCategory.addPreference(
new StorageVolumePreference(context, vol, 0));
}
}
packages/apps/Settings/src/com/android/settings/deviceinfo/StorageVolumePreference.java
public StorageVolumePreference(Context context, VolumeInfo volume, long totalBytes) {
super(context);
mStorageManager = context.getSystemService(StorageManager.class);
mVolume = volume;
if (volume.isMountedReadable()) {
// TODO: move statfs() to background thread
final File path = volume.getPath();
long freeBytes = 0;
long usedBytes = 0;
if (volume.getType() == VolumeInfo.TYPE_PRIVATE) {
final StorageStatsManager stats =
context.getSystemService(StorageStatsManager.class);
try {
//作为TYPE_PRIVATE,调用StorageStatsManager.getTotalBytes接口获取存储总容量大小
totalBytes = stats.getTotalBytes(volume.getFsUuid());
//作为TYPE_PRIVATE,调用StorageStatsManager.getFreeBytes接口获取存储可用容量大小
freeBytes = stats.getFreeBytes(volume.getFsUuid());
usedBytes = totalBytes - freeBytes;
} catch (IOException e) {
Log.w(TAG, e);
}
} else {
// StorageStatsManager can only query private volumes.
// Default to previous storage calculation for public volumes.
if (totalBytes <= 0) {
/*
作为便携式存储,调用File.getTotalSpace接口获取存储总容量大小。
注意此处并没有调用FileUtils.roundStorageSize接口进行向上整数对齐,
那么为什么这个SD卡被格式化为便携式存储设备后在设置中显示的是"16GB"整数呢,
下面会有详细解答
*/
totalBytes = path.getTotalSpace();
}
freeBytes = path.getFreeSpace();//作为便携式存储,调用File.getFreeSpace接口获取存储可用容量大小
usedBytes = totalBytes - freeBytes;
}
final String used = Formatter.formatFileSize(context, usedBytes);
final String total = Formatter.formatFileSize(context, totalBytes);
setSummary(context.getString(R.string.storage_volume_summary, used, total));
if (totalBytes > 0) {
mUsedPercent = (int) ((usedBytes * 100) / totalBytes);
}
frameworks/base/core/java/android/app/usage/StorageStatsManager.java
private final IStorageStatsManager mService;
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();
}
}
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();
}
}
frameworks/base/core/java/android/app/usage/IStorageStatsManager.aidl
interface IStorageStatsManager {
boolean isQuotaSupported(String volumeU