引言

在现在的Android手机中,EMMC已经从32G,64G开始了向128G, 512G的快速转变。
随着5G时代的到来,以及近些年Camera的兴起,越来越多数据将会在本地进行运算和存储。
那么,对于存储的管理就会越来越受人重视。
下图是一个AOSP Pixel的Storage截图,当然,这个界面各个厂商也是修改的最凶的。

我们这里主要分析的是原生的Storage manager的清理逻辑,以及下方各类型数据存储记录的规则。

Storage manager

Storage manager实现逻辑分析

在点击Storage manager的界面后,我们可以看到如下的界面:

那么点击移除照片和视频,将会有30天,60天,90天这几个选项。
代码实现的路径为src/com/android/settings/deletionhelper/AutomaticStorageManagerSettings.java
在进来之后,初始化的方法为:

    @Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view = super.onCreateView(inflater, container, savedInstanceState);initializeDaysToRetainPreference();initializeSwitchBar();return view;}

可以看到时进行了初始化Perference和SwitchBar的初始化设置。

    private void initializeDaysToRetainPreference() {mDaysToRetain = (DropDownPreference) findPreference(KEY_DAYS);mDaysToRetain.setOnPreferenceChangeListener(this);ContentResolver cr = getContentResolver();int photosDaysToRetain =Settings.Secure.getInt(cr,Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,Utils.getDefaultStorageManagerDaysToRetain(getResources()));String[] stringValues =getResources().getStringArray(R.array.automatic_storage_management_days_values);mDaysToRetain.setValue(stringValues[daysValueToIndex(photosDaysToRetain, stringValues)]);}

在这边,将会从数据库中取出保存的AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN的值,并进行显示。

    private void initializeSwitchBar() {final SettingsActivity activity = (SettingsActivity) getActivity();mSwitchBar = activity.getSwitchBar();mSwitchBar.setSwitchBarText(R.string.automatic_storage_manager_master_switch_title,R.string.automatic_storage_manager_master_switch_title);mSwitchBar.show();mSwitchController =new AutomaticStorageManagerSwitchBarController(getContext(),mSwitchBar,mMetricsFeatureProvider,mDaysToRetain,getFragmentManager());}

取到的值,将会通过AutomaticStorageManagerSwitchBarController对象来进行初始化的保存。
而当每次用户操作数据有改变的时候,我们将会通过监听来获得:

    @Overridepublic boolean onPreferenceChange(Preference preference, Object newValue) {if (KEY_DAYS.equals(preference.getKey())) {Settings.Secure.putInt(getContentResolver(),Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,Integer.parseInt((String) newValue));}return true;}

这边其实就是会将对应的值写到数据库中。那么AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN这个变量,就会非常的重要。
定义如下:

        /*** How many days of information for the automatic storage manager to retain on the device.** @hide*/public static final String AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN ="automatic_storage_manager_days_to_retain";

那么定义出来的天数是怎么检测的呢?这就从一个广播说起:

        <!-- Automatic storage management tasks. --><serviceandroid:name=".automatic.AutomaticStorageManagementJobService"android:label="@string/automatic_storage_manager_service_label"android:permission="android.permission.BIND_JOB_SERVICE"android:enabled="@bool/enable_automatic_storage_management"android:exported="false"/><receiver android:name=".automatic.AutomaticStorageBroadcastReceiver"android:enabled="@bool/enable_automatic_storage_management"><intent-filter><action android:name="android.intent.action.BOOT_COMPLETED" /></intent-filter></receiver>

