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 +=