一、資料存盤方式介紹
Android 使用的檔案系統類似于其他平臺上基于磁盤的檔案系統,該系統為您提供了以下幾種保存應用資料的選項:
- 應用專屬存盤空間:存盤僅供應用使用的檔案,可以存盤到內部存盤卷中的專屬目錄或外部存盤空間中的其他專屬目錄,使用內部存盤空間中的目錄保存其他應用不應訪問的敏感資訊,
- 共享存盤:存盤您的應用打算與其他應用共享的檔案,包括媒體、檔案和其他檔案,
- 偏好設定:以鍵值對形式存盤私有原始資料,
- 資料庫:使用 Room 持久性庫將結構化資料存盤在專用資料庫中,
下表匯總了這些選項的特點:
| 內容型別 | 訪問方法 | 所需權限 | 其他應用是否可以訪問 | 卸載應用是否移除 |
|---|---|---|---|---|
| 僅供您的應用使用的檔案 | 從內部存盤空間訪問,可以使用 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權限 | 是 | 否 |
二、不同存盤方式使用
2.1 應用專屬存盤空間
內部存盤:/data/data/packagename/
外部存盤:/sdcard/Android/data/packagename/
這兩個目錄本App無需申請訪問權限即可使用,使用方式也很簡單,直接通過路徑訪問即可,
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();
}
}
2.2 共享媒體檔案
申請權限
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 里宣告存盤權限,還需要動態申請權限,
//檢查權限,并回傳需要申請的權限串列
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()]));
}
}
訪問方式
1. 通過路徑訪問
以圖片為例,假設圖片存盤在/sdcard/Pictures/test.jpg
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);
}
2. 通過MediaStore獲取路徑
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;
}
3. 通過MediaStore獲取Uri
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
Android 10及以上版本無法直接通過路徑獲取到檔案,即 BitmapFactory.decodeFile(imagePath) 無法正常使用,會提示沒有權限,只能通過 Uri 方式獲取到圖片資源,
2.3 SharedPreferences
SharedPreferences 主要用來保存相對較小鍵值對集合,使用也十分簡單,
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);
2.4 資料庫
關于 Room 的基本使用可以參考我的另外一篇文章:Android Room 使用
2.5 外部非媒體的共享存盤空間
除了應用專屬空間之外的存盤空間,
申請權限
與共享媒體檔案一樣,都需要申請 WRITE_EXTERNAL_STORAGE 和 READ_EXTERNAL_STORAGE
訪問方式
1. 通過路徑訪問
與媒體檔案一樣,直接構造路徑進行訪問,
通過 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 的檔案夾,
2. 通過SAF訪問
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
Android 10 開始增加了磁區存盤功能,限制APP只能訪問應用專屬存盤空間,無法直接通過路徑訪問sdcard中的檔案,只能使用SAF的方式,
可以避免各個APP無節操的一直往sdcard卡中寫入資料,而申請檔案讀寫權限的提示語也做了修改,
Android 6 - Android 9

Android 10及以上版本:

對比低版本,只允許訪問照片和媒體的內容,
適配Android 11
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();
}
}
}
授權方式如下圖:

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/279837.html
標籤:其他
