如何检查设备在 Android Oreo API 26 及更高版本上的存储空间是否不足

How to check if the device has Low Storage on Android Oreo API 26 and Above

如何检查设备在 Android 8 Oreo 上的存储空间是否不足。我在 Android 文档中看到 Intent.ACTION_DEVICE_STORAGE_LOW 在 API 26.

中已弃用

This constant was deprecated in API level 26. if your app targets O or above, this broadcast will no longer be delivered to any BroadcastReceiver defined in your manifest. Instead, apps are strongly encouraged to use the improved getCacheDir() behavior so the system can automatically free up storage when needed. - Android Documentation

他们鼓励我改用 getCacheDir()。

但我不太了解,因为 getCacheDir() 似乎 return 系统缓存目录路径作为 FILE 对象,只能用于清除缓存或类似的东西。

但我需要检查设备是否 运行 设备存储空间不足。我希望有人能在这方面帮助我

请参阅 Android 的(AndroidX 工作)StorageNotLowTracker 实现的示例,了解如何在存储变低或正常时接收系统广播。

请注意,这是 AndroidX 在使用 'storage not low constraint' 时使用的实现。它使用已弃用的 Intent 过滤器广播操作,但它今天仍然有效。

我创建了一个类似的实现(未在此答案中共享),可以注册、取消注册并有两个回调:存储不足和存储正常。

请参阅 StorageNotLowTrackerTest 以获取有关如何对此进行测试的示例。


下面保留旧答案供参考

如问题中正确所述,建议使用 API 26 Intent.ACTION_DEVICE_STORAGE_LOW is deprecated and Context#getCacheDir() 来释放应用程序缓存中的 space。

这有多个问题(在下面列举),但首先:请注意,保持缓存 'reasonably small'(例如 1 MB)是一种很好的做法,我引用:

getCacheDir()

Returns a File representing an internal directory for your app's temporary cache files. Be sure to delete each file once it is no longer needed and implement a reasonable size limit for the amount of memory you use at any given time, such as 1MB.

Caution: If the system runs low on storage, it may delete your cache files without warning.

(source)

所以,这里存在三个问题:

  1. 我们应该清除缓存,但它可能已经相当小了(例如 1 MB),因此清除它可能不会释放足够的空间 space 以使空闲存储空间再次恢复正常(类似于 also已弃用 Intent.ACTION_DEVICE_STORAGE_OK,以前可用于此)
  2. 如前所述,缓存很可能已经被系统清除,因为存储空间不足,系统可能会根据需要清除应用程序的缓存。因此,您自己清除它可能不会释放任何存储空间。
  3. 该文档根本没有指定如何实际检测设备存储空间是否不足。

所以,清除缓存似乎没有帮助,所以我不会详细介绍如何做到这一点。

但是,根据 this answer,我们可以假设在 10% 可用存储时系统进入我们想要检测的低存储状态。这个数字是 Android 的默认值,但根据链接的答案,几乎无法阻止设备制造商(或 ROM 开发商)更改它。

此时,对我来说,这 10% 是一个神奇的数字,我想知道我是否可以通过编程方式确定这个阈值。如果您知道如何,请编辑我的答案,post 自己的答案或评论我的答案。

要使用 getCacheDir() 执行此操作,您可以使用以下内容:

Java,来自 Context(例如 Activity):

File cacheDir = getCacheDir();
if (cacheDir.getUsableSpace() * 100 / cacheDir.getTotalSpace() <= 10) { // Alternatively, use cacheDir.getFreeSpace()
  // Handle storage low state
} else {
  // Handle storage ok state
}

Kotlin,来自 Context(例如 Activity):

if (cacheDir.usableSpace * 100 / cacheDir.totalSpace <= 10) { // Alternatively, use cacheDir.freeSpace
  // Handle storage low state
} else {
  // Handle storage ok state
}

现在,是使用可用的 space 还是免费的 space,我还不是很清楚。差异描述为 here.

深入研究 Android 源代码,我发现了一个系统服务,我无法在我的代码中访问它,它检查低存储空间:DeviceStorageMonitorService. It gets its lowBytes variable from StorageManager#getStorageLowBytes,我也无法访问。如果这可以通过某种非 hacky 方式实现,那将是获得低存储字节阈值的一种方式。在那里你看到源使用 getUsableSpace(),所以这就是为什么我也为我的代码片段选择它而不是 getFreeSpace()

在深入研究 android 的代码后,excatlty class 释放了名为 DeviceStorageMonitorService

的低存储通知

这是我发现的,有些手机使用 sys_storage_threshold_percentage,有些手机使用 sys_storage_threshold_max_bytes,因此要测试存储,您应该使用两个键从 Settings.Secure 中获取实际值,然后在 sys_storage_threshold_percentage * Total memory size of data system foldersys_storage_threshold_max_bytes 之间进行比较,然后取较小的值并将其与数据系统文件夹的可用存储 space 进行比较,这是如何做的代码

    private void checkForLowStorage() {

        long mFreeMem = getDeviceCurrentStorage();
        float deviceLowStorageThreshold = getDeviceLowStorageThreshold();
        if (mFreeMem <= deviceLowStorageThreshold) {
            Toast.makeText(this, R.string.low_storage_error_message, Toast.LENGTH_LONG).show();
            // Handle storage low state
        } else {
            // Handle storage ok state
        }
    }

    private long getDeviceCurrentStorage() {

        long mFreeMem = 0;
        try {
            StatFs mDataFileStats = new StatFs("/data");
            mDataFileStats.restat("/data");
            mFreeMem = (long) mDataFileStats.getAvailableBlocksLong() *
                    mDataFileStats.getBlockSizeLong();
        } catch (IllegalArgumentException e) {
            // use the old value of mFreeMem
        }
        return mFreeMem;
    }

    private long getDeviceLowStorageThreshold() {

        long value = Settings.Secure.getInt(
                getContentResolver(),
                "sys_storage_threshold_percentage",
                10);
        StatFs mDataFileStats = new StatFs("/data");
        long mTotalMemory = ((long) mDataFileStats.getBlockCountLong() *
                mDataFileStats.getBlockSizeLong()) / 100L;
        value *= mTotalMemory;
        long maxValue = Settings.Secure.getInt(
                getContentResolver(),
                "sys_storage_threshold_max_bytes",
                500*1024*1024);
        return Math.min(value, maxValue);
    }

我还在测试它,不知道它是否不能在某些设备上运行