Android Service之设备存储空间监控
在负责文件系统模块的过程中,经常会碰到由于系统空间被消耗完而导致的问题,因此要确保为系统功能(如数据库同步)保留一定的空间。在功能机中一般是由文件系统模块预留,那么在Android系统是怎样对设备存储空间进行管理和监控的呢?
如果你在使用Android手机时有过把memory填满或者即将填满的经历,也许你会注意到在这种情况下手机的Notifications栏会有“Storage space running out”的通知。当点开该通知你会发现Setting-->Storage settings –>Device memory 下会有如下提示:Not enough storage space.
这个服务的实现是在frameworks\base\services\core\java\com\android\server\storage\DeviceStorageMonitorService.java。DeviceStorageMonitorService类实现了一个监控设备上存储空间的服务。如果设备的剩余存储空间小于某一个阀值(默认是存储空间的10%,这个值可以设置)时将会向用户发送剩余空间不足的警告,让用户释放一些空间。
下面就分析一下这个类。首先看一下该服务是如何被添加进来的。在frameworks\base\services\java\com\android\server\SystemServer.java中使用ServiceManager.addService()来添加系统服务: startOtherServices
t.traceBegin("StartDeviceMonitor"); mSystemServiceManager.startService(DeviceStorageMonitorService.class); t.traceEnd();
构造方法
通过handler 轮询check 设备存储使用情况
public DeviceStorageMonitorService(Context context) { super(context); mHandlerThread = new HandlerThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_CHECK: check(); return; } } }; }
@Override public void onStart() { final Context context = getContext(); mNotifManager = context.getSystemService(NotificationManager.class); mCacheFileDeletedObserver = new CacheFileDeletedObserver(); mCacheFileDeletedObserver.startWatching(); // Ensure that the notification channel is set up PackageManager packageManager = context.getPackageManager(); boolean isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK); if (isTv) { 盒子属于TV 创建NotificationChannel mNotifManager.createNotificationChannel(new NotificationChannel( TV_NOTIFICATION_CHANNEL_ID, context.getString( com.android.internal.R.string.device_storage_monitor_notification_channel), NotificationManager.IMPORTANCE_HIGH)); } 注册服务 publishBinderService(SERVICE, mRemoteService); publishLocalService(DeviceStorageMonitorInternal.class, mLocalService); // Kick off pass to examine storage state mHandler.removeMessages(MSG_CHECK); mHandler.obtainMessage(MSG_CHECK).sendToTarget(); }
/** * Core logic that checks the storage state of every mounted private volume. * Since this can do heavy I/O, callers should invoke indirectly using * {@link #MSG_CHECK}. */ @WorkerThread private void check() { final StorageManager storage = getContext().getSystemService(StorageManager.class); final int seq = mSeq.get(); // Check every mounted private volume to see if they're low on space for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { final File file = vol.getPath(); 获取系统设定的存储阈值 final long fullBytes = storage.getStorageFullBytes(file); final long lowBytes = storage.getStorageLowBytes(file); // Automatically trim cached data when nearing the low threshold; // when it's within 150% of the threshold, we try trimming usage // back to 200% of the threshold. if (file.getUsableSpace() < (lowBytes * 3) / 2) { final PackageManagerService pms = (PackageManagerService) ServiceManager .getService("package"); try { pms.freeStorage(vol.getFsUuid(), lowBytes * 2, 0); } catch (IOException e) { Slog.w(TAG, e); } } // Send relevant broadcasts and show notifications based on any // recently noticed state transitions. final UUID uuid = StorageManager.convert(vol.getFsUuid()); final State state = findOrCreateState(uuid); final long totalBytes = file.getTotalSpace(); final long usableBytes = file.getUsableSpace(); int oldLevel = state.level; int newLevel; 获取设备存储状态 if (mForceLevel != State.LEVEL_UNKNOWN) { // When in testing mode, use unknown old level to force sending // of any relevant broadcasts. oldLevel = State.LEVEL_UNKNOWN; newLevel = mForceLevel; } else if (usableBytes <= fullBytes) { newLevel = State.LEVEL_FULL; } else if (usableBytes <= lowBytes) { newLevel = State.LEVEL_LOW; } else if (StorageManager.UUID_DEFAULT.equals(uuid) && !isBootImageOnDisk() && usableBytes < BOOT_IMAGE_STORAGE_REQUIREMENT) { newLevel = State.LEVEL_LOW; } else { newLevel = State.LEVEL_NORMAL; } // Log whenever we notice drastic storage changes if ((Math.abs(state.lastUsableBytes - usableBytes) > DEFAULT_LOG_DELTA_BYTES) || oldLevel != newLevel) { EventLogTags.writeStorageState(uuid.toString(), oldLevel, newLevel, usableBytes, totalBytes); state.lastUsableBytes = usableBytes; } 发送通知和广播 updateNotifications(vol, oldLevel, newLevel); updateBroadcasts(vol, oldLevel, newLevel, seq); state.level = newLevel; } // Loop around to check again in future; we don't remove messages since // there might be an immediate request pending. if (!mHandler.hasMessages(MSG_CHECK)) { mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHECK), DEFAULT_CHECK_INTERVAL); } }
StorageManager
位于 frameworks\base\core\java\android\os\storage\StorageManager.java 获取系统设置的阈值
/** * Return the number of available bytes at which the given path is * considered full. * * @hide */ @UnsupportedAppUsage public long getStorageFullBytes(File path) { return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES, DEFAULT_FULL_THRESHOLD_BYTES); }
/** * Return the number of available bytes at which the given path is * considered running low on storage. * * @hide */ @UnsupportedAppUsage public long getStorageLowBytes(File path) { final long lowPercent = Settings.Global.getInt(mResolver, Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE); final long lowBytes = (path.getTotalSpace() * lowPercent) / 100; final long maxLowBytes = Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES); return Math.min(lowBytes, maxLowBytes); }
Settings字段
位于 frameworks\base\core\java\android\provider\Settings.java
/** * Maximum byte size of the low storage threshold. This is to ensure * that {@link #SYS_STORAGE_THRESHOLD_PERCENTAGE} does not result in an * overly large threshold for large storage devices. Currently this must * be less than 2GB. This default is 500MB. * * @hide */ public static final String SYS_STORAGE_THRESHOLD_MAX_BYTES = "sys_storage_threshold_max_bytes"; /** * Minimum bytes of free storage on the device before the data partition * is considered full. By default, 1 MB is reserved to avoid system-wide * SQLite disk full exceptions. * * @hide */ public static final String SYS_STORAGE_FULL_THRESHOLD_BYTES = "sys_storage_full_threshold_bytes";
可以使用串口命令进行调整
settings put global sys_storage_threshold_percentage 95 settings put global sys_storage_threshold_max_bytes 5000000000 SettingsProvider的数据保存在文件/data/system/users/0/settings_***.xml和数据库settings.db中 写入顺序 key value 写入的apk包名 <setting id="177" name="sys_storage_threshold_percentage" value="95" package="com.android.shell" />