android framework13-settings[04 Storage页面]

193 阅读8分钟

1.简介

边学边记录,

2.StorageDashboardFragment.java

在第一篇里没写完,只简单学了 VolumeOptionMenuController和 StorageSelectionPreferenceController,这里继续研究页面里的其他数据

2.1.StorageUsageProgressBarPreferenceController

image.png

        mStorageUsageProgressBarController.setSelectedStorageEntry(mSelectedStorageEntry);

>setSelectedStorageEntry

选择的存储卡发生变化以后,更新数据

    public void setSelectedStorageEntry(StorageEntry storageEntry) {
        mStorageEntry = storageEntry;
        getStorageStatsAndUpdateUi();
    }

>getStorageStatsAndUpdateUi

    private void getStorageStatsAndUpdateUi() {
           //挂载状态,并且private的(这个好像只有内置卡了?)
        if (mStorageEntry != null && mStorageEntry.isMounted() && mStorageEntry.isPrivate()) {
         //使用缓存数据刷新总大小以及使用大小
            StorageCacheHelper.StorageCache cachedData = mStorageCacheHelper.retrieveCachedSize();
            mTotalBytes = cachedData.totalSize;
            mUsedBytes = cachedData.totalUsedSize;
            mIsUpdateStateFromSelectedStorageEntry = true;
            updateState(mUsageProgressBarPreference);
        }
        // Get the latest data from StorageStatsManager.
        ThreadUtils.postOnBackgroundThread(() -> {
            try {
                if (mStorageEntry == null || !mStorageEntry.isMounted()) {
                //非挂载状态抛出异常,catch里把大小弄为0
                    throw new IOException();
                }
                //内置卡和外置卡不同的获取大小的方法
                if (mStorageEntry.isPrivate()) {
                    // StorageStatsManager can only query private storages.
                    mTotalBytes = mStorageStatsManager.getTotalBytes(mStorageEntry.getFsUuid());
                    mUsedBytes = mTotalBytes
                            - mStorageStatsManager.getFreeBytes(mStorageEntry.getFsUuid());
                } else {
                    final File rootFile = mStorageEntry.getPath();
                    if (rootFile == null) {
                        throw new IOException();
                    }
                    mTotalBytes = rootFile.getTotalSpace();
                    mUsedBytes = mTotalBytes - rootFile.getFreeSpace();
                }
            } catch (IOException e) {
                // The storage device isn't present.
                mTotalBytes = 0;
                mUsedBytes = 0;
            }

            if (mUsageProgressBarPreference == null) {
                return;
            }
            mIsUpdateStateFromSelectedStorageEntry = true;
            ThreadUtils.postOnMainThread(() -> updateState(mUsageProgressBarPreference));
        });
    }

>updateState

    public void updateState(Preference preference) {
        if (!mIsUpdateStateFromSelectedStorageEntry) {
            return;
        }
        mIsUpdateStateFromSelectedStorageEntry = false;
        mUsageProgressBarPreference.setUsageSummary(StorageUtils.getStorageSummary(
                mContext, R.string.storage_usage_summary, mUsedBytes));
        mUsageProgressBarPreference.setTotalSummary(StorageUtils.getStorageSummary(
                mContext, R.string.storage_total_summary, mTotalBytes));
        mUsageProgressBarPreference.setPercent(mUsedBytes, mTotalBytes);
    }

2.2.ManageStoragePreferenceController

image.png

        ManageStoragePreferenceController manageStoragePreferenceController =
                use(ManageStoragePreferenceController.class);
        manageStoragePreferenceController.setUserId(mUserId);

>setUserId

设置userId,并查询下有没有可以对应action的存储管理器

    public void setUserId(int userId) {
        mUserId = userId;
        mManageStorageDrawable = StorageUtils.getManageStorageIcon(mContext, userId);
    }
  • getManageStorageIcon 根据action查一下,有没有对应的activity,有的话返回对应的图标
    public static Drawable getManageStorageIcon(Context context, int userId) {
        ResolveInfo resolveInfo = context.getPackageManager().resolveActivityAsUser(
                new Intent(StorageManager.ACTION_MANAGE_STORAGE), 0 /* flags */, userId);
        if (resolveInfo == null || resolveInfo.activityInfo == null) {
            return null;
        }

        return Utils.getBadgedIcon(context, resolveInfo.activityInfo.applicationInfo);
    }

>getAvailabilityStatus

有图标也就是说有对应的activity处理这个,则可用,否则不可用

    public int getAvailabilityStatus() {
        return mManageStorageDrawable == null ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
    }

