Android10_存儲之scoped storage&媒體文件-分區存儲權限變更及適配

Scoped storage

文件存儲介紹了內部存儲和外部存儲相關的內容。因爲外部存儲容易讀寫,所以在手機中經常看到很多“亂七八糟”的文件或文件夾,這些就是應用肆意創建的。

Android Q(10)開始添加了scoped storage的功能,更好的限制了應用訪問外部存儲。

 

先見個例子,下面代碼運行在Android Q上會有什麼現象呢:

AndroidManifest.xml中權限聲明:

 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

 

執行代碼:

複製代碼

File[] externalFiles = context.getExternalFilesDirs( null );
for (File file : externalFiles) {
    try {
        File fileA = new File( file, "aaaa.txt" );
        FileOutputStream fosA = new FileOutputStream( fileA );
        fosA.close();

        File fileB = new File( file.getParentFile().getParentFile().getParentFile().getParentFile(), "bbbb.txt" );
        Log.d( TAG, "fileA="+fileA+";\nfileB="+fileB);
        FileOutputStream fosB = new FileOutputStream( fileB );
        fosB.close();
    } catch (IOException e) {
        Log.d( TAG, "exception: "+e.getMessage() );
        e.printStackTrace();
    }
}

複製代碼

 

執行的結果:

log的結果如下,實際與log是符合的。上述代碼在四個 位置各創建一個文件,2個創建成功了2個fail了。/storage/emulated/0/和/storage/3B80-111D/下創建失敗,提示權限問題。

1

2

3

4

5

6

2019-12-13 10:52:43.541 3973-3973/com.flx.testfilestorage D/flx_storage: fileA=/storage/emulated/0/Android/data/com.flx.testfilestorage/files/aaaa.txt;

    fileB=/storage/emulated/0/bbbb.txt

2019-12-13 10:52:43.543 3973-3973/com.flx.testfilestorage D/flx_storage: exception: /storage/emulated/0/bbbb.txt: open failed: EACCES (Permission denied)

2019-12-13 10:52:43.554 3973-3973/com.flx.testfilestorage D/flx_storage: fileA=/storage/3B80-111D/Android/data/com.flx.testfilestorage/files/aaaa.txt;

    fileB=/storage/3B80-111D/bbbb.txt

2019-12-13 10:52:43.556 3973-3973/com.flx.testfilestorage D/flx_storage: exception: /storage/3B80-111D/bbbb.txt: open failed: EACCES (Permission denied)

  

scoped storage在Android 10及更高版本默認開啓。若之前的應用不滿足這一功能,而運行在Android 10上 則需要將下面的屬性設置成true,  關閉這一功能

1

<application android:requestLegacyExternalStorage="true" ... > 

上述代碼修改後就能執行完成了。

注:後續版本可能強制要求開啓scoped storage功能,上述關閉屬性方法可能只是一個過渡。

 

以前的讀寫外部存儲的權限:READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE。只要設置了這個就能夠很容易的讀寫外部存儲上的文件。

當scoped storage功能添加後,對權限和路徑 具體有如下表格:

