1. 程式人生 > 其它 >Android 11 後的應用資料和檔案

Android 11 後的應用資料和檔案

Android應用資料的儲存方式有四種,分別是**應用專屬儲存空間**、**共享儲存**、**偏好設定**、**資料庫**。

Android應用資料的儲存方式有四種,分別是應用專屬儲存空間共享儲存偏好設定資料庫

應用專屬儲存空間

應用專屬儲存空間:存放應用專屬檔案,主要包括兩個空間,解除安裝後移除

  • 內部儲存空間:位於系統內部,通常情況下其他應用無法訪問,空間較小,寫入前應查詢可用空間

    • 內部持久檔案目錄:/data/user/0/com.xxx/files

      • 獲取方法:context.getFilesDir()

      • 可使用java.io.File建立和訪問檔案

      • 檢視檔案列表

        Array<String> files = context.fileList();
        
      • 流操作:

        • 使用資訊流儲存檔案
        String filename = "myfile";
        String fileContents = "Hello world!";
        try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
         fos.write(fileContents.toByteArray());
        }
        
        • 使用資訊流讀取檔案
        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) {
            // Error occurred when opening raw file for reading.
        } finally {
            String contents = stringBuilder.toString();
        }
        
    • 內部快取檔案目錄:/data/user/0/com.xxx/cache

      • 獲取方法:context.getCacheDir()

      • 可使用java.io.File訪問檔案

      • 建立檔案:

        File.createTempFile(filename, null, context.getCacheDir());
        
      • 移除檔案:

        • 對快取檔案:cacheFile.delete();
        • 對應用上下文:context.deleteFile(cacheFileName);
        • 若系統空間不足,快取檔案有被系統直接刪除的可能
  • 外部儲存空間:通常指的是使用者使用檔案管理器可直接管理的那部分空間,也包含使用SD卡拓展

    的空間。

    • 物理卷

      • 通常情況下,Android手機的外部儲存空間總是有一個物理卷,所以一般不用進行可用性驗證。驗證方法:

        Environment.getExternalStorageState()		//如果返回的狀態為 Environment.MEDIA_MOUNTED,則可以讀寫
        
      • 由於外部儲存空間的物理卷可能不止一個(插入SD卡的情況),需要選擇用於應用專屬儲存空間的物理卷。一般情況下,統一檔案寫入主外部儲存卷。獲取主外部儲存卷路徑的方法:

        File[] externalStorageVolumes = ContextCompat.getExternalFilesDirs(getApplicationContext(), null);	//物理卷陣列
        File primaryExternalStorage = externalStorageVolumes[0];
        
    • 可使用java.io.File建立和訪問檔案

    • 持久檔案目錄:/storage/emulated/0/Android/data/com.xxx/files

      獲取方法:

      context.getExternalFilesDir(null);  //null 可由 android.os.Environment 下的 預定義子目錄名稱進行替換,以為儲存特定媒體檔案進行分類
      
    • 快取檔案目錄:/storage/emulated/0/Android/data/com.xxx/cache

      • 獲取方法:context.getExternalCacheDir()
      • 刪除方法:externalCacheFile.delete();

共享儲存