>handlePreferenceTreeClick

    public boolean handlePreferenceTreeClick(Preference preference) {
        //..
        //根据action跳转到对应的页面
        final Intent intent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivityAsUser(intent, new UserHandle(mUserId));
        return true;
    }

2.3.StorageItemPreferenceController

如下图,这个是控制下面一堆选项的

image.png

    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
        final List<AbstractPreferenceController> controllers = new ArrayList<>();
        final StorageManager sm = context.getSystemService(StorageManager.class);
        //那一堆存储列表就是这个控制器
        mPreferenceController = new StorageItemPreferenceController(context, this,
                null /* volume */, new StorageManagerVolumeProvider(sm), mIsWorkProfile);
        controllers.add(mPreferenceController);
        //这个是多用户的时候用的,到时候最底部还会多显示其他用户的空间使用情况
        mSecondaryUsers = SecondaryUserController.getSecondaryUserControllers(context,
                mUserManager, mIsWorkProfile /* isWorkProfileOnly */);
        controllers.addAll(mSecondaryUsers);

        return controllers;
    }

下图就是mSecondaryUsers显示的效果,这个就不研究了 image.png

>displayPreference

首先获取我们要控制的所有preference

image.png

    public void displayPreference(PreferenceScreen screen) {
        mScreen = screen;
        //这个是上图的控件,外置sdcard用这个
        mPublicStoragePreference = screen.findPreference(PUBLIC_STORAGE_KEY);
        //剩下的8个是内置卡用的
        mImagesPreference = screen.findPreference(IMAGES_KEY);
        mVideosPreference = screen.findPreference(VIDEOS_KEY);
        mAudioPreference = screen.findPreference(AUDIO_KEY);
        mAppsPreference = screen.findPreference(APPS_KEY);
        mGamesPreference = screen.findPreference(GAMES_KEY);
        mDocumentsAndOtherPreference = screen.findPreference(DOCUMENTS_AND_OTHER_KEY);
        mSystemPreference = screen.findPreference(SYSTEM_KEY);
        mTrashPreference = screen.findPreference(TRASH_KEY);
    }

>handlePreferenceTreeClick

-点击事件,system是显示个对话框,其他的都是跳转到对应的intent

    public boolean handlePreferenceTreeClick(Preference preference) {
        if (preference.getKey() == null) {
            return false;
        }
        switch (preference.getKey()) {
            case PUBLIC_STORAGE_KEY:
                launchPublicStorageIntent();
                return true;
            case IMAGES_KEY:
                launchActivityWithUri(mImagesUri);
                return true;
            case VIDEOS_KEY:
                launchActivityWithUri(mVideosUri);
                return true;
            case AUDIO_KEY:
                launchActivityWithUri(mAudioUri);
                return true;
            case APPS_KEY:
                launchAppsIntent();
                return true;
            case GAMES_KEY:
                launchGamesIntent();
                return true;
            case DOCUMENTS_AND_OTHER_KEY:
                launchActivityWithUri(mDocumentsAndOtherUri);
                return true;
            case SYSTEM_KEY:
                final SystemInfoFragment dialog = new SystemInfoFragment();
                dialog.setTargetFragment(mFragment, 0);
                dialog.show(mFragment.getFragmentManager(), SYSTEM_FRAGMENT_TAG);
                return true;
            case TRASH_KEY:
                launchTrashIntent();
                return true;
            default:
                // Do nothing.
        }
        return super.handlePreferenceTreeClick(preference);
    }

>setVolume

切换查看的存储卡的时候会调用这个

    public void setVolume(VolumeInfo volume) {
        mVolume = volume;

        if (mPublicStoragePreference != null) {
        //外置卡那个file选项可见性
            mPublicStoragePreference.setVisible(isValidPublicVolume());
        }

        if (isValidPrivateVolume()) {
        //这里是内置卡,preferences的可见性在onLoadFinished方法里设置为true
            mIsDocumentsPrefShown = isDocumentsPrefShown();
        } else {
        //非内置卡,隐藏这堆preferences
            setPrivateStorageCategoryPreferencesVisibility(false);
        }
    }