File location Permissions needed Method of accessing (*) Files removed when app uninstalled?
App-specific directory None getExternalFilesDir() Yes
Media collections
(photos, videos, audio)
READ_EXTERNAL_STORAGE
only when
accessing other apps' files
MediaStore No
Downloads
(documents and
e-books)
None Storage Access Framework
(loads system's file picker)
No

從上表中看到,只是訪問其他應用的媒體文件 才需要READ_EXTERNAL_STORAGE權限,其他訪問方式都不需要任何權限。(Storage Access Framework不瞭解可以點擊鏈接 瞭解下)

 

開啓scoped storage後,訪問自身應用創建的文件都不需要任何權限(不管文件時創建在內部存儲還是外部存儲中)。而訪問其他應用創建的文件,需要滿足

1.需要READ_EXTERNAL_STORAGE權限;

2.該文件需要在下列某個媒體集合中:

  照片:存儲在 MediaStore.Images 中。(image/*)
  視頻:存儲在 MediaStore.Video 中。(video/*)
  音樂文件:存儲在 MediaStore.Audio 中。(audio/*)

爲了訪問另一應用創建的文件(包括“downloads”目錄下的文件),您的應用必須使用存儲訪問框架(Storage Access Framework),用戶可以通過該框架選擇特定文件。

注意:使用scoped storage的應用無法直接訪問類似 sdcard/DCIM/IMG1024.JPG 的路徑。 要訪問此類文件,必須使用MediaStore,並調用openFile()之類的方法。


scoped storage還添加了媒體相關數據限制

除非您的應用已獲得 ACCESS_MEDIA_LOCATION 權限,否則圖片文件中的 Exif 元數據會被刪除。
MediaStore.Files 表已經過濾,僅顯示照片、視頻和音頻文件。例如,該表格不會再顯示 PDF 文件。(下面媒體文件部分也說到的)

 

 

媒體文件

MediaStore提供api接口 來訪問下面定義良好的的媒體文件:

照片:存儲在 MediaStore.Images(image/*) 中。
視頻:存儲在 MediaStore.Video(video/*) 中。
音頻:存儲在 MediaStore.Audio(audio/*) 中。

 MediaStore.Files包含了所有media類型的文件集合。如果使用了scoped storage,則 MediaStore.Files僅僅包含上面3個類型(Images,Video,Audio)。

 

訪問媒體文件

加載媒體文件,調用ContentResolver的方法:

  • 單個媒體文件,調用openFileDescriptor()。
  • 單個媒體文件的縮略圖,調用loadThumbnail()。
  • 獲取媒體文件集合,調用query()。

如下面一段代碼 已Images爲例,手機中只有拍攝的兩張圖片。

複製代碼

     ContentResolver contentResolver = this.getContentResolver();
        Uri imgUri =  MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        Uri firstImgUri = null;
        Cursor cursor = contentResolver.query( imgUri, null, null, null );
        if (cursor != null && cursor.getCount() > 0) {
            cursor.moveToFirst();
            firstImgUri = Uri.fromFile( new File( cursor.getString( cursor.getColumnIndex( MediaStore.Images.Media.DATA ) ) ) );
            do {
                Log.d( TAG, "img cursor data=" + cursor.getString( cursor.getColumnIndex( MediaStore.Images.Media.DATA ) )
                    +";\nimg cursor type=" + cursor.getString( cursor.getColumnIndex( MediaStore.Images.Media.MIME_TYPE ) ));
            } while (cursor.moveToNext());
        }
        Log.d( TAG, "firstImgUri="+firstImgUri );
        try {
            ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor( firstImgUri, "r" );

            if (Build.VERSION.SDK_INT >= 29) {
                Bitmap bitmap = contentResolver.loadThumbnail( firstImgUri, new Size( 200,200 ), null );
            }
        } catch (Exception e) {
            Log.d( TAG, "exception: "+e.getMessage() );
        }

複製代碼

執行後的結果:

1

2

3

4

5

2019-01-02 11:31:00.937 15513-15513/com.flx.testfilestorage D/flx_storage: img cursor data=/storage/emulated/0/DCIM/Camera/IMG_20190102_031552_3.jpg;

    img cursor type=image/jpeg

2019-01-02 11:31:00.937 15513-15513/com.flx.testfilestorage D/flx_storage: img cursor data=/storage/emulated/0/DCIM/Camera/IMG_20190102_031909_3.jpg;

    img cursor type=image/jpeg

2019-01-02 11:31:00.938 15513-15513/com.flx.testfilestorage D/flx_storage: firstImgUri=file:///storage/emulated/0/DCIM/Camera/IMG_20190102_031552_3.jpg

  

IS_PENDING獨佔

Android 10以後,當寫入磁盤時 應用可以通過IS_PENDING標誌實現對媒體文件的獨佔訪問。

如:

複製代碼

     ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.DISPLAY_NAME, "TEST.jpg");
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        values.put(MediaStore.Images.Media.IS_PENDING, 1);

        Uri collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
        Uri item = contentResolver.insert(collection, values);

        try (ParcelFileDescriptor pfd = contentResolver.openFileDescriptor(item, "w", null)) {
            Parcel out = Parcel.obtain();
            pfd.writeToParcel( out, Parcelable.PARCELABLE_WRITE_RETURN_VALUE );
        } catch (IOException e) {
            Log.d( TAG, "e:"+e.getMessage() );
        }

        values.clear();
        values.put(MediaStore.Images.Media.IS_PENDING, 0);//釋放,使其他應用可以訪問
        contentResolver.update(item, values, null, null);

複製代碼

善始者實繁,克終者蓋寡

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章