问题描绘

Android13 上进行SD卡格式化,格式化后显现的SD卡容量为0,退出从头进入,显现正常。

源码剖析

首先在Settings->Storage页面,切换到SD card,页面将显现SD card的存储信息

Android13 SD卡格式化问题剖析

代码坐落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办法获取总容量巨细,经过自定义日志,我们得到以下信息

Android13 SD卡格式化问题剖析

能够看到sdcard的类型为PUBLIC,diskId为disk:179,0,挂载状况为可写,路径为/storage/70D3-1521,而且终究打印的容量巨细为31448498176,单位Bytes,换算下来为32GB,已运用巨细为851968,大约为832KB。

此时,点击右上角的菜单,挑选“Format”进行格式化

Android13 SD卡格式化问题剖析

此时点击“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调用了长途StorageManagerServicepartitionPublic()办法

    @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页面,提示格式化已完结,一起关闭当时格式化页面

Android13 SD卡格式化问题剖析

点击“Done”按钮,关闭当时页面,回来StorageDashboardFragment页面,并履行onResume生命周期办法,重启履行refreshUi办法来获取sdcard巨细并更新UI,整个进程经过自定义log,打印日志如下

Android13 SD卡格式化问题剖析

问题剖析

可见全体流程为:

  • 用户进入Settings -> Storage
  • 切换到sdcard,经过File.getTotalSpace()获取sdcard容量巨细并显现
  • 点击格式化,经过vold完结sdcard的格式化操作
  • 回来Storage页面,从头获取sdcard的巨细。

整个进程涉及到的核心API为File.getTotalSpace()vold进程相关的API,均为Google原生API。

一起抓取了整个进程的logcat日志,发现在sdcard格式化后打印了如下日志

Android13 SD卡格式化问题剖析

此日志在格式化之前并未输出,说明格式化之后导致sdcard找不到了,因而巨细才会显现0。

退出Settings -> Storage页面从头进入,切换到sdcard后显现正常。

或许跟vold有关,已让bsp同事帮忙排查。