Android StorageManager实现原理剖析
引言
在現在的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
在進來之后,初始化的方法為:
可以看到時進行了初始化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對象來進行初始化的保存。
而當每次用戶操作數據有改變的時候,我們將會通過監聽來獲得:
這邊其實就是會將對應的值寫到數據庫中。那么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的方法:
當系統在低存儲的模式下,并且打開了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為例:
這里就會丁志偉相應跳轉的activity了。
計算的方式如下,仍然以Photo的類型來進行說明:
這里其實是會有一個多用戶的概念:
這里其實就是拿兩個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的操作。
這邊會去調用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实现原理剖析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BIOS详解
- 下一篇: EMI/EMC设计经典问答