>onLoadFinished

    public void onLoadFinished(@Nullable SparseArray<StorageAsyncLoader.StorageResult> result,
            int userId) {
        //更新这些preferences的数据
        StorageCacheHelper.StorageCache storageCache = getSizeInfo(result, userId);
        // Set size info to each preference
        mImagesPreference.setStorageSize(storageCache.imagesSize, mTotalSize, animate);
        mVideosPreference.setStorageSize(storageCache.videosSize, mTotalSize, animate);
        mAudioPreference.setStorageSize(storageCache.audioSize, mTotalSize, animate);
        mAppsPreference.setStorageSize(storageCache.allAppsExceptGamesSize, mTotalSize, animate);
        mGamesPreference.setStorageSize(storageCache.gamesSize, mTotalSize, animate);
        mDocumentsAndOtherPreference.setStorageSize(storageCache.documentsAndOtherSize, mTotalSize,
                animate);
        mTrashPreference.setStorageSize(storageCache.trashSize, mTotalSize, animate);
        if (mSystemPreference != null) {
            mSystemPreference.setStorageSize(storageCache.systemSize, mTotalSize, animate);
        }
        // Cache the size info
        if (result != null) {
            mStorageCacheHelper.cacheSizeInfo(storageCache);
        }

        // Sort the preference according to size info in descending order
        if (!mIsPreferenceOrderedBySize) {
        //按照使用大小排序,移除所有preferences再重新添加
            updatePrivateStorageCategoryPreferencesOrder();
            mIsPreferenceOrderedBySize = true;
        }
        //修改可见性
        setPrivateStorageCategoryPreferencesVisibility(true);
    }

>onEmptyTrashComplete

    private void launchTrashIntent() {
        final Intent intent = new Intent("android.settings.VIEW_TRASH");

        if (mPackageManager.resolveActivityAsUser(intent, 0 /* flags */, mUserId) == null) {
            final long trashSize = mTrashPreference.getStorageSize();
            if (trashSize > 0) {
            //没有找到处理trash的intent,自己处理,弹个对话框,回调就是下边的方法
                new EmptyTrashFragment(mFragment, mUserId, trashSize,
                        this /* onEmptyTrashCompleteListener */).show();
            } else {
                Toast.makeText(mContext, R.string.storage_trash_dialog_empty_message,
                        Toast.LENGTH_SHORT).show();
            }
        } else {
            //有处理trash的页面,跳转过去
            mContext.startActivityAsUser(intent, new UserHandle(mUserId));
        }
    }

这个是清空完trash以后,可以看到会重新排序

    public void onEmptyTrashComplete() {
        if (mTrashPreference == null) {
            return;
        }
        mTrashPreference.setStorageSize(0, mTotalSize, true /* animate */);
        updatePrivateStorageCategoryPreferencesOrder();
    }

controller都学完了,下边看下Fragment里的一些操作

2.4.内置卡的数据使用

refreshUi方法里

        if (mSelectedStorageEntry.isPrivate()) {
            mStorageInfo = null;
            mAppsResult = null;
            if (mStorageCacheHelper.hasCachedSizeInfo()) {
                //有缓存数据先使用缓存数据
                mPreferenceController.onLoadFinished(mAppsResult, mUserId);
            } else {
                //支持loading的话就显示个loading
                maybeSetLoading(isQuotaSupported());
                // 防止出现闪烁的情况,数据先设置为null,后边拿到数据再重新设置
                mPreferenceController.setVolume(null);
            }
            // 获取内置卡的数据使用情况
            getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
            //这个是获取volume的总大小以及使用大小
            getLoaderManager()
                 .restartLoader(VOLUME_SIZE_JOB_ID, Bundle.EMPTY, new VolumeSizeCallbacks());
                 //这个是获取用户头像的,给那个mSecondaryUsers用的
            getLoaderManager().restartLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks());
        }

2.5.LoaderManager

异步获取数据用的是LoaderManager,简单看下逻辑

getLoaderManager().restartLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks());

首先getLoaderManger()是Fragment里自带的方法,其实就是下边这个,现在也推荐这样使用

LoaderManager.getInstance(this)

>LoaderManagerImpl

public static <T extends LifecycleOwner & ViewModelStoreOwner> LoaderManager getInstance(
        @NonNull T owner) {
    return new LoaderManagerImpl(owner, owner.getViewModelStore());
}

//

LoaderManagerImpl(@NonNull LifecycleOwner lifecycleOwner,
        @NonNull ViewModelStore viewModelStore) {
    mLifecycleOwner = lifecycleOwner;
    mLoaderViewModel = LoaderViewModel.getInstance(viewModelStore);
}

>restartLoader

重新启动一个loader,有对应id的旧的loader,先销毁,再新建一个

public <D> Loader<D> restartLoader(int id, @Nullable Bundle args,
        @NonNull LoaderCallbacks<D> callback) {
    if (mLoaderViewModel.isCreatingLoader()) {
        throw new IllegalStateException("正在创建loader");
    }
    if (Looper.getMainLooper() != Looper.myLooper()) {
        throw new IllegalStateException("必须在主线程调用");
    }

   
    LoaderInfo<D> info = mLoaderViewModel.getLoader(id);
    Loader<D> priorLoader = null;
    if (info != null) {
    //有旧的先销毁
        priorLoader = info.destroy(false);
    }
    // And create a new Loader
    return createAndInstallLoader(id, args, callback, priorLoader);
}

