文件操作
- 內部存儲
- 外置SD卡
- 內置SD卡:/storage/emulated/0,其中又分為私有目錄(Android/)和公共目錄
apk
- res/raw下的和assert下的,這些數據只能讀取,不能寫入。單個文件大小不能超過1M。
- res/raw不可以有目錄結構,而assets則可以有目錄結構。
- res/raw中的文件會被映射到R.java文件中,訪問的時候直接使用資源ID即R.id.filename;assets文件夾下的文件不會被映射到R.java中,訪問的時候需要AssetManager類。
- 讀取
InputStream is = getResources().openRawResource(R.string.app_name);
InputStream is = getResources().getAssets().open("file.txt");
內部存儲
-
內部存儲 和外部存儲以是否是應用的安裝目錄來划分,內部存儲是在應用的安裝目錄下,外部存儲 在應用的安裝目錄外。
-
內部存儲的文件是應用的私有文件,其他應用不能訪問。
-
data是應用安裝目錄,需要root權限才能查看
-
應用訪問自己的內部存儲不需要權限,訪問外部存儲有可能需要申請權限。
-
應用卸載后,文件會被移除。
-
創建模式
- MODE_APPEND:即向文件尾寫入數據
- MODE_PRIVATE:即僅打開文件可寫入數據
- MODE_WORLD_READABLE:所有程序均可讀該文件數據,api17廢棄
- MODE_WORLD_WRITABLE:即所有程序均可寫入數據,api17廢棄
-
獲取內部存儲的方式
// 會新建一個目錄/data/user/0/com.example.myfile/app_newFolder
File file1 = getDir("newFolder", MODE_PRIVATE);
// /data/user/0/com.example.myfile/app_
File file2 = getDir("", MODE_PRIVATE);
// /data/user/0/com.example.myfile/files
File file3 = getFilesDir();
// /data/user/0/com.example.myfile/cache
File file4 = getCacheDir();
// /data/user/0/com.example.myfile
String s = getApplicationInfo().dataDir;
- 訪問和存儲文件
File file = new File(context.getFilesDir(), filename); // 勿多次打開和關閉同一文件,創建新文件時,寫入數據才會創建成功
- 使用信息流存儲文件
// 獲取會寫入filesDir目錄中的文件的FileOutputStream
// Android 7.0以上,必須用Context.MODE_PRIVATE文件模式傳遞到openFileOutput()
String fileContents = "Hello world!";
// Java7新特性:try括號內的資源會在try語句結束后自動釋放
// 前提是這些可關閉的資源必須實現java.lang.AutoCloseable接口
// openFileOutput只能操作filesDir目錄
try (FileOutputStream fos = openFileOutput("myfile", Context.MODE_PRIVATE)) {
fos.write(fileContents.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
File file = new File(getFilesDir(), "myfile");
Log.d(TAG, String.valueOf(file.exists())); // true
Log.d(TAG, file.getAbsolutePath()); // /data/user/0/com.example.myfile/files/myfile
- 使用信息流訪問文件
// 只能使用openFileOutput和openFileInput進行操作
// 並且也只能操作filesDir目錄
String fileName = "myfile";
FileInputStream fis = context.openFileInput(fileName);
InputStreamReader inputStreamReader =
new InputStreamReader(fis, StandardCharsets.UTF_8);
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
String line = reader.readLine();
while (line != null) {
stringBuilder.append(line).append('\n');
line = reader.readLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
String contents = stringBuilder.toString();
Log.d(TAG, "content:"+contents); // content:Hello world!
}
- 查看文件列表
// 獲取包含filesDir目錄中所有文件名稱的數組
String[] files = fileList();
for (String file : files) {
Log.d(TAG, file);
}
- 創建嵌套目錄
File directory = context.getFilesDir();
// 通過給定的父抽象路徑名和子路徑名字符串創建一個新的File實例
File file = new File(directory, filename);
- 創建緩存文件
/**
* 參數1:前綴字符串定義的文件名;必須至少有三個字符長,文件名實際會被追加亂碼
* 參數2:后綴字符串定義文件的擴展名;如果為null后綴".tmp" 將被使用
*/
File.createTempFile(filename, null, context.getCacheDir());
- 訪問緩存文件
// 當設備的內部存儲空間不足時,Android 可能會刪除這些緩存文件以回收空間。因此,請在讀取前檢查緩存文件是否存在
File cacheFile = new File(context.getCacheDir(), filename);
- 移除緩存文件
// 對該文件的File對象使用
cacheFile.delete();
// 或者使用上下文的方法,不能包含路徑分隔符
context.deleteFile(cacheFileName);
外部存儲
-
對於支持外插SD卡的設備,外部存儲包括兩部分:內置存儲卡和外置SD卡。
-
Android 4.3 以下,只能通過Context.getExternalFilesDir(type) 來獲取外部存儲在內置存儲卡分區的私有目錄,無法獲取外置SD卡。
-
Android 4.3 開始,可以通過Context.getExternalFilesDirs(type) 獲取一個File數組,包含了內置存儲卡分區和外置SD的私有目錄地址。
-
可以使用兼容庫的靜態方法 ContextCompate.getExternalFilesDirs() 兼容 4.3。
-
對外部存儲空間訪問所需的權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
版本 | 存儲位置 | 是否需要讀寫權限 |
---|---|---|
Android 4.4以前 | 外部存儲(公共目錄和私有目錄) | 需要 |
Android 4.4以后 | 外部存儲(公共目錄) | 需要 |
Android 4.4以后 | 外部存儲(私有目錄) | 不需要 |
- 分區存儲
Android 9(API 級別 28)或更低版本的設備上,只要其他應用具有相應的存儲權限,任何應用都可以訪問外部存儲空間中的應用專屬文件。
Android 10(API 級別 29)及更高版本為目標平台的應用在默認情況下被賦予了對外部存儲空間的分區訪問權限(即分區存儲)。此類應用只能訪問外部存儲空間上的應用專屬目錄,以及本應用所創建的特定類型的媒體文件。
- 驗證存儲空間可用性
// 檢查是否可以讀寫
private boolean isExternalStorageWritable() {
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED;
}
// 檢查是不是只讀
private boolean isExternalStorageReadable() {
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED ||
Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED_READ_ONLY;
}
- 先在注冊清單中申請,再動態申請
// 申請權限
public void requestPermission() {
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 如果沒有授權則申請授權,參數3是請求碼
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
}
- 判斷授權結果
// 獲取授權結果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// 判斷授權結果
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "已授權");
} else {
Toast.makeText(this, "已拒絕權限", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
- 在沒有可移除外部存儲空間的設備上,請使用以下命令啟用虛擬卷,以測試外部存儲空間可用性邏輯
adb shell sm set-virtual-disk true
- 獲取外部存儲
File file = getExternalFilesDir(null);
Log.d(TAG, "" + file); // /storage/emulated/0/Android/data/com.example.myfile/files
file = getExternalCacheDir();
Log.d(TAG, "" + file); // /storage/emulated/0/Android/data/com.example.myfile/cache
// 內置sd卡絕對路徑
Environment.getExternalStorageDirectory().getAbsolutePath();
// 內置sd卡路徑:files[0]
// 外置sd卡路徑:files[1]
File[] files = getExternalFilesDirs(null);
Log.d(TAG, ""+ files[0]); // /storage/emulated/0/Android/data/com.example.myfile/files
Log.d(TAG, ""+ files[1]); // /storage/0BF1-3C09/Android/data/com.example.myfile/files
files = getExternalCacheDirs();
Log.d(TAG, ""+ files[0]); // /storage/emulated/0/Android/data/com.example.myfile/cache
Log.d(TAG, ""+ files[1]); // /storage/0BF1-3C09/Android/data/com.example.myfile/cache
- 內置sd卡路徑:/storage/emulated/0才是最終源頭,/mnt/sdcard和/sdcard是指向它的一個軟連接而已。
- /mnt/sdcard指向/storage/self/primary
- /sdcard指向/storage/self/primary
- /storage/self/primary指向/storage/emulated/0
- 選擇物理存儲位置
// 返回數組中的第一個元素被視為主外部存儲卷
// 除非該卷已滿或不可用,否則應使用該卷
File[] externalStorageVolumes =
ContextCompat.getExternalFilesDirs(getApplicationContext(), null);
// /storage/emulated/0/Android/data/com.example.myfile/files
File primaryExternalStorage = externalStorageVolumes[0];
- 訪問持久性文件
// 從外部存儲空間訪問應用專屬文件
// getExternalFilesDir參數默認訪問的是files文件夾,也可以指定子文件夾
File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);
- 創建緩存文件
// 將應用專屬文件添加到外部存儲空間中的緩存
File externalCacheFile = new File(context.getExternalCacheDir(), filename);
- 移除緩存文件
// 從外部緩存目錄中移除文件
externalCacheFile.delete();
- 媒體內容
@Nullable
File getAppSpecificAlbumStorageDir(Context context, String albumName) {
// Get the pictures directory that's inside the app-specific directory on
// external storage.
File file = new File(context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES), albumName);
if (file == null || !file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
- 常用操作
getDatabasePath():>/data/user/0/com.example.myfile/databases/sample.db
getCacheDir():>/data/user/0/com.example.myfile/cache
getFilesDir():>/data/user/0/com.example.myfile/files
getDir("mydir"):>/data/user/0/com.example.myfile/app_webview/Web Data
getPackageCodePath():>/data/app/~~yaBwJJfuCBkU_v4FpprZuA==/com.example.myfile-MU9Z1oQkiOSdUFS7RKD0gw==/base.apk
getPackageResourcePath():/data/app/~~yaBwJJfuCBkU_v4FpprZuA==/com.example.myfile-MU9Z1oQkiOSdUFS7RKD0gw==/base.
getExternalFilesDir():/storage/emulated/0/Android/data/com.example.myfile/files
getExternalFilesDirs():---/storage/emulated/0/Android/data/com.example.myfile/files
getExternalCacheDir():/storage/emulated/0/Android/data/com.example.myfile/cache
getExternalCacheDirs():---/storage/emulated/0/Android/data/com.example.myfile/cache
getObbDir():/storage/emulated/0/Android/obb/com.example.myfile
getObbDirs():---/storage/emulated/0/Android/obb/com.example.myfile
Environment.getExternalStorageState():mounted
Environment.getExternalStorageDirectory():/storage/emulated/0
Environment.getDownloadCacheDirectory():/data/cache
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC):/storage/emulated/0/Music
Environment.getRootDirectory():/system
查詢可用空間
getAllocatableBytes()
的返回值可能大於設備上的當前可用空間量。這是因為系統已識別出可以從其他應用的緩存目錄中移除的文件。
// App needs 10 MB within internal storage.
private static final long NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;
StorageManager storageManager =
getApplicationContext().getSystemService(StorageManager.class);
UUID appSpecificInternalDirUuid = storageManager.getUuidForPath(getFilesDir());
long availableBytes =
storageManager.getAllocatableBytes(appSpecificInternalDirUuid);
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
storageManager.allocateBytes(
appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP);
} else {
Intent storageIntent = new Intent();
storageIntent.setAction(ACTION_MANAGE_STORAGE);
// Display prompt to user, requesting that they choose files to remove.
}
- 獲取存儲信息
package com.example.myfile;
import android.app.ActivityManager;
import android.content.Context;
import android.os.Environment;
import android.os.StatFs;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.Log;
import java.io.File;
import java.lang.reflect.Method;
public class StorageInfo {
private static final int INTERNAL_STORAGE = 0; // 內置sd卡
private static final int EXTERNAL_STORAGE = 1; // 外置sd卡
private static final String TAG = "wmj";
// RAM
public static String getRAMInfo(Context context) {
long totalSize = 0;
long availableSize = 0;
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
// 全局內存的使用信息
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
totalSize = memoryInfo.totalMem; // 單位B
availableSize = memoryInfo.availMem;
return "內存可用/總共:"
+ Formatter.formatFileSize(context, availableSize)
+ "/" + Formatter.formatFileSize(context, totalSize);
}
// 判斷SD是否掛載
public static boolean isSDCardMount() {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}
// 內置sd卡
public static String getROMInfo(Context context) {
// getDataDirectory是內置sd卡
StatFs statFs = new StatFs(Environment.getDataDirectory().getPath()); // /storage/emulated/0
long totalCounts = statFs.getBlockCountLong(); // 總塊數
long availableCounts = statFs.getAvailableBlocksLong(); // 可用塊數
long size = statFs.getBlockSizeLong(); // 塊大小
long availROMSize = availableCounts * size;
long totalROMSize = totalCounts * size;
return "內置sd卡可用/總共:"
+ Formatter.formatFileSize(context, availROMSize) + "/"
+ Formatter.formatFileSize(context, totalROMSize);
}
// 外置SD卡
public static String getSDCardInfo(Context context) {
if (isSDCardMount()) {
/**
* Android 4.4 開始,系統將機身存儲划分為內部存儲和外部存儲,這樣在沒有擴展SD卡時,
* 外部存儲就是機身存儲的一部分,指向/storage/emulated/0。
* 當有擴展SD卡插入時,系統將獲得兩個外部存儲路徑。
*/
// files[0]是內置sd卡路徑,files[1]是外置sd卡路徑
File[] files = context.getExternalFilesDirs(null);
StatFs statFs = new StatFs(files[1].getAbsolutePath());
long totalCounts = statFs.getBlockCountLong(); // 總塊數
long availableCounts = statFs.getAvailableBlocksLong(); // 可用塊數
long size = statFs.getBlockSizeLong(); // 塊大小
long availROMSize = availableCounts * size;
long totalROMSize = totalCounts * size;
return "外置sd卡可用/總共:"
+ Formatter.formatFileSize(context, availROMSize) + "/"
+ Formatter.formatFileSize(context, totalROMSize);
} else {
return "無外置SD卡";
}
}
// 獲取ROM信息 0:內部ROM,1:外置SD卡
public static String getStorageInfo(Context context, int type) {
String path = getStoragePath(context, type);
// 判斷是否有外置SD卡
if (!isSDCardMount() || TextUtils.isEmpty(path) || path == null) {
return "無外置SD卡";
}
File file = new File(path);
StatFs statFs = new StatFs(file.getPath());
long blockCount = statFs.getBlockCountLong(); // 總塊數
long availableBlocks = statFs.getAvailableBlocksLong(); // 可用塊數
long blockSize = statFs.getBlockSizeLong(); // 塊大小
long totalSpace = blockSize * blockCount;
long availableSpace = availableBlocks * blockSize;
return "可用/總共:"
+ Formatter.formatFileSize(context, availableSpace) + "/"
+ Formatter.formatFileSize(context, totalSpace);
}
// 通過反射獲取手機存儲路徑
public static String getStoragePath(Context context, int type) {
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
try {
Method getPathsMethod = sm.getClass().getMethod("getVolumePaths", (Class<?>[]) null);
String[] path = (String[]) getPathsMethod.invoke(sm, (Object[]) null);
switch (type) {
case INTERNAL_STORAGE:
return path[type];
case EXTERNAL_STORAGE:
if (path.length > 1) {
return path[type];
} else {
return null;
}
default:
break;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
Log.d(TAG, "外置sd卡是否加載:" + StorageInfo.isSDCardMount()); // 外置sd卡是否加載:true
Log.d(TAG, StorageInfo.getRAMInfo(this)); // 內存可用/總共:1.22 GB/2.08 GB
Log.d(TAG, StorageInfo.getROMInfo(this)); // 內置sd卡可用/總共:5.93 GB/6.24 GB
Log.d(TAG, StorageInfo.getSDCardInfo(this)); // 外置sd卡可用/總共:535 MB/535 MB
Log.d(TAG, "內置sd卡"+StorageInfo.getStorageInfo(this, 0)); // 內置sd卡可用/總共:5.93 GB/6.24 GB
Log.d(TAG, "外置sd卡"+StorageInfo.getStorageInfo(this, 1)); // 外置sd卡可用/總共:535 MB/535 MB