因为我们前面设置的是天数,所以这边我们将会起一个receiver来接收boot_complete的广播。

    @Overridepublic void onReceive(Context context, Intent intent) {// Automatic deletion serviceJobScheduler jobScheduler =(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);ComponentName component = new ComponentName(context,AutomaticStorageManagementJobService.class);long periodicOverride = SystemProperties.getLong(DEBUG_PERIOD_FLAG, PERIOD);JobInfo job = new JobInfo.Builder(AUTOMATIC_STORAGE_JOB_ID, component).setRequiresCharging(true).setRequiresDeviceIdle(true).setPeriodic(periodicOverride).build();jobScheduler.schedule(job);}

这边可以看到,其实就是根据boot complete的时间,设置了jobscheduler的service去定时执行AutomaticStorageManagementJobService。
实现的文件为AutomaticStorageManagementJobService.java
在这边我们关注的是onStartJob的方法:

    @Overridepublic boolean onStartJob(JobParameters args) {// We need to double-check the preconditions here because they are not enforced for a// periodic job.if (!preconditionsFulfilled()) {// By telling the system to re-schedule the job, it will attempt to execute again at a// later idle window -- possibly one where we are charging.jobFinished(args, true);return false;}mProvider = FeatureFactory.getFactory(this).getStorageManagementJobProvider();if (maybeDisableDueToPolicy(mProvider, this, getClock())) {jobFinished(args, false);return false;}if (!volumeNeedsManagement()) {Log.i(TAG, "Skipping automatic storage management.");Settings.Secure.putLong(getContentResolver(),Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,System.currentTimeMillis());jobFinished(args, false);return false;}if (!Utils.isStorageManagerEnabled(getApplicationContext())) {Intent maybeShowNotificationIntent =new Intent(NotificationController.INTENT_ACTION_SHOW_NOTIFICATION);maybeShowNotificationIntent.setClass(getApplicationContext(),NotificationController.class);getApplicationContext().sendBroadcast(maybeShowNotificationIntent);jobFinished(args, false);return false;}if (mProvider != null) {return mProvider.onStartJob(this, args, getDaysToRetain());}jobFinished(args, false);return false;}

当系统在低存储的模式下,并且打开了automatic storage management的功能,那么才会最后去执行mProvider的onStartJob的工作。
mProvider在AOSP中的实现就基本上终止了,pixel会使用文件管理器替换掉这个功能。
而华为,小米,Oppo,Vivo等厂商也是使用不同的定制化的apk去overlay storageManager。
这里需要注意,这个其实可以被overlay,override掉,所以也方便了各大厂商在这边的定制。

各种类型计算方案实现

计算这边我们分为两部分,第一部分是我们点击Settings的Perference后,进行的Activity跳转。

    @Overridepublic boolean handlePreferenceTreeClick(Preference preference) {if (preference == null) {return false;}Intent intent = null;if (preference.getKey() == null) {return false;}switch (preference.getKey()) {case PHOTO_KEY:intent = getPhotosIntent();break;case AUDIO_KEY:intent = getAudioIntent();break;case GAME_KEY:intent = getGamesIntent();break;case MOVIES_KEY:intent = getMoviesIntent();break;case OTHER_APPS_KEY:// Because we are likely constructed with a null volume, this is theoretically// possible.if (mVolume == null) {break;}intent = getAppsIntent();break;case FILES_KEY:intent = getFilesIntent();FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().action(mContext, SettingsEnums.STORAGE_FILES);break;case SYSTEM_KEY:final SystemInfoFragment dialog = new SystemInfoFragment();dialog.setTargetFragment(mFragment, 0);dialog.show(mFragment.getFragmentManager(), SYSTEM_FRAGMENT_TAG);return true;}if (intent != null) {intent.putExtra(Intent.EXTRA_USER_ID, mUserId);Utils.launchIntent(mFragment, intent);return true;}return super.handlePreferenceTreeClick(preference);}

其实在这边,就可以很容易的定义不同的perference以及将会出发的intent。
以点击Photo为例:

    private Intent getPhotosIntent() {Bundle args = getWorkAnnotatedBundle(2);args.putString(ManageApplications.EXTRA_CLASSNAME, Settings.PhotosStorageActivity.class.getName());args.putInt(ManageApplications.EXTRA_STORAGE_TYPE,ManageApplications.STORAGE_TYPE_PHOTOS_VIDEOS);return new SubSettingLauncher(mContext).setDestination(ManageApplications.class.getName()).setTitleRes(R.string.storage_photos_videos).setArguments(args).setSourceMetricsCategory(mMetricsFeatureProvider.getMetricsCategory(mFragment)).toIntent();}

这里就会丁志伟相应跳转的activity了。


计算的方式如下,仍然以Photo的类型来进行说明:
这里其实是会有一个多用户的概念:

    public void onLoadFinished(SparseArray<StorageAsyncLoader.AppsStorageResult> result,int userId) {final StorageAsyncLoader.AppsStorageResult data = result.get(userId);final StorageAsyncLoader.AppsStorageResult profileData = result.get(Utils.getManagedProfileId(mContext.getSystemService(UserManager.class), userId));mPhotoPreference.setStorageSize(getPhotosSize(data, profileData), mTotalSize);mAudioPreference.setStorageSize(getAudioSize(data, profileData), mTotalSize);mGamePreference.setStorageSize(getGamesSize(data, profileData), mTotalSize);mMoviesPreference.setStorageSize(getMoviesSize(data, profileData), mTotalSize);mAppPreference.setStorageSize(getAppsSize(data, profileData), mTotalSize);mFilePreference.setStorageSize(getFilesSize(data, profileData), mTotalSize);if (mSystemPreference != null) {// Everything else that hasn't already been attributed is tracked as// belonging to system.long attributedSize = 0;for (int i = 0; i < result.size(); i++) {final StorageAsyncLoader.AppsStorageResult otherData = result.valueAt(i);attributedSize +=otherData.gamesSize+ otherData.musicAppsSize+ otherData.videoAppsSize+ otherData.photosAppsSize+ otherData.otherAppsSize;attributedSize += otherData.externalStats.totalBytes- otherData.externalStats.appBytes;}final long systemSize = Math.max(TrafficStats.GB_IN_BYTES, mUsedBytes - attributedSize);mSystemPreference.setStorageSize(systemSize, mTotalSize);}}

这里其实就是拿两个data:

        final StorageAsyncLoader.AppsStorageResult data = result.get(userId);final StorageAsyncLoader.AppsStorageResult profileData = result.get(Utils.getManagedProfileId(mContext.getSystemService(UserManager.class), userId));

然后进行后续photo,games等内容的传递。

    private long getPhotosSize(StorageAsyncLoader.AppsStorageResult data,StorageAsyncLoader.AppsStorageResult profileData) {if (profileData != null) {return data.photosAppsSize + data.externalStats.imageBytes+ data.externalStats.videoBytes+ profileData.photosAppsSize + profileData.externalStats.imageBytes+ profileData.externalStats.videoBytes;} else {return data.photosAppsSize + data.externalStats.imageBytes+ data.externalStats.videoBytes;}}

在photo中,其实只是对data的photo的size进行了相加,

    public static class AppsStorageResult {public long gamesSize;public long musicAppsSize;public long photosAppsSize;public long videoAppsSize;public long otherAppsSize;public long cacheSize;public StorageStatsSource.ExternalStorageStats externalStats;}

因为在AppsStorageResult类中,已经对其进行了计算。
这里就要提一下StorageAsyncLoader的实现了,在函数初始化后,将会对app进行loadapp的操作。

    @Overridepublic SparseArray<AppsStorageResult> loadInBackground() {return loadApps();}private SparseArray<AppsStorageResult> loadApps() {mSeenPackages = new ArraySet<>();SparseArray<AppsStorageResult> result = new SparseArray<>();List<UserInfo> infos = mUserManager.getUsers();// Sort the users by user id ascending.Collections.sort(infos,new Comparator<UserInfo>() {@Overridepublic int compare(UserInfo userInfo, UserInfo otherUser) {return Integer.compare(userInfo.id, otherUser.id);}});for (int i = 0, userCount = infos.size(); i < userCount; i++) {UserInfo info = infos.get(i);result.put(info.id, getStorageResultForUser(info.id));}return result;}

这边会去调用getStorageResultForUser进行统计,然后put到result中。

    private AppsStorageResult getStorageResultForUser(int userId) {Log.d(TAG, "Loading apps");List<ApplicationInfo> applicationInfos =mPackageManager.getInstalledApplicationsAsUser(0, userId);AppsStorageResult result = new AppsStorageResult();UserHandle myUser = UserHandle.of(userId);for (int i = 0, size = applicationInfos.size(); i < size; i++) {ApplicationInfo app = applicationInfos.get(i);StorageStatsSource.AppStorageStats stats;try {stats = mStatsManager.getStatsForPackage(mUuid, app.packageName, myUser);} catch (NameNotFoundException | IOException e) {// This may happen if the package was removed during our calculation.Log.w(TAG, "App unexpectedly not found", e);continue;}final long dataSize = stats.getDataBytes();final long cacheQuota = mStatsManager.getCacheQuotaBytes(mUuid, app.uid);final long cacheBytes = stats.getCacheBytes();long blamedSize = dataSize;// Technically, we could overages as freeable on the storage settings screen.// If the app is using more cache than its quota, we would accidentally subtract the// overage from the system size (because it shows up as unused) during our attribution.// Thus, we cap the attribution at the quota size.if (cacheQuota < cacheBytes) {blamedSize = blamedSize - cacheBytes + cacheQuota;}// This isn't quite right because it slams the first user by user id with the whole code// size, but this ensures that we count all apps seen once.if (!mSeenPackages.contains(app.packageName)) {blamedSize += stats.getCodeBytes();mSeenPackages.add(app.packageName);}switch (app.category) {case CATEGORY_GAME:result.gamesSize += blamedSize;break;case CATEGORY_AUDIO:result.musicAppsSize += blamedSize;break;case CATEGORY_VIDEO:result.videoAppsSize += blamedSize;break;case CATEGORY_IMAGE:result.photosAppsSize += blamedSize;break;default:// The deprecated game flag does not set the category.if ((app.flags & ApplicationInfo.FLAG_IS_GAME) != 0) {result.gamesSize += blamedSize;break;}result.otherAppsSize += blamedSize;break;}}Log.d(TAG, "Loading external stats");try {result.externalStats = mStatsManager.getExternalStorageStats(mUuid,UserHandle.of(userId));} catch (IOException e) {Log.w(TAG, e);}Log.d(TAG, "Obtaining result completed");return result;}

针对不同APP的category来进行类别的划分,并且进行size的计算。

Android StorageManager实现原理剖析相关推荐

  1. 《深入理解Android:Telephony原理剖析与最佳实践》一1.3 Android Telephony框架结构...

    1.3 Android Telephony框架结构 前面对Android手机操作系统整体框架结构及每一层进行了简单的分析和说明,相信大家对Android智能手机操作系统有了一些基本的了解和认识.结合前 ...

  2. 《深入理解Android:Telephony原理剖析与最佳实践》一1.1 智能手机的系统结构

    1.1 智能手机的系统结构 Android手机的基本硬件结构是符合智能手机的基本硬件结构,我们要学习Android移动开发,首先需要了解智能手机的硬件系统基本结构. 随着通信领域的快速发展,移动终端发 ...

  3. Android LOG系统原理剖析

    引言 在我们android的开发过程中,最不可少的就是加Log,打印Log的操作. 这样可以帮助我们去查看各个变量,理清楚代码的逻辑. 而Android系统,提供了不同维度,不同层面,不同模块的Log ...

  4. Android View学习笔记(三):Scroller的原理剖析及使用(上)

    一.前言 上一篇文章中,讨论了View的几种基本滑动方式,但是这些滑动方式是生硬的,在一瞬间完成的,这给用户非常不好的体验,所以为了提高用户体验,我们需要将View弹性滑动.什么是弹性滑动?就是一个V ...

  5. [Android] Toast问题深度剖析(二)

    欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 作者: QQ音乐技术团队 题记 Toast 作为 Android 系统中最常用的类之一,由于其方便的api设计和简洁的交互体验,被我们所广泛采用 ...

  6. 基本功 | Litho的使用及原理剖析

    1. 什么是Litho? Litho是Facebook推出的一套高效构建Android UI的声明式框架,主要目的是提升RecyclerView复杂列表的滑动性能和降低内存占用.下面是Litho官网的 ...

  7. 断点续传的原理剖析与实例讲解

    断点续传的原理剖析与实例讲解 本文所要讲的是Android断点续传的内容,以实例的形式进行了详细介绍. 一.断点续传的原理 其实断点续传的原理很简单,就是在http的请求上和一般的下载有所不同而已. ...

  8. 基本功 | Litho的使用及原理剖析(转)

    美团技术团队分享: https://tech.meituan.com/2019/03/14/litho-use-and-principle-analysis.html 1. 什么是Litho? Lit ...

  9. 【基本功】Litho的使用及原理剖析

    总第344篇 2019年 第22篇 美美导读:[基本功]专栏又上新了,本期介绍一套高效构建Android UI的声明式框架--Litho.作者将带领大家深入剖析它的原理和用法. 1. 什么是Litho ...

  10. OkHttp 原理剖析

    OkHttp 原理剖析 文章目录 OkHttp 原理剖析 一.基本介绍 二.基本使用 2.1 依赖配置 2.2 基本请求 三.原理剖析 3.1 创建请求 3.2 执行同步请求 3.2.1 执行同步请求 ...

最新文章

  1. 7_7_2013 E.Function
  2. 手握6亿把钥匙 能否打开“智能家居”的大门?
  3. BZOJ4129: Haruna’s Breakfast
  4. UA SIE545 优化理论基础 函数凸性的一些有趣的判断方法
  5. Oracle-Soft Parse/Hard Parse/Soft Soft Parse解读
  6. Ability跳转指定Slice的方法
  7. 加大weblogic在Linux内存,在linux运行weblogic出现运行内存不足错误,求鞭挞....
  8. 编码方式_【每日一题】| 常见的编码方式之栅栏密码
  9. 智慧气象机器_智慧电缆隧道火热建设中 传感器+机器人成标配
  10. 什么?你做的差异基因方法不合适?
  11. csapp 深入理解计算机系统 csapp.h csapp.c文件配置
  12. SSH应用之BBS之路-2、Hibernate配置
  13. opencv提供的带参数例程
  14. yb3防爆电机型号含义_【产品信息】防爆充电机
  15. newInstance() 和 new 有什么区别
  16. 基于LQR的二自由度云台控制与仿真
  17. win10 1903错误应用程序无法正常启动0xc0000135解决
  18. 数据处理的神来之笔 解决缓存击穿的终极利器 1
  19. SVG代码例子及含义
  20. 程序员“薪资被应届生倒挂“现象明显,跳槽还是等待?

热门文章

  1. 台达触摸屏DOP-B系列——通过宏和子画面弹出提示框
  2. 随机微分方程与 Ito Lemma 的关系
  3. 基于SSM框架搭建的疫情打卡系统 报告+项目源码及数据库文件
  4. MoSonic:对SubSonic的分布式存储、缓存改进方案尝试(1)
  5. gerber文件如何转为PCB文件
  6. MEMS惯性传感器初始姿态角的确定
  7. 大疆文档(2)-指南
  8. Cheat Enginee(CE)自带教程使用指南
  9. Java游戏项目分享
  10. 人大经济论坛SAS入门到高级教程