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
参数注释:

  1. if=文件名:输入文件名,缺省为标准输入。即指定源文件。< if=input file >
  2. of=文件名:输出文件名,缺省为标准输出。即指定目的文件。< of=output file >x