引言
在現在的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
在進來之后,初始化的方法為:
@Override
public 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物件來進行初始化的保存,
而當每次用戶操作資料有改變的時候,我們將會通過監聽來獲得:
@Override
public 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. -->
<service
android: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的廣播,
@Override
public void onReceive(Context context, Intent intent) {
// Automatic deletion service
JobScheduler 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的方法:
@Override
public 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跳轉,
@Override
public 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的操作,
@Override
public 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>() {
@Override
public 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的計算,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/208828.html
標籤:其他
上一篇:小白真心求助!!!電腦上微信開發者工具報錯.別人的顯示沒問題!!
下一篇:關于Android初學者需要知道的基礎知識以及Android Studio相關快捷鍵與提高效率的設定Setting(二)