>createAndInstallLoader

private <D> Loader<D> createAndInstallLoader(int id, @Nullable Bundle args,
        @NonNull LoaderCallbacks<D> callback, @Nullable Loader<D> priorLoader) {
    LoaderInfo<D> info;
    try {
        mLoaderViewModel.startCreatingLoader();
        //调用callback里的方法获取loader实例对象
        Loader<D> loader = callback.onCreateLoader(id, args);
        if (loader == null) {
            throw new IllegalArgumentException("Object returned from onCreateLoader "
                    + "must not be null");
        }
        if (loader.getClass().isMemberClass()
                && !Modifier.isStatic(loader.getClass().getModifiers())) {
            throw new IllegalArgumentException("回调里给的loader不能是一个静态内部类");
        }
        //实例化一个LoaderInfo对象
        info = new LoaderInfo<>(id, args, loader, priorLoader);
       //放入map里
        mLoaderViewModel.putLoader(id, info);
    } finally {
        mLoaderViewModel.finishCreatingLoader();
    }
    return info.setCallback(mLifecycleOwner, callback);
}

2.6.LoaderInfo

可以看到是个LiveData,实现了Loader的监听

public static class LoaderInfo<D> extends MutableLiveData<D>
        implements Loader.OnLoadCompleteListener<D> {

LoaderInfo(int id, @Nullable Bundle args, @NonNull Loader<D> loader,
        @Nullable Loader<D> priorLoader) {
    mId = id;
    mArgs = args;
    mLoader = loader;
    mPriorLoader = priorLoader;
    //给loader设置监听
    mLoader.registerListener(id, this);
}

>onLoadComplete

可以看到回调里会直接更新这个liveData的数据

public void onLoadComplete(@NonNull Loader<D> loader, @Nullable D data) {
   
    if (Looper.myLooper() == Looper.getMainLooper()) {
        setValue(data);
    } else {
        postValue(data);
    }
}

>onActive

protected void onActive() {
    mLoader.startLoading();
}

>setCallback

Loader<D> setCallback(@NonNull LifecycleOwner owner,
        @NonNull LoaderCallbacks<D> callback) {
    LoaderObserver<D> observer = new LoaderObserver<>(mLoader, callback);
    //observe是liveData里的方法
    //也就是LoaderInfo数据变化的时候调用observer的onChanged方法
    observe(owner, observer);
    if (mObserver != null) {
        removeObserver(mObserver);
    }
    mLifecycleOwner = owner;
    mObserver = observer;
    return mLoader;
}

2.7.LoaderObserver

当LoaderObserver数据发生变化的时候,会调用callback里的onLoadFinished

LoaderObserver(@NonNull Loader<D> loader, @NonNull LoaderCallbacks<D> callback) {
    mLoader = loader;
    mCallback = callback;
}

@Override
public void onChanged(@Nullable D data) {
    mCallback.onLoadFinished(mLoader, data);
    mDeliveredData = true;
}

2.8 总结下

  • LoaderInfo是一个livedata
  • Loader在LoaderInfo里注册了一个监听器,回调方法是onLoadComplete,方法里会修改LoaderInfo的值
  • setCallback方法里给LoaderInfo注册了观察者LoaderObserver,LoaderInfo数据变化的时候会调用mCallback的onLoadFinished方法
  • 注意:Loader必须手动调用forceLoad方法才会开始加载数据,一般放在startLoading方法里

2.9.AsyncTaskLoader

public abstract class AsyncTaskLoader<D> extends Loader<D> {

顾名思义,就是在后台执行任务的

>onForceLoad

可以看到这里实例化了一个LoaderTask

protected void onForceLoad() {
    super.onForceLoad();
    cancelLoad();
    mTask = new LoadTask();
   
    executePendingTask();
}

>executePendingTask

根据条件执行task

void executePendingTask() {
    if (mCancellingTask == null && mTask != null) {
        if (mTask.waiting) {
            mTask.waiting = false;
            mHandler.removeCallbacks(mTask);
        }
        if (mUpdateThrottle > 0) {
            long now = SystemClock.uptimeMillis();
            if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
                // Not yet time to do another load.
                mTask.waiting = true;
                mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
                return;
            }
        }
        //在executor里执行任务
        mTask.executeOnExecutor(mExecutor, (Void[]) null);
    }
}

>executeOnExecutor

ModernAsyncTask.java

public final ModernAsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params) {
//..

    mStatus = Status.RUNNING;

    onPreExecute();

    mWorker.mParams = params;
    //mFuture就是runnable,会执行其run方法,run方法里会执行下边的mWorker里的call方法
    exec.execute(mFuture);

    return this;
}

