Android 获取手机存储总大小,系统占用空间

一、Android 存储介绍及通常查询大小

手机存储有两种,内置内存和外置内存(SD),目前可扩展内存的机型正在减少,大部分是内置存储的手机,内置128G、256G已经很常见,但如果有扩展功能的话,买个乞丐版+SD卡也是美滋滋,毕竟厂家增加存储空间后手机定价也不便宜。言归正传,获取存储空间,很简单,使用中的 android.os.StatFs,传入需查阅的内存路径即可查询总内存大小,剩余可用空间,已用空间,示例代码如下:

public void queryStorage(){
        StatFs statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());

        //存储块总数量
        long blockCount = statFs.getBlockCount();
        //块大小
        long blockSize = statFs.getBlockSize();
        //可用块数量
        long availableCount = statFs.getAvailableBlocks();
        //剩余块数量,注:这个包含保留块(including reserved blocks)即应用无法使用的空间 
        long freeBlocks = statFs.getFreeBlocks();
        //这两个方法是直接输出总内存和可用空间,也有getFreeBytes
        //API level 18(JELLY_BEAN_MR2)引入
        long totalSize = statFs.getTotalBytes();
        long availableSize = statFs.getAvailableBytes();

		Log.d("statfs","total = " + getUnit(totalSize));
		Log.d("statfs","availableSize = " + getUnit(availableSize));
		
		//这里可以看出 available 是小于 free ,free 包括保留块。
		Log.d("statfs","total = " + getUnit(blockSize * blockCount));
		Log.d("statfs","available = " + getUnit(blockSize * availableCount));
        Log.d("statfs","free = " + getUnit(blockSize * freeBlocks));
}

private String[] units = {"B", "KB", "MB", "GB", "TB"};

/**
 * 单位转换
 */
private String getUnit(float size) {
        int index = 0;
        while (size > 1024 && index < 4) {
            size = size / 1024;
            index++;
        }
        return String.format(Locale.getDefault(), " %.2f %s", size, units[index]);
    }

示例运行截图:
Android 获取手机存储总大小,系统占用空间_第1张图片
注:我手机只有内置存储,大小是 16G,用这个方法是不会把系统空间算进来的。计算SD卡容量,需要兼容不同厂家定制的挂载路径,只要有sd路径就可以查询容量,sd卡查询不在本文说明范围,可自行查找其他文章。

本文是为了实现查询手机存储总大小以及系统占用,所以需要使用Android的另一个类,存储管理 android.os.StorageManager,API地址 StorageManager 。

在 android 6.0 之前,只能查到共享卷即除系统外的内存大小,目前我没有找到其他可读取总内存的方法,如有请评论一下,在android 7.1 之后版本,StorageManager 提供一个方法 getPrimaryStorageSize 可以查询内置存储的总容量,所以在 6.0 - 7.0 版本也是不能查所有,以及计算系统的占用大小,7.1之后版本可以准确计算存储大小以及系统占用请求,如果你看到一些机型可以显示系统占用多少,有可能是厂商自行设计的API,具体你可以查看显示的总容量是否为自己购买的内存大小。下面进入正文。

二、类简介

StorageManager 从API level 9 (Android 2.3, Gingerbread) 引入,部分被hide修饰函数在不同android版本上区别较大,也有遇到过厂商会删除或者重写某些函数,所以在使用时要做好异常处理,因为NoSuchMethodException 很多。

基本函数,外暴露方法:

  • allocate 函数,为你的应用申请空间,查询可用空间等
  • cache 系统给应用提供的缓存
  • obb 隔离文件系统,处理如游戏安装包的外置资源,减少APK大小
  • StorageVolume 用户可以使用的共享/外置存储卷

但重点是里面被隐藏的方法,让我们对手机总存储查询及系统占用计算提供了可能,至于为什么@hide,如果曾有在不同系统版本调用或者看过android源码,会发现各个版本区别特别大,况且这也不是一个特别需要的功能。所以我们将使用反射来做这个查询,但仍会有很大几率失败,厂商的定制修改若不按常路出牌,开发要么做特殊适配,否则也是失败。[允悲]
隐藏方法@hide

  • public @NonNull List getVolumes() 获取手机所有存储卷,内置和外置
  • public long getPrimaryStorageSize() 获取内置存储大小,主要用来计算系统占用
  • 隐藏类 VolumeInfo 存储卷的基本信息

三、查询

存储空间查询分两步,一是内置存储,二是外置存储。
系统大小,获取内置大小减去内置存储块大小(系统 = 内置总 - volume.getPath().getTotalSpace())

Talk is cheap,show me the code.

1. android 6.0 之前版本查询(6.0-7.0 版本也适用)

上面说过在 6.0 - 7.0 版本也是不能查总存储空间,以及计算系统的占用大小,所以可以直接通过 StatFs 来查询(代码在上面),不过外置存储的话需要去适配sd路径,比较麻烦。
下面是通过 StorageManager 做的内存查询,需做版本区分

public void query(){
	StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
	float unit = 1024;
	int version = Build.VERSION.SDK_INT;
	if (version < Build.VERSION_CODES.M) {//小于6.0
		long totalSize = 0, availableSize = 0;
	    try {
	        Method getVolumeList = StorageManager.class.getDeclaredMethod("getVolumeList");
	        StorageVolume[] volumeList = (StorageVolume[]) getVolumeList.invoke(storageManager);
	        if (volumeList != null) {
	            Method getPathFile = null;
	            for (StorageVolume volume : volumeList) {
	                if (getPathFile == null) {
	                    getPathFile = volume.getClass().getDeclaredMethod("getPathFile");
	                }
	                File file = (File) getPathFile.invoke(volume);
	                totalSize += file.getTotalSpace();
	                availableSize += file.getUsableSpace();
	            }
	        }
	    } catch (NoSuchMethodException e) {
	        e.printStackTrace();
	    } catch (IllegalAccessException e) {
	        e.printStackTrace();
	    } catch (InvocationTargetException e) {
	        e.printStackTrace();
	    }
	    String data = "totalSize = " + getUnit(totalSize, unit) + " ,availableSize = " + getUnit(availableSize, unit);
	        Log.d(TAG, data);
	}
}

//或者直接使用 StatFs 直接查询
public void queryStorage(){
	//如上面
}

2. android 6.0-android 7.0 版本查询

虽然 6.0-7.0 这区间版本不能查到内置内存总容量,但也可以查询到内置存储卷,而不仅仅是共享卷部分,但同样也可以直接使用StatFs查询,无区别

public void query(){
	StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
	try {
	    Method getVolumes = StorageManager.class.getDeclaredMethod("getVolumes");//6.0
	    List<Object> getVolumeInfo = (List<Object>) getVolumes.invoke(storageManager);
	    long total = 0L, used = 0L;
	    for (Object obj : getVolumeInfo) {
	
	        Field getType = obj.getClass().getField("type");
	        int type = getType.getInt(obj);
	        Log.d(TAG, "type: " + type);
	        if (type == 1) {//TYPE_PRIVATE
	
	            long totalSize = 0L;
	            long systemSize = 0L;
	
	            Method isMountedReadable = obj.getClass().getDeclaredMethod("isMountedReadable");
	            boolean readable = (boolean) isMountedReadable.invoke(obj);
	            if (readable) {
	                Method file = obj.getClass().getDeclaredMethod("getPath");
	                File f = (File) file.invoke(obj);
	                totalSize = f.getTotalSpace();
	                String _msg = "剩余总内存:" + getUnit(f.getTotalSpace(), unit) + "\n可用内存:" + getUnit(f.getFreeSpace(), unit) + "\n已用内存:" + getUnit(f.getTotalSpace() - f.getFreeSpace(), unit);
	                Log.d(TAG, _msg);
	                used += totalSize - f.getFreeSpace();
	                total += totalSize;
	            }
	            String data = "totalSize = " + getUnit(totalSize, unit) + " ,used(with system) = " + getUnit(used, unit) + " ,free = " + getUnit(totalSize - used, unit);
				Log.d(TAG, data);
	        } else if (type == 0) {//TYPE_PUBLIC
	            //外置存储
	            Method isMountedReadable = obj.getClass().getDeclaredMethod("isMountedReadable");
	            boolean readable = (boolean) isMountedReadable.invoke(obj);
	            if (readable) {
	                Method file = obj.getClass().getDeclaredMethod("getPath");
	                File f = (File) file.invoke(obj);
	                used += f.getTotalSpace() - f.getFreeSpace();
	                total += f.getTotalSpace();
	            }
	        } else if (type == 2) {//TYPE_EMULATED
	
	        }
	    }
	    Log.d(TAG, "总内存 total = " + getUnit(total, 1000) + "\n已用 used(with system) = " + getUnit(used, 1000) + "\n可用 available = " + getUnit(total - used, 1000));
	
	} catch (Exception e) {
	    e.printStackTrace();
	}	
}
3. android 7.1及之后版本查询
public void query(){
	StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
	try {
		Method getVolumes = StorageManager.class.getDeclaredMethod("getVolumes");//6.0
		List<Object> getVolumeInfo = (List<Object>) getVolumes.invoke(storageManager);
		long total = 0L, used = 0L;
		for (Object obj : getVolumeInfo) {
		
		    Field getType = obj.getClass().getField("type");
		    int type = getType.getInt(obj);
		
		    Log.d(TAG, "type: " + type);
		    if (type == 1) {//TYPE_PRIVATE
		
		        long totalSize = 0L;
		
		        //获取内置内存总大小
		        if (version >= Build.VERSION_CODES.O) {//8.0也可以不做这个判断
		            unit = 1000;
		            Method getFsUuid = obj.getClass().getDeclaredMethod("getFsUuid");
		            String fsUuid = (String) getFsUuid.invoke(obj);
		            totalSize = getTotalSize(fsUuid);//8.0 以后使用
		        } else if (version >= Build.VERSION_CODES.N_MR1) {//7.1.1
		        	//5.0 6.0 7.0没有
		            Method getPrimaryStorageSize = StorageManager.class.getMethod("getPrimaryStorageSize");
		            totalSize = (long) getPrimaryStorageSize.invoke(storageManager);
		        }
		        long systemSize = 0L;
		        Method isMountedReadable = obj.getClass().getDeclaredMethod("isMountedReadable");
		        boolean readable = (boolean) isMountedReadable.invoke(obj);
		        if (readable) {
		            Method file = obj.getClass().getDeclaredMethod("getPath");
		            File f = (File) file.invoke(obj);
		
		            if (totalSize == 0) {
		                totalSize = f.getTotalSpace();
		            }
		            String _msg = "剩余总内存:" + getUnit(f.getTotalSpace(), unit) + "\n可用内存:" + getUnit(f.getFreeSpace(), unit) + "\n已用内存:" + getUnit(f.getTotalSpace() - f.getFreeSpace(), unit);
		            Log.d(TAG, _msg);
		            systemSize = totalSize - f.getTotalSpace();
		            used += totalSize - f.getFreeSpace();
		            total += totalSize;
		        }
		        Log.d(TAG, "totalSize = " + getUnit(totalSize, unit) + " ,used(with system) = " + getUnit(used, unit) + " ,free = " + getUnit(totalSize - used, unit));
		
		    } else if (type == 0) {//TYPE_PUBLIC
		        //外置存储
		        Method isMountedReadable = obj.getClass().getDeclaredMethod("isMountedReadable");
		        boolean readable = (boolean) isMountedReadable.invoke(obj);
		        if (readable) {
		            Method file = obj.getClass().getDeclaredMethod("getPath");
		            File f = (File) file.invoke(obj);
		            used += f.getTotalSpace() - f.getFreeSpace();
		            total += f.getTotalSpace();
		        }
		    } else if (type == 2) {//TYPE_EMULATED
		
		    }
		}
		Log.d(TAG, "总内存 total = " + getUnit(total, 1000) + " ,已用 used(with system) = " + getUnit(used, 1000)+ "\n可用 available = " + getUnit(total - used, 1000));
		} catch (Exception e) {
			e.printStackTrace();
		}
}

