1. 程式人生 > >Android 獲取手機儲存總大小,系統佔用空間

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]); }

示例執行截圖:
記憶體示例
注:我手機只有內建儲存,大小是 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<VolumeInfo> 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 +=