Android 存储空间不足提示
android的存储空间不足主要在DeviceStorageMonitorService中进行逻辑处理的
主要包括以下
1.monitor设备存储
2.每60秒check free space
3.send notification
4.send broadcast
DeviceStorageMonitorService是一个系统服务,在SystemServer的startOtherServices中启动
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
t.traceBegin("StartDeviceMonitor");
mSystemServiceManager.startService(DeviceStorageMonitorService.class);
t.traceEnd();
}
启动之后我们可以通过命令 adb shell dumpsys devicestoragemonitor来查看内部的一些状态
Known volumes:
Default:
level=NORMAL lastUsableBytes=527691776
lowBytes=524288000 fullBytes=1048576
path=/data
mSeq=1 mForceState=UNKNOWN
level代表当前状态,分4种
private static final int LEVEL_UNKNOWN = -1; //未知
private static final int LEVEL_NORMAL = 0; //正常
private static final int LEVEL_LOW = 1; //低存储
private static final int LEVEL_FULL = 2; //存储已满
lastUsableBytes表示上一次check时所剩存储空间大小单位Byte
lowBytes表示低存储的阈值 524288000对应500M
fullBytes表示满存储的阈值 1048576对应1M
内部有一个handler循环往复的调用check方法,没60S一次
mHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CHECK:
check();
return;
}
}
};
private void check() {
final StorageManager storage = getContext().getSystemService(StorageManager.class);
if(storage != null) {
// 检查所有可用的存储器的剩余存储
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
final File file = vol.getPath();
final long fullBytes = storage.getStorageFullBytes(file);
final long lowBytes = storage.getStorageLowBytes(file);
// 当可用空间少于阈值的150%时通知PMS清理应用缓存到200%阈值
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);
}
}
// 根据最近状态转化,来发送通知和广播
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) {
// 在测试模式强制执行逻辑
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;
}
//更新通知
updateNotifications(vol, oldLevel, newLevel);
//更新广播
updateBroadcasts(vol, oldLevel, newLevel, seq);
state.level = newLevel;
}
} else {
Slog.w(TAG, "StorageManager service not ready !!!");
}
// 下一次循环,DEFAULT_CHECK_INTERVAL = 60S
if (!mHandler.hasMessages(MSG_CHECK)) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHECK),
DEFAULT_CHECK_INTERVAL);
}
}
updateNotification根据当前状态,更新通知,其中在点击通知时触发ACTION_MANAGE_STORAGE的调用,启动文件管理应用
private void updateNotifications(VolumeInfo vol, int oldLevel, int newLevel) {
//获取uuid
final UUID uuid = StorageManager.convert(vol.getFsUuid());
if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) {
//ACTION_MANAGE_STORAGE,点击通知时触发隐式调用文件管理
Intent lowMemIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
lowMemIntent.putExtra(StorageManager.EXTRA_UUID, uuid);
lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent intent = PendingIntent.getActivityAsUser(context, 0, lowMemIntent, 0,
null, UserHandle.CURRENT);
Notification notification =
new Notification.Builder(context, SystemNotificationChannels.ALERTS)
...
.setContentIntent(intent)
...
.build();
mNotifManager.notifyAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
notification, UserHandle.ALL);
} else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
//取消通知
mNotifManager.cancelAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
UserHandle.ALL);
}
}
updateBroadcasts处理系统低存储的相关广播,主要是低存储和满存储广播的发送和取消
private void updateBroadcasts(VolumeInfo vol, int oldLevel, int newLevel, int seq) {
//低存储状态广播
final Intent lowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
.putExtra(EXTRA_SEQUENCE, seq);
//取消低存储状态广播
final Intent notLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
.putExtra(EXTRA_SEQUENCE, seq);
//判断发送或者取消广播
if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) {
getContext().sendStickyBroadcastAsUser(lowIntent, UserHandle.ALL);
} else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
getContext().removeStickyBroadcastAsUser(lowIntent, UserHandle.ALL);
getContext().sendBroadcastAsUser(notLowIntent, UserHandle.ALL);
}
//存储满广播
final Intent fullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)
.putExtra(EXTRA_SEQUENCE, seq);
//存储满取消广播
final Intent notFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)
.putExtra(EXTRA_SEQUENCE, seq);
//判断发送
if (State.isEntering(State.LEVEL_FULL, oldLevel, newLevel)) {
getContext().sendStickyBroadcastAsUser(fullIntent, UserHandle.ALL);
} else if (State.isLeaving(State.LEVEL_FULL, oldLevel, newLevel)) {
getContext().removeStickyBroadcastAsUser(fullIntent, UserHandle.ALL);
getContext().sendBroadcastAsUser(notFullIntent, UserHandle.ALL);
}
}
实际调试测试过程中,我们可以使用命令
adb shell dumpsys devicestoragemonitor force-low 触发低存储流程调用
adb shell dumpsys devicestoragemonitor force-not-low 取消低存储流程调用
adb shell dumpsys devicestoragemonitor reset 重置测试状态
案例分析:
客户需要修改低存储的阈值,以及data分区预留空间的大小,方便即使在存储满的情况下,系统关键的进程或服务还能够正常使用,还要修改低存储阈值条件
final long lowBytes = storage.getStorageLowBytes(file);
在StorageManager.java中getStorageLowBytes方法会取500M和总可用容量的5%的最小值,
private static final int DEFAULT_THRESHOLD_PERCENTAGE = 5;
private static final long DEFAULT_THRESHOLD_MAX_BYTES = DataUnit.MEBIBYTES.toBytes(500);
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);
}
data分区预留空间
通过命令adb shell stat -f data
File: "data"
ID: 0000fc0900000000 Namelen: 255 Type: 0xf2f52010
Block Size: 4096 Fundamental block size: 4096
Blocks: Total: 6233848 Free: 5872336 Available: 5860277
Inodes: Total: 6029824 Free: 5860277
其中 (Free-Available)*4096
可用的减去可获取的乘上每个Block快大小就是预留空间的大小
预留一部分MTP空间,防止通过MTP方式填满存储造成的问题
修改frameworks/avmedia/mtp/MtpStorage.cpp,返回时预留一定空间
4000*4096=16M
diff --git a/media/mtp/MtpStorage.cpp b/media/mtp/MtpStorage.cpp
index a147325..4ebb134 100644
--- a/media/mtp/MtpStorage.cpp
+++ b/media/mtp/MtpStorage.cpp
@@ -72,7 +72,11 @@
struct statfs stat;
if (statfs(getPath(), &stat))
return -1;
- return (uint64_t)stat.f_bavail * (uint64_t)stat.f_bsize;
+//
+ uint64_t freeSpace = (uint64_t)stat.f_bavail * (uint64_t)stat.f_bsize;
+ uint64_t thresholdSpace = (uint64_t)(4000) * (uint64_t)stat.f_bsize;
+ return (freeSpace > thresholdSpace ? freeSpace - thresholdSpace : 0);
+//
}
const char* MtpStorage::getDescription() const {
快速填满存储手法:
adb shell dd if=/dev/zero of=/mnt/sdcard/bigfile 注解:
dd:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。
注意:指定数字的地方若以下列字符结尾,则乘以相应的数字:b=512;c=1;k=1024;w=2
参数注释:
- if=文件名:输入文件名,缺省为标准输入。即指定源文件。< if=input file >
- of=文件名:输出文件名,缺省为标准输出。即指定目的文件。< of=output file >x