/**
 * API 26 android O
 * 获取总共容量大小,包括系统大小
 */
public long getTotalSize(String fsUuid) {
    try {
        UUID id;
        if (fsUuid == null) {
            id = StorageManager.UUID_DEFAULT;
        } else {
            id = UUID.fromString(fsUuid);
        }
        StorageStatsManager stats = getSystemService(StorageStatsManager.class);
        return stats.getTotalBytes(id);
    } catch (NoSuchFieldError | NoClassDefFoundError | NullPointerException | IOException e) {
        e.printStackTrace();
        return -1;
    }
}

综上,要获取系统内存大小,需先获取内置存储空间大小,通过 StorageManager 的方法 getPrimaryStorageSize()获取,也可以通过StorageStatsManager(前提是 Android O 及以上版本,需权限permission.PACKAGE_USAGE_STATS),获取内置存储大小后,减去内置存储块的总容量,即为系统占用的空间大小。因为在 7.1.1版本后才有getPrimaryStorageSize() 这方法,所以在该版本前都可以使用StatFs来完成查询,此时系统大小未知。

完整代码如下:
更好的阅读体验移步 github <~~ 戳我

public static void queryWithStorageManager(Context context) {
	//5.0 查外置存储
	StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
	float unit = 1024;
	int version = Build.VERSION.SDK_INT;
	if (version < Build.VERSION_CODES.M) {//小于6.0
	    try {
	        Method getVolumeList = StorageManager.class.getDeclaredMethod("getVolumeList");
	        StorageVolume[] volumeList = (StorageVolume[]) getVolumeList.invoke(storageManager);
	        long totalSize = 0, availableSize = 0;
	        if (volumeList != null) {
	            Method getPathFile = null;
	            for (StorageVolume volume : volumeList) {
	                if (getPathFile == null) {
	                    getPathFile = volume.getClass().getDeclaredMethod("getPathFile");
	                }
	                File file = (File) getPathFile.invoke(volume);
	                totalSize += file.getTotalSpace();
	                availableSize += file.getUsableSpace();
	            }
	        }
	        Log.d(TAG, "totalSize = " + getUnit(totalSize, unit) + " ,availableSize = " + getUnit(availableSize, unit));
	    } catch (NoSuchMethodException e) {
	        e.printStackTrace();
	    } catch (IllegalAccessException e) {
	        e.printStackTrace();
	    } catch (InvocationTargetException e) {
	        e.printStackTrace();
	    }
	} else {
	
	    try {
	        Method getVolumes = StorageManager.class.getDeclaredMethod("getVolumes");//6.0
	        List<Object> getVolumeInfo = (List<Object>) getVolumes.invoke(storageManager);
	        long total = 0L, used = 0L;
	        for (Object obj : getVolumeInfo) {
	
	            Field getType = obj.getClass().getField("type");
	            int type = getType.getInt(obj);
	
	            Log.d(TAG, "type: " + type);
	            if (type == 1) {//TYPE_PRIVATE
	
	                long totalSize = 0L;
	
	                //获取内置内存总大小
	                if (version >= Build.VERSION_CODES.O) {//8.0
	                    unit = 1000;
	                    Method getFsUuid = obj.getClass().getDeclaredMethod("getFsUuid");
	                    String fsUuid = (String) getFsUuid.invoke(obj);
	                    totalSize = getTotalSize(context, fsUuid);//8.0 以后使用
	                } else if (version >= Build.VERSION_CODES.N_MR1) {//7.1.1
	                    Method getPrimaryStorageSize = StorageManager.class.getMethod("getPrimaryStorageSize");//5.0 6.0 7.0没有
	                    totalSize = (long) getPrimaryStorageSize.invoke(storageManager);
	                }
	                long systemSize = 0L;
	
	                Method isMountedReadable = obj.getClass().getDeclaredMethod("isMountedReadable");
	                boolean readable = (boolean) isMountedReadable.invoke(obj);
	                if (readable) {
	                    Method file = obj.getClass().getDeclaredMethod("getPath");
	                    File f = (File) file.invoke(obj);
	
	                    if (totalSize == 0) {
	                        totalSize = f.getTotalSpace();
	                    }
	                    systemSize = totalSize - f.getTotalSpace();
	                    used += totalSize - f.getFreeSpace();
	                    total += totalSize;
	                }
	                Log.d(TAG, "设备内存大小:" + getUnit(totalSize, unit) + "\n系统大小:" + getUnit(systemSize, unit));
	                Log.d(TAG, "totalSize = " + getUnit(totalSize, unit) + " ,used(with system) = " + getUnit(used, unit) + " ,free = " + getUnit(totalSize - used, unit));
	
	            } else if (type == 0) {//TYPE_PUBLIC
	                //外置存储
	                Method isMountedReadable = obj.getClass().getDeclaredMethod("isMountedReadable");
	                boolean readable = (boolean) isMountedReadable.invoke(obj);
	                if (readable) {
	                    Method file = obj.getClass().getDeclaredMethod("getPath");
	                    File f = (File) file.invoke(obj);
	                    used += f.getTotalSpace() - f.getFreeSpace();
	                    total += f.getTotalSpace();
	                }
	            } else if (type == 2) {//TYPE_EMULATED
	
	            }
	        }
	        Log.d(TAG, "总内存 total = " + getUnit(total, unit) + "\n已用 used(with system) = " + getUnit(used, 1000) + "\n可用 available = " + getUnit(total - used, unit));
	
	    } catch (SecurityException e) {
	        Log.e(TAG, "缺少权限:permission.PACKAGE_USAGE_STATS");
	    } catch (Exception e) {
	        e.printStackTrace();
	    }
	}
}

测试结果截图:

android P:手机存储截图
Android 获取手机存储总大小,系统占用空间_第2张图片
Log日志:进制分别为 1024 和 1000
日志

android 5.1 存储截图(这个是魅族mx5,不知道他的系统怎么计算,然后加上去就是总量 14.56 = 2.77 + 11.79)
Android 获取手机存储总大小,系统占用空间_第3张图片
Log日志:
日志

android 6.0 模拟器存储
Android 获取手机存储总大小,系统占用空间_第4张图片
Log 日志:
Android 获取手机存储总大小,系统占用空间_第5张图片

此处附上不同版本获取内置总大小的源码,有兴趣可以到下面的源码网站查看

/** {@hide} */ 
//android N
public long getPrimaryStorageSize() {
        for (String path : INTERNAL_STORAGE_SIZE_PATHS) {
                final long numberBlocks = readLong(path);
                if (numberBlocks > 0) {
                        return numberBlocks * INTERNAL_STORAGE_SECTOR_SIZE;
                    }
            }
        return 0;
    }
/** {@hide} */ 
//android O
public long getPrimaryStorageSize() {
        return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace());
    }

/** {@hide} */ 
//android P
public long getPrimaryStorageSize() {
    return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
            + Environment.getRootDirectory().getTotalSpace());
}

参考:
android 源码在线查询 www.androidos.net.cn
android 源码在线查询 androidxref.com

你可能感兴趣的:(Android开发)