Android 使用的文件系統相似於其餘平臺上基於磁盤的文件系統。該系統爲您提供瞭如下幾種保存應用數據的選項:java
下表彙總了這些選項的特色:android
內容類型 | 訪問方法 | 所需權限 | 其餘應用是否能夠訪問 | 卸載應用是否移除 |
---|---|---|---|---|
僅供您的應用使用的文件 | 從內部存儲空間訪問,能夠使用 getFilesDir() 或 getCacheDir() 方法; 從外部存儲空間訪問,能夠使用 getExternalFilesDir() 或 getExternalCacheDir() 方法 |
從內部存儲空間訪問不須要任何權限 若是應用在搭載 Android 4.4(API 級別 19)或更高版本的設備上運行,從外部存儲空間訪問不須要任何權限 |
若是文件存儲在內部存儲空間中的目錄內,則不能訪問 若是文件存儲在外部存儲空間中的目錄內,則能夠訪問 |
是 |
可共享的媒體文件(圖片、音頻文件、視頻) | MediaStore API | 在 Android 10(API 級別 29)或更高版本中,訪問其餘應用的文件須要 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 權限 在 Android 9(API 級別 28)或更低版本中,訪問全部文件均須要相關權限 |
是,但其餘應用須要 READ_EXTERNAL_STORAGE 權限 | 否 |
鍵值對 | SharedPreferences | 無 | 否 | 是 |
結構化數據 | 數據庫 Room | 無 | 否 | 是 |
外部共享存儲空間文件 | 經過路徑或者Uri訪問 | 須要 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 權限 android 11增長MANAGE_EXTERNAL_STORAGE權限 |
是 | 否 |
內部存儲:/data/data/packagename/ 外部存儲:/sdcard/Android/data/packagename/數據庫
這兩個目錄本App無需申請訪問權限便可使用。使用方式也很簡單,直接經過路徑訪問便可。markdown
public static String readFile(Context context, String fileName) {
File file = new File(context.getCacheDir(), fileName);
try {
FileInputStream fis = new FileInputStream(file);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
Log.d(TAG, "readFile: " + br.readLine());
return br.readLine();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public static void writeFile(Context context, String fileName, String content) {
File file = new File(context.getCacheDir(), fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(content.getBytes());
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
複製代碼
Android 6.0 以前是無需申請動態權限的,在AndroidManifest.xml 裏聲明存儲權限:框架
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
複製代碼
Android 6.0 以後除了在AndroidManifest.xml 裏聲明存儲權限,還須要動態申請權限。ide
//檢查權限,並返回須要申請的權限列表
private List<String> checkPermission(Context context, String[] checkList) {
List<String> list = new ArrayList<>();
for (int i = 0; i < checkList.length; i++) {
if (PackageManager.PERMISSION_GRANTED != ActivityCompat
.checkSelfPermission(context, checkList[i])) {
list.add(checkList[i]);
}
}
return list;
}
//申請權限
private void requestPermission(Activity activity, String requestPermissionList[]) {
ActivityCompat.requestPermissions(activity, requestPermissionList, 100);
}
//用戶做出選擇後,返回申請的結果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 100) {
for (int i = 0; i < permissions.length; i++) {
if (permissions[i].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(MainActivity.this, "存儲權限申請成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "存儲權限申請失敗", Toast.LENGTH_SHORT).show();
}
}
}
}
}
//測試申請存儲權限
private void testPermission(Activity activity) {
String[] checkList = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE};
List<String> needRequestList = checkPermission(activity, checkList);
if (needRequestList.isEmpty()) {
Toast.makeText(MainActivity.this, "無需申請權限", Toast.LENGTH_SHORT).show();
} else {
requestPermission(activity, needRequestList.toArray(new String[needRequestList.size()]));
}
}
複製代碼
以圖片爲例,假設圖片存儲在/sdcard/Pictures/test.jpg
post
private Bitmap getBitmap(String fileName) {
//獲取目錄:/storage/emulated/0/
File rootFile = Environment.getExternalStorageDirectory();
String imagePath = rootFile.getAbsolutePath() + File.separator + Environment.DIRECTORY_PICTURES + File.separator + fileName;
return BitmapFactory.decodeFile(imagePath);
}
複製代碼
private Bitmap getBitmap(Context context, String fileName) {
ContentResolver contentResolver = context.getContentResolver();
//查詢條件
String selection = MediaStore.Images.Media.DISPLAY_NAME + " = ?";
String[] selectionArgs = new String[]{fileName};
Cursor cursor = contentResolver
.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, selection, selectionArgs, null);
while (cursor.moveToNext()) {
//獲取圖片路徑
String imagePath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA));
return BitmapFactory.decodeFile(imagePath);
}
return null;
}
複製代碼
private Bitmap getBitmap(Context context, String fileName) throws FileNotFoundException {
ContentResolver contentResolver = context.getContentResolver();
String selection = MediaStore.Images.Media.DISPLAY_NAME + " = ?";
String[] selectionArgs = new String[]{fileName};
Cursor cursor = contentResolver
.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, selection, selectionArgs, null);
while (cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID));
//獲取Uri
Uri contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
);
//經過Uri構造InputStream
InputStream inputStream = contentResolver.openInputStream(contentUri);
//經過InputStream獲取Bitmap
return BitmapFactory.decodeStream(inputStream);
}
return null;
}
複製代碼
Android 10及以上版本沒法直接經過路徑獲取到文件,即 BitmapFactory.decodeFile(imagePath)
沒法正常使用,會提示沒有權限。只能經過 Uri
方式獲取到圖片資源。測試
SharedPreferences 主要用來保存相對較小鍵值對集合,使用也十分簡單。ui
Context context = getActivity();
//建立SharedPreferences
SharedPreferences sharedPref = context.getSharedPreferences(
name, Context.MODE_PRIVATE);
//經過editor 寫入數據
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(key, value);
editor.commit();
//讀取數據
int data = sharedPref.getInt(key, defaultValue);
複製代碼
關於 Room 的基本使用能夠參考個人另一篇文章:Android Room 使用this
除了應用專屬空間以外的存儲空間。
與共享媒體文件同樣,都須要申請 WRITE_EXTERNAL_STORAGE
和 READ_EXTERNAL_STORAGE
與媒體文件同樣,直接構造路徑進行訪問。 經過 Environment.getExternalStorageDirectory()
獲取外部存儲路徑,而後經過這個路徑進行操做便可。
private void testPublicFile() {
File rootFile = Environment.getExternalStorageDirectory();
String imagePath = rootFile.getAbsolutePath() + File.separator + "myDir";
File myDir = new File(imagePath);
if (!myDir.exists()) {
myDir.mkdir();
}
}
複製代碼
在/sdcard/目錄下建立 myDir 的文件夾。
Storage Access Framework 簡稱SAF:存儲訪問框架。至關於系統內置了文件選擇器,經過它能夠拿到想要訪問的文件信息。
private void startSAF() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
//選擇圖片
intent.setType("image/jpeg");
//會跳轉到一個文件選擇器中
startActivityForResult(intent, 100);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 100 && data != null) {
//選中返回的圖片封裝在uri裏
Uri uri = data.getData();
Bitmap bitmap = openUri(uri);
if (bitmap != null) {
binding.iv.setImageBitmap(bitmap);
}
}
}
private Bitmap openUri(Uri uri) {
try {
//從uri構造輸入流
InputStream fis = getContentResolver().openInputStream(uri);
return BitmapFactory.decodeStream(fis);
} catch (Exception e) {
Log.e(TAG, "openUri: ", e);
}
return null;
}
複製代碼
跳轉到系統內置的文件選擇器:
該方式不須要申請讀寫權限,也能夠訪問外部文件,可是沒法直接獲取到外部文件的路徑,須要經過Uri進行轉換。
Android 10 開始增長了分區存儲功能,限制APP只能訪問應用專屬存儲空間,沒法直接經過路徑訪問sdcard中的文件,只能使用SAF的方式。 能夠避免各個APP無節操的一直往sdcard卡中寫入數據。而申請文件讀寫權限的提示語也作了修改。
Android 6 - Android 9:
Android 10及以上版本:
對比低版本,只容許訪問照片和媒體的內容。
Android 11 增長了一個全部文件訪問權限 MANAGE_EXTERNAL_STORAGE
,該權限比文件讀寫權限更爲嚴格,不是彈出框提示,而是須要跳轉到設置界面進行受權。若是受權該權限,就容許開發者使用路徑方式訪問外部存儲的全部文件。 AndroidManifest.xml 中新增如下權限:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
複製代碼
代碼中動態申請權限:
private void requestPermission() {
//須要判斷版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// 先判斷有沒有權限
if (Environment.isExternalStorageManager()) {
//已經受權
} else {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 101);
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 101 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
Toast.makeText(this, "全部文件訪問權限受權成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "全部文件訪問權限受權失敗", Toast.LENGTH_SHORT).show();
}
}
}
複製代碼
受權方式以下圖: