首先我接触这个,是因为公司有个需求,检测存储空间变化,低于多少,发出提醒.这个服务刚好满足需求.
/**
* Service that monitors and maintains free space on storage volumes.
* <p>
* As the free space on a volume nears the threshold defined by
* {@link StorageManager#getStorageLowBytes(File)}, this service will clear out
* cached data to keep the disk from entering this low state.
*/
public class DeviceStorageMonitorService extends SystemService {
首先看名字和继承,就知道是一个系统服务,这个SystemService 和我们apk 中的可不是一个概念,他起来的要早很多,很多系统功能都是在这里初始化完成的.好了,这个后面说,今天主要说这个服务.
既然是服务,肯定是一直运行的,那他是如何动态监测存储变化的呢
framework/base/services/java/com/android/server/SystemServer.java
1,启动监测服务
t.traceBegin("StartDeviceMonitor");
mSystemServiceManager.startService(DeviceStorageMonitorService.class);
t.traceEnd();
2,监测流程
可以看到上面服务一启动,就会调用onstart() 方法,发送MSG_CHECK 消息
@Override
public void onStart() {
//.......
// Kick off pass to examine storage state
mHandler.removeMessages(MSG_CHECK);
mHandler.obtainMessage(MSG_CHECK).sendToTarget();
}
那么这个消息又是如何处理的呢.
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;
}
}
};
}
可以看到收到message 过后,调用的是check() 方法,这个是核心了.
/**
* 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);
}
}
简单解释下因为这个方法是读取/data 分区空间大小的,android 给他贴心的加到线程处理了,
注意上面几个关键代码:
1,到底多少算是低内存呢(这个我们系统有定制,所以会修改)
final long lowBytes = storage.getStorageLowBytes(file); 看下这个方法
@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);
}
总空间的百分比(默认是5%)和 500M 的最小值 作为最低空间提醒的标准
2,线程结束时候重复发消息
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHECK),
DEFAULT_CHECK_INTERVAL);
DEFAULT_CHECK_INTERVAL 这个默认是一分钟,达到一分钟检测一次内存的效果
3,上面记录三个状态:
State.LEVEL_FULL; 空间满了
State.LEVEL_LOW; 空间低
State.LEVEL_NORMAL 空间正常
3,业务逻辑处理
系统默认是低内存发通知,这部分,可以根据需要修改
private void updateNotifications(VolumeInfo vol, int oldLevel, int newLevel) {
final Context context = getContext();
final UUID uuid = StorageManager.convert(vol.getFsUuid());
if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) {
Intent lowMemIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
lowMemIntent.putExtra(StorageManager.EXTRA_UUID, uuid);
lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final CharSequence title = context.getText(
com.android.internal.R.string.low_internal_storage_view_title);
final CharSequence details;
if (StorageManager.UUID_DEFAULT.equals(uuid)) {
details = context.getText(isBootImageOnDisk()
? com.android.internal.R.string.low_internal_storage_view_text
: com.android.internal.R.string.low_internal_storage_view_text_no_boot);
} else {
details = context.getText(
com.android.internal.R.string.low_internal_storage_view_text);
}
PendingIntent intent = PendingIntent.getActivityAsUser(context, 0, lowMemIntent,
PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
Notification notification =
new Notification.Builder(context, SystemNotificationChannels.ALERTS)
.setSmallIcon(com.android.internal.R.drawable.stat_notify_disk_full)
.setTicker(title)
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color))
.setContentTitle(title)
.setContentText(details)
.setContentIntent(intent)
.setStyle(new Notification.BigTextStyle()
.bigText(details))
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setCategory(Notification.CATEGORY_SYSTEM)
.extend(new Notification.TvExtender()
.setChannelId(TV_NOTIFICATION_CHANNEL_ID))
.build();
notification.flags |= Notification.FLAG_NO_CLEAR;
mNotifManager.notifyAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
notification, UserHandle.ALL);
FrameworkStatsLog.write(FrameworkStatsLog.LOW_STORAGE_STATE_CHANGED,
Objects.toString(vol.getDescription()),
FrameworkStatsLog.LOW_STORAGE_STATE_CHANGED__STATE__ON);
} else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
mNotifManager.cancelAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
UserHandle.ALL);
FrameworkStatsLog.write(FrameworkStatsLog.LOW_STORAGE_STATE_CHANGED,
Objects.toString(vol.getDescription()),
FrameworkStatsLog.LOW_STORAGE_STATE_CHANGED__STATE__OFF);
}
}
注意这两个方法
if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel))
if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel))
主要逻辑是进入低内存的时候,判断之前的状态,如果之前是正常的,就发通知,如果一直处理低内存状态,就不处理,避免重发通知处理
如果是低内存到正常内存状态,那就自动把通知取消掉.
这样一个完整的内存检测机制就分析完了
4,问题
1,通知无声,不能写入数据库等
如果一直处于低内存状态,重启,会发现通知发出来了,但是没有声音,而且也不能往数据库写数据,这个主要是启动时序不同导致的,
解决办法就是,把第一次检测内存的动作往后面挪一挪.这个就涉及到SystemService 启动阶段和生命周期了.这里不做详述,解法就是等系统起来再发通知.
重写启动阶段的方法,第一次发送message 的地方发这里
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_BOOT_COMPLETED) {
// Kick off pass to examine storage state
mHandler.removeMessages(MSG_CHECK);
mHandler.obtainMessage(MSG_CHECK).sendToTarget();
}
}