>ModernAsyncTask

ModernAsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        @Override
        public Result call() throws Exception {
            mTaskInvoked.set(true);
            Result result = null;
            try {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //最终会调用这个方法
                result = doInBackground(mParams);
                Binder.flushPendingCommands();
            }finally {
                postResult(result);
            }
            return result;
        }
    };

    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
            try {
                final Result result = get();

                postResultIfNotInvoked(result);
            } 
        }
    };
}

>FutureTask

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}
public class FutureTask<V> implements RunnableFuture<V> {
//这里的callback就是上边的WorkerRunnable
public FutureTask(Callable<V> callable) {
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}
public void run() {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
            //调用call方法获取数据
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    }

>LoadTask

AsyncTaskLoader里用到的就是这个对象

final class LoadTask extends ModernAsyncTask<Void, Void, D> implements Runnable {
    private final CountDownLatch mDone = new CountDownLatch(1);
    @Override
    protected D doInBackground(Void... params) {
        try {
        //可以看到,最终调用的是Loader里的onLoadInBackground
            D data = AsyncTaskLoader.this.onLoadInBackground();
            return data;
        }
    }

2.10.实例

这里看下fragment里用到的几种loader

>AsyncLoaderCompat

public class VolumeSizesLoader extends AsyncLoaderCompat<PrivateStorageInfo> {

可以看到在onStartLoading里调用了forceLoad

public abstract class AsyncLoaderCompat<T> extends AsyncTaskLoader<T> {
    private T mResult;

    public AsyncLoaderCompat(final Context context) {
        super(context);
    }

    @Override
    protected void onStartLoading() {
        if (mResult != null) {
            deliverResult(mResult);
        }
        
        if (takeContentChanged() || mResult == null) {
            forceLoad();
        }
    }

>AsyncLoaderCompat

public class UserIconLoader extends AsyncLoaderCompat<SparseArray<Drawable>> {

在onStartLoading里调用了forceLoad

public abstract class AsyncLoaderCompat<T> extends AsyncTaskLoader<T> {
    private T mResult;

    public AsyncLoaderCompat(final Context context) {
        super(context);
    }

    @Override
    protected void onStartLoading() {
        if (mResult != null) {
            deliverResult(mResult);
        }

        if (takeContentChanged() || mResult == null) {
            forceLoad();
        }
    }

3.后记

3.1.存储卡大小获取

  • 需要api 31
  • 获取的字节数,除以1000就行,别除以1024,手机和电脑的计算方式不一样。
public static void testStorage(Context context) {
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            File data = Environment.getExternalStorageDirectory();
            StorageManager storageManager = context.getSystemService(StorageManager.class);
            UUID currentVolumeUUID = storageManager.getStorageVolume(data).getStorageUuid();

            StorageStatsManager storageStatsManager = context.getSystemService(StorageStatsManager.class);
            long totalByte = storageStatsManager.getTotalBytes(currentVolumeUUID);
            long freeByte = storageStatsManager.getFreeBytes(currentVolumeUUID);
            System.out.println("log========Show: byte==" + totalByte + "===" + freeByte);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

>格式化返回的结果

public static String formatBytes(long sizeBytes) {
    final int unit = 1000;
    final boolean isNegative = (sizeBytes < 0);
    float result = isNegative ? -sizeBytes : sizeBytes;
    String suffix = "B";
    long mult = 1;
    if (result > 900) {
        suffix = "KB";
        mult = unit;
        result = result / unit;
    }
    if (result > 900) {
        suffix = "MB";
        mult *= unit;
        result = result / unit;
    }
    if (result > 900) {
        suffix = "GB";
        mult *= unit;
        result = result / unit;
    }
    if (result > 900) {
        suffix = "TB";
        mult *= unit;
        result = result / unit;
    }
    if (result > 900) {
        suffix = "PB";
        mult *= unit;
        result = result / unit;
    }

    final String roundFormat;
    if (mult == 1 || result >= 100) {
        roundFormat = "%.0f";
    } else if (result < 1) {
        roundFormat = "%.2f";
    } else if (result < 10) {
        roundFormat = "%.1f";
    } else { // 10 <= result < 100
        roundFormat = "%.0f";
    }

    if (isNegative) {
        result = -result;
    }
    final String roundedString = String.format(roundFormat, result);

    return roundedString + suffix;
}