共享儲存:包括媒體、文件和其他檔案

  • 媒體:MediaStore

    • 分割槽儲存

      • 說明:以Android 10(targetApi = 29)以及更高版本為目標的應用預設情況下僅有對外部儲存空間分割槽訪問許可權,應用只能訪問外部儲存空間私有目錄/storage/emulated/0/Android/data/com.xxx/files)和自己建立的檔案,其他檔案無法直接訪問。

      • 使用分割槽儲存後,訪問外部儲存空間公有目錄的方法:

        • 使用MediaStore提供的API建立和訪問圖片、視訊、音訊資源

          • 示例1:將bitmap圖片儲存到公有目錄Pictures資料夾下

            Bitmap bitmap =  ((BitmapDrawable) img.getDrawable()).getBitmap();
            ContentValues contentValues = new ContentValues();
            contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, System.currentTimeMillis());
            contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
            //指定Pictures資料夾下的二級資料夾名稱
            String path = String.format("%s/GalleryApplication/", Environment.DIRECTORY_PICTURES); 
            
            contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, path);
            
            Uri uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
            if(uri != null) {
            	try {
            		OutputStream outputStream = contentResolver.openOutputStream(uri);
            		bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
                     outputStream.close();
                     Toast.makeText(MainActivity.this, "新增圖片成功", Toast.LENGTH_SHORT).show();
            	} catch (IOException e) {
            	}
            }
            
          • 示例2:獲取公有目錄Pictures資料夾下的圖片

            Cursor cursor = contentResolver.query(
            	MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            	null,
            	null,
            	null,
            	MediaStore.MediaColumns.DATE_ADDED + " desc"
            );
            if (cursor != null) {
            	while (cursor.moveToNext()) {
                    long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
                    String displayName = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));
                    Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
                    Log.d("wangxiang", "displayName: " + displayName);	//檔名
                    Log.d("wangxiang", "uri: " + uri);	//uri
            	}
            	cursor.close();
            }
            
            • 注意:在沒有獲取讀取儲存空間的許可權READ_EXTERNAL_STORAGE的情況下,只能獲取到應用自己建立的圖片
        • 使用MediaStore提供的API下載檔案到Download目錄

        • 使用SAF(儲存訪問框架)建立和訪問其它任意型別的資源

  • 文件和其他檔案(媒體檔案亦可):使用儲存訪問框架

    - 建立新檔案:使用[`ACTION_CREATE_DOCUMENT`](https://developer.android.com/reference/android/content/Intent?hl=zh-cn#ACTION_CREATE_DOCUMENT) intent 操作
    
         - 示例:另存bitmap圖片到手機記憶體
    
           ```java
           Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
           intent.addCategory(Intent.CATEGORY_OPENABLE);
           intent.setType("image/*");
           intent.putExtra(Intent.EXTRA_TITLE,  System.currentTimeMillis() + ".png");
           startActivityForResult(intent, 666);
           
           //startActivityForResult回撥時呼叫
           Uri uri = data.getData();
           try {
           	OutputStream outputStream = getContentResolver().openOutputStream(uri);	//只選中了目錄,資料為空
           	Bitmap bitmap =  ((BitmapDrawable) img.getDrawable()).getBitmap();
           	bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
           	outputStream.close();
           	Toast.makeText(MainActivity.this, "另存圖片成功", Toast.LENGTH_SHORT).show();
           } catch (IOException e) {
           	Log.e("wangxiang", "Exception: " + e.getMessage());
           }
           ```
    
    • 開啟檔案:ACTION_OPEN_DOCUMENT intent 操作

      • 示例:使用SAF選擇圖片並開啟

        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("image/*");
        startActivityForResult(intent, 777);
        
        //startActivityForResult回撥時呼叫
        Uri uri = data.getData();
        Glide.with(this).load(uri).into(img);
        

偏好設定 - 未完待續

資料庫 - 未完待續

總結

Android檔案操作一共可以分多少種?

不管分多少種,在本地環境下,除應用私有目錄外,Android 11 以後只能獲取到uri,逐漸地對檔案的操作也就轉化成對uri的操作。

對獲取到的uri,如何進行檔案操作?

使用uri獲取輸出流outputstream,將其轉化為輸入流inputstream,再將檔案儲存至私有目錄,就可對其進行傳統的檔案操作。

對獲取到的uri,怎麼進行檔案上傳?

可以通過獲取到的流,進行操作。

  • 對Android 11 APP 、Android 11 裝置 來說:

    1. 儲存空間許可權更名為檔案和媒體許可權。
    2. 只請求READ_EXTERNAL_STORAGE許可權時,相當於只獲取了媒體庫許可權只能讀寫媒體庫公有目錄下的檔案、私有目錄下的檔案和使用SAF獲取到的有許可權訪問的目錄
    3. WRITE_EXTERNAL_STORAGE許可權被棄用,要想直接對內部儲存空間讀寫,需要申請所有檔案訪問許可權MANAGE_EXTERNAL_STORAGE
  • 對Android 10 APP(未啟用分割槽儲存)、Android 11 裝置來說:

    1. 儲存許可權更名為檔案和媒體許可權。
    2. 只請求READ_EXTERNAL_STORAGE許可權,也相當於請求了Android 11 上的 MANAGE_EXTERNAL_STORAGE許可權。
  • 對於Android 10 APP(未啟用分割槽儲存)、Android 10 及以下裝置來說:許可權處理同 Android 9 及以下裝置。

  • 對於Android 10 APP(啟用分割槽儲存)、Android 10 及以上裝置來說:

    1. WRITE_EXTERNAL_STORAGE許可權無用,要想直接對內部儲存空間讀寫,需關閉分割槽儲存
    2. 請求READ_EXTERNAL_STORAGE許可權時,相當於只獲取了媒體庫許可權只能讀寫媒體庫公有目錄下的檔案、私有目錄下的檔案和使用SAF獲取到的有許可權訪問的目錄