问题描绘
Android13 上进行SD卡格式化,格式化后显现的SD卡容量为0,退出从头进入,显现正常。
源码剖析
首先在Settings
->Storage
页面,切换到SD card,页面将显现SD card的存储信息
代码坐落packages/apps/Settings/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
@Override
public void onResume() {
super.onResume();
if (mIsLoadedFromCache) {
mIsLoadedFromCache = false;
} else {
mStorageEntries.clear();
mStorageEntries.addAll(
StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
Log.d("jasonwan", "---refreshUi-09---");
//改写UI
refreshUi();
}
mStorageManager.registerListener(mStorageEventListener);
}
private void refreshUi() {
mStorageSelectionController.setStorageEntries(mStorageEntries);
mStorageSelectionController.setSelectedStorageEntry(mSelectedStorageEntry);
//设置已挑选的存储项
mStorageUsageProgressBarController.setSelectedStorageEntry(mSelectedStorageEntry);
mOptionMenuController.setSelectedStorageEntry(mSelectedStorageEntry);
getActivity().invalidateOptionsMenu();
// To prevent flicker, hides secondary users preference.
// onReceivedSizes will set it visible for private storage.
setSecondaryUsersVisible(false);
if (!mSelectedStorageEntry.isMounted()) {
// Set null volume to hide category stats.
mPreferenceController.setVolume(null);
return;
}
Log.d("jasonwan", " mStorageCacheHelper.hasCachedSizeInfo()="+mStorageCacheHelper.hasCachedSizeInfo());
Log.d("jasonwan", " mSelectedStorageEntry.isPrivate()="+mSelectedStorageEntry.isPrivate());
//sdcard属于public volume,因而这里的isPrivate()为false
if (mStorageCacheHelper.hasCachedSizeInfo() && mSelectedStorageEntry.isPrivate()) {
StorageCacheHelper.StorageCache cachedData = mStorageCacheHelper.retrieveCachedSize();
mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
mPreferenceController.setUsedSize(cachedData.totalUsedSize);
mPreferenceController.setTotalSize(cachedData.totalSize);
Log.d("jasonwan", " totalUsedSize="+cachedData.totalUsedSize+", totalSize="+cachedData.totalSize);
}
if (mSelectedStorageEntry.isPrivate()) {
mStorageInfo = null;
mAppsResult = null;
// Hide the loading spinner if there is cached data.
if (mStorageCacheHelper.hasCachedSizeInfo()) {
//TODO(b/220259287): apply cache mechanism to secondary user
mPreferenceController.onLoadFinished(mAppsResult, mUserId);
} else {
maybeSetLoading(isQuotaSupported());
// To prevent flicker, sets null volume to hide category preferences.
// onReceivedSizes will setVolume with the volume of selected storage.
mPreferenceController.setVolume(null);
}
// Stats data is only available on private volumes.
getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
getLoaderManager()
.restartLoader(VOLUME_SIZE_JOB_ID, Bundle.EMPTY, new VolumeSizeCallbacks());
getLoaderManager().restartLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks());
} else {
mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
}
}
在refreshUi
办法中,会进行容量巨细的显现,sdcard由于是public volume,所以isPrivate()
办法为false,sdcard的容量巨细核算及显现在mStorageUsageProgressBarController.setSelectedStorageEntry(mSelectedStorageEntry)
中
/** Set StorageEntry to display. */
public void setSelectedStorageEntry(StorageEntry storageEntry) {
mStorageEntry = storageEntry;
//获取sdcard存储状况并更新UI
getStorageStatsAndUpdateUi();
}
private void getStorageStatsAndUpdateUi() {
// Use cached data for both total size and used size.
Log.d("jasonwan", "---getStorageStatsAndUpdateUi---");
Log.d("jasonwan", " mStorageEntry=n"+mStorageEntry.toString());
Log.d("jasonwan", " mStorageEntry.isMounted()="+mStorageEntry.isMounted());
Log.d("jasonwan", " mStorageEntry.isPrivate()="+mStorageEntry.isPrivate());
//sdcard的isPrivate()为false,因而,这里的if不履行
if (mStorageEntry != null && mStorageEntry.isMounted() && mStorageEntry.isPrivate()) {
StorageCacheHelper.StorageCache cachedData = mStorageCacheHelper.retrieveCachedSize();
mTotalBytes = cachedData.totalSize;
mUsedBytes = cachedData.totalUsedSize;
mIsUpdateStateFromSelectedStorageEntry = true;
Log.d("jasonwan", " 01-mUsedBytes="+mUsedBytes);
Log.d("jasonwan", " 01-mTotalBytes="+mTotalBytes);
updateState(mUsageProgressBarPreference);
}
// Get the latest data from StorageStatsManager.
//从StorageStatsManager中获取最新的数据,在子线程履行
ThreadUtils.postOnBackgroundThread(() -> {
try {
if (mStorageEntry == null || !mStorageEntry.isMounted()) {
throw new IOException();
}
//sdcard的isPrivate()为false,因而,这里履行else
if (mStorageEntry.isPrivate()) {
// StorageStatsManager can only query private storages.
mTotalBytes = mStorageStatsManager.getTotalBytes(mStorageEntry.getFsUuid());
mUsedBytes = mTotalBytes
- mStorageStatsManager.getFreeBytes(mStorageEntry.getFsUuid());
Log.d("jasonwan", " 02-mUsedBytes="+mUsedBytes);
Log.d("jasonwan", " 02-mTotalBytes="+mTotalBytes);
} else {
//获取sdcard的File目标
final File rootFile = mStorageEntry.getPath();
Log.d("jasonwan", " rootFile == null? "+(rootFile==null?"null":rootFile.getAbsolutePath()));
if (rootFile == null) {
Log.d(TAG, "Mounted public storage has null root path: " + mStorageEntry);
throw new IOException();
}
//直接经过File目标获取总容量巨细
mTotalBytes = rootFile.getTotalSpace();
mUsedBytes = mTotalBytes - rootFile.getFreeSpace();
//自定义log,打印容量巨细的值
Log.d("jasonwan", " 03-mUsedBytes="+mUsedBytes);
Log.d("jasonwan", " 03-mTotalBytes="+mTotalBytes);
}
} catch (IOException e) {
// The storage device isn't present.
mTotalBytes = 0;
mUsedBytes = 0;
Log.d("jasonwan", " 04-mUsedBytes="+mUsedBytes);
Log.d("jasonwan", " 04-mTotalBytes="+mTotalBytes);
}
if (mUsageProgressBarPreference == null) {
return;
}
mIsUpdateStateFromSelectedStorageEntry = true;
ThreadUtils.postOnMainThread(() -> updateState(mUsageProgressBarPreference));
});
}
在getStorageStatsAndUpdateUi
办法中创立了一个子线程,并在子线程中直接获取sdcard的File
目标,并经过File
目标的getTotalSpace
办法获取总容量巨细,经过自定义日志,我们得到以下信息
能够看到sdcard的类型为PUBLIC,diskId为disk:179,0,挂载状况为可写,路径为/storage/70D3-1521,而且终究打印的容量巨细为31448498176,单位Bytes,换算下来为32GB,已运用巨细为851968,大约为832KB。
此时,点击右上角的菜单,挑选“Format”进行格式化
此时点击“Format”,确认格式化,系统将对sdcard进行格式化,其代码坐落packages/apps/Settings/src/com/android/settings/deviceinfo/StorageWizardFormatConfirm.java
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getContext();
final Bundle args = getArguments();
final String diskId = args.getString(EXTRA_DISK_ID);
final String formatForgetUuid = args.getString(EXTRA_FORMAT_FORGET_UUID);
final boolean formatPrivate = args.getBoolean(EXTRA_FORMAT_PRIVATE, false);
final DiskInfo disk = context.getSystemService(StorageManager.class)
.findDiskById(diskId);
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(TextUtils.expandTemplate(
getText(R.string.storage_wizard_format_confirm_v2_title),
disk.getShortDescription()));
if (formatPrivate) {
builder.setMessage(TextUtils.expandTemplate(
getText(R.string.storage_wizard_format_confirm_v2_body),
disk.getDescription(),
disk.getShortDescription(),
disk.getShortDescription()));
} else {
builder.setMessage(TextUtils.expandTemplate(
getText(R.string.storage_wizard_format_confirm_v2_body_external),
disk.getDescription(),
disk.getShortDescription(),
disk.getShortDescription()));
}
builder.setNegativeButton(android.R.string.cancel, null);
builder.setPositiveButton(
TextUtils.expandTemplate(getText(R.string.storage_menu_format_option),
disk.getShortDescription()),
(dialog, which) -> {
//点击Format按钮
Log.d("jasonwan","click format button");
final Intent intent = new Intent(context, StorageWizardFormatProgress.class);
intent.putExtra(EXTRA_DISK_ID, diskId);
intent.putExtra(EXTRA_FORMAT_FORGET_UUID, formatForgetUuid);
intent.putExtra(EXTRA_FORMAT_PRIVATE, formatPrivate);
context.startActivity(intent);
});
return builder.create();
}
点击完“Format”按钮后,格式化功能将在StorageWizardFormatProgress
页面进行
public class StorageWizardFormatProgress extends StorageWizardBase {
private static final String TAG = "StorageWizardFormatProgress";
private static final String PROP_DEBUG_STORAGE_SLOW = "sys.debug.storage_slow";
private boolean mFormatPrivate;
private PartitionTask mTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mDisk == null) {
finish();
return;
}
setContentView(R.layout.storage_wizard_progress);
setKeepScreenOn(true);
mFormatPrivate = getIntent().getBooleanExtra(EXTRA_FORMAT_PRIVATE, false);
setHeaderText(R.string.storage_wizard_format_progress_title, getDiskShortDescription());
setBodyText(R.string.storage_wizard_format_progress_body, getDiskDescription());
setBackButtonVisibility(View.INVISIBLE);
setNextButtonVisibility(View.INVISIBLE);
mTask = (PartitionTask) getLastCustomNonConfigurationInstance();
//创立异步使命进行格式化
if (mTask == null) {
mTask = new PartitionTask();
mTask.setActivity(this);
mTask.execute();
} else {
mTask.setActivity(this);
}
}
@Override
public Object onRetainCustomNonConfigurationInstance() {
return mTask;
}
public static class PartitionTask extends AsyncTask<Void, Integer, Exception> {
public StorageWizardFormatProgress mActivity;
private volatile int mProgress = 20;
private volatile long mPrivateBench;
@Override
protected Exception doInBackground(Void... params) {
//异步使命履行中,在子线程完结
final StorageWizardFormatProgress activity = mActivity;
final StorageManager storage = mActivity.mStorage;
try {
Log.d("jasonwan","---formatting---");
Log.d("jasonwan"," activity.mFormatPrivate="+activity.mFormatPrivate);
//sdcard属于public volume,这里的mFormatPrivate为false
if (activity.mFormatPrivate) {
storage.partitionPrivate(activity.mDisk.getId());
publishProgress(40);
final VolumeInfo privateVol = activity.findFirstVolume(TYPE_PRIVATE, 50);
final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
if(null != privateVol) {
storage.benchmark(privateVol.getId(), new IVoldTaskListener.Stub() {
@Override
public void onStatus(int status, PersistableBundle extras) {
// Map benchmark 0-100% progress onto 40-80%
publishProgress(40 + ((status * 40) / 100));
}
@Override
public void onFinished(int status, PersistableBundle extras) {
result.complete(extras);
}
});
mPrivateBench = result.get(60, TimeUnit.SECONDS).getLong("run",
Long.MAX_VALUE);
}
// If we just adopted the device that had been providing
// physical storage, then automatically move storage to the
// new emulated volume.
if (activity.mDisk.isDefaultPrimary()
&& Objects.equals(storage.getPrimaryStorageUuid(),
StorageManager.UUID_PRIMARY_PHYSICAL)) {
Log.d(TAG, "Just formatted primary physical; silently moving "
+ "storage to new emulated volume");
storage.setPrimaryStorageUuid(privateVol.getFsUuid(), new SilentObserver());
}
} else {
//履行格式化,传入diskId
storage.partitionPublic(activity.mDisk.getId());
}
return null;
} catch (Exception e) {
return e;
}
}
@Override
protected void onProgressUpdate(Integer... progress) {
//更新使命进展
mProgress = progress[0];
mActivity.setCurrentProgress(mProgress);
}
public void setActivity(StorageWizardFormatProgress activity) {
mActivity = activity;
mActivity.setCurrentProgress(mProgress);
}
@Override
protected void onPostExecute(Exception e) {
//异步使命完结后
final StorageWizardFormatProgress activity = mActivity;
if (activity.isDestroyed()) {
return;
}
if (e != null) {
Log.e(TAG, "Failed to partition", e);
Toast.makeText(activity, e.getMessage(), Toast.LENGTH_LONG).show();
activity.finishAffinity();
return;
}
if (activity.mFormatPrivate) {
// When the adoptable storage feature originally launched, we
// benchmarked both internal storage and the newly adopted
// storage and we warned if the adopted device was less than
// 0.25x the speed of internal. (The goal was to help set user
// expectations and encourage use of devices comparable to
// internal storage performance.)
// However, since then, internal storage has started moving from
// eMMC to UFS, which can significantly outperform adopted
// devices, causing the speed warning to always trigger. To
// mitigate this, we've switched to using a static threshold.
// The static threshold was derived by running the benchmark on
// a wide selection of SD cards from several vendors; here are
// some 50th percentile results from 20+ runs of each card:
// 8GB C4 40MB/s+: 3282ms
// 16GB C10 40MB/s+: 1881ms
// 32GB C10 40MB/s+: 2897ms
// 32GB U3 80MB/s+: 1595ms
// 32GB C10 80MB/s+: 1680ms
// 128GB U1 80MB/s+: 1532ms
// Thus a 2000ms static threshold strikes a reasonable balance
// to help us identify slower cards. Users can still proceed
// with these slower cards; we're just showing a warning.
// The above analysis was done using the "r1572:w1001:s285"
// benchmark, and it should be redone any time the benchmark
// changes.
Log.d(TAG, "New volume took " + mPrivateBench + "ms to run benchmark");
if (mPrivateBench > 2000
|| SystemProperties.getBoolean(PROP_DEBUG_STORAGE_SLOW, false)) {
mActivity.onFormatFinishedSlow();
} else {
mActivity.onFormatFinished();
}
} else {
//格式化已完结
mActivity.onFormatFinished();
}
}
}
public void onFormatFinished() {
//跳转到完结页面
final Intent intent = new Intent(this, StorageWizardFormatSlow.class);
intent.putExtra(EXTRA_FORMAT_SLOW, false);
startActivity(intent);
//关闭当时页面
finishAffinity();
}
public void onFormatFinishedSlow() {
final Intent intent = new Intent(this, StorageWizardFormatSlow.class);
intent.putExtra(EXTRA_FORMAT_SLOW, true);
startActivity(intent);
finishAffinity();
}
private static class SilentObserver extends IPackageMoveObserver.Stub {
@Override
public void onCreated(int moveId, Bundle extras) {
// Ignored
}
@Override
public void onStatusChanged(int moveId, int status, long estMillis) {
// Ignored
}
}
}
StorageWizardFormatProgress
页面创立了一个 PartitionTask
来进行格式化,格式化由storage.partitionPublic(activity.mDisk.getId())
来完结,它经过AIDL调用了长途StorageManagerService
的partitionPublic()
办法
@Override
public void partitionPublic(String diskId) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
try {
//StorageManagerService又调用了Vold服务来完结sdcard的格式化
mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
}
能够看到StorageManagerService
又调用了Vold
服务来完结sdcard的格式化。
vold即Volume看护进程,用来管理Android中存储类(包含U盘和SD卡)的热拔插事件,处于Kernel和Framework之间,是两个层级衔接的桥梁。vold在系统中以看护进程存在,是一个独自的进程,在开机阶段由Init进程拉起。在system/vold/vold.rc
中有具体配置。发动之后监听来自kernel的UEvent,挂载U盘并和Framework层的StorageManager通讯、设置挂载选项、用户权限等,以实现外部存储对上层app和用户的可见性。
上述PartitionTask
履行完后,会跳转到StorageWizardFormatSlow
页面,提示格式化已完结,一起关闭当时格式化页面
点击“Done”按钮,关闭当时页面,回来StorageDashboardFragment
页面,并履行onResume
生命周期办法,重启履行refreshUi
办法来获取sdcard巨细并更新UI,整个进程经过自定义log,打印日志如下
问题剖析
可见全体流程为:
- 用户进入Settings -> Storage
- 切换到sdcard,经过
File.getTotalSpace()
获取sdcard容量巨细并显现 - 点击格式化,经过
vold
完结sdcard的格式化操作 - 回来Storage页面,从头获取sdcard的巨细。
整个进程涉及到的核心API为File.getTotalSpace()
和vold
进程相关的API,均为Google原生API。
一起抓取了整个进程的logcat日志,发现在sdcard格式化后打印了如下日志
此日志在格式化之前并未输出,说明格式化之后导致sdcard找不到了,因而巨细才会显现0。
退出Settings -> Storage页面从头进入,切换到sdcard后显现正常。
或许跟vold有关,已让bsp同事帮忙排查。