Android 检测系统存储空间变化之DeviceStorageMonitorService

首先我接触这个,是因为公司有个需求,检测存储空间变化,低于多少,发出提醒.这个服务刚好满足需求.

/**
 * 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();
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值