声明:原创文章,禁止转载!
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
@Overridepublic 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 threadfinal 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