1. 程式人生 > 實用技巧 >總結系列-Android10適配(2)-分割槽儲存

總結系列-Android10適配(2)-分割槽儲存

上篇介紹了Android10的部分適配總結,其中儲存適配採用的為相容模式,本篇單獨介紹正常分割槽儲存適配.

Google於2019年9月3日釋出了Android10 release版本,為了更好的保護使用者資料並限制裝置冗餘檔案增加,Android 10版本變更了裝置外部儲存訪問方式,外部儲存新特性稱為分割槽儲存(ScopedStorage),分割槽儲存遵循以下三個原則對外部儲存檔案訪問方式重新設計,便於使用者更好的管理外部儲存檔案.

(本篇主要總結,具體樣例程式碼較多,參見末尾連結)

檔案歸屬

系統記錄檔案由哪個應用建立,應用不需要儲存許可權即可以訪問應用自己建立檔案

應用資料保護

新增外部儲存應用私有目錄檔案訪問限制,應用即使申請了儲存許可權也不能訪問其他應用外部儲存私有目錄檔案

使用者資料保護

新增pdf、office、doc等檔案的訪問限制,使用者即使申請了儲存許可權也不能訪問其他應用建立的pdf、office、doc等檔案

相關目錄:
應用相容模式
1相容模式設定
2 判斷相容模式介面
分割槽儲存新特性
分割槽儲存概覽
1 分割槽儲存
2 應用私有目錄檔案訪問
3 共享目錄檔案訪問
4 其他受影響變更
儲存特性Android版本差異概覽
分割槽適配方案
分割槽儲存適配方案概覽
1檔案遷移
2 檔案訪問相容性適配
分割槽儲存適配指導
1 MediaStore API
2 Storage Access Framework

3 分享場景適配
百度APP分割槽儲存適配經驗樣例

應用相容模式

分割槽儲存存在一定的適配成本,Google為Android10版本提供了過渡方案,設定應用以相容模式執行,Android11將不再支援該行為,各應用需要在Android11發版之前完成分割槽儲存適配工作

1 相容模式設定

應用未完成外部儲存適配工作,可以臨時以相容模式執行,相容模式下應用申請儲存許可權,即可擁有外部儲存完整目錄訪問許可權,通過Android10之前檔案訪問方式執行,以下兩種方法設定應用以相容模式執行
(1)Target小於等於Android 9(API level 28)
(2)Tagret大於等於Android 10(API level 29),在manifest中設定requestLegacyExternalStorage屬性為true

android10的時候在targetSdkVersion = 29應用中,設定android:requestLegacyExternalStorage="true",就可以不啟動分割槽儲存,讓以前的檔案讀取正常使用。但是targetSdkVersion = 30中不行了,強制開啟分割槽儲存。
當然,如果是覆蓋安裝呢,可以增加android:preserveLegacyExternalStorage="true",暫時關閉分割槽儲存,好讓開發者完成資料遷移的工作。為什麼是暫時呢?因為只要解除安裝重灌,就會失效了。
以下是關於分割槽儲存會遇到的所有情況,
分情況執行:
1) targetSdkVersion = 28,執行後正常讀寫。
2) targetSdkVersion = 29,不刪除應用,targetSdkVersion 由28修改到29,覆蓋安裝,執行後正常讀寫。
3) targetSdkVersion = 29,刪除應用,重新執行,讀寫報錯,程式崩潰(open failed: EACCES (Permission denied))
4) targetSdkVersion = 29,新增android:requestLegacyExternalStorage="true"(不啟用分割槽儲存),讀寫正常不報錯
5) targetSdkVersion = 30,不刪除應用,targetSdkVersion 由29修改到30,讀寫報錯,程式崩潰(open failed: EACCES (Permission denied))
6) targetSdkVersion = 30,不刪除應用,targetSdkVersion 由29修改到30,增加android:preserveLegacyExternalStorage="true",讀寫正常不報錯
7) targetSdkVersion = 30,刪除應用,重新執行,讀寫報錯,程式崩潰(open failed: EACCES (Permission denied))

2 判斷相容模式介面


Environment.isExternalStorageLegacy()
返回值true : 應用以相容模式執行 false:應用以分割槽儲存特性執行

(備註:應用已完成儲存適配工作且已開啟分割槽儲存開關,如果當前應用以相容模式執行,覆蓋安裝後應用仍然會以相容模式執行,解除安裝重新安裝應用才會以分割槽儲存模式執行)

在適配時,我們的判斷程式碼如下:
// 使用Environment.isExternalStorageLegacy()來檢查APP的執行模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&!Environment.isExternalStorageLegacy()) {
}


這樣的好處是你可以在使用者升級後,能方便的將使用者的資料移動至應用的特定目錄。否則你只能通過SAF去移動,這樣會非常麻煩。如果你要移動資料注意只適用於Android 10下,所以現在適配反而是一個好時機。當然如果你不需要遷移資料,那適配會更省事。

分割槽儲存新特性介紹

分割槽儲存概覽

1 分割槽儲存新特性對外部儲存進行了重新設計,外部儲存被分為應用私有目錄以及共享目錄兩個部分

(1)應用私有目錄:儲存應用私有資料,外部儲存應用私有目錄對應Android/data/packagename,內部儲存應用私有目錄對應data/data/packagename;
* 內部儲存空間目錄:系統會阻止其他應用訪問這些位置,並且在 Android 10(API 級別 29)及更高版本中,系統會對這些位置進行加密。這些特徵使得這些位置非常適合儲存只有應用本身才能訪問的敏感資料。
* 外部儲存空間目錄:其他應用可以在具有適當許可權的情況下訪問這些目錄,但儲存在這些目錄中的檔案僅供您的應用使用。如果您明確打算建立其他應用能夠訪問的檔案,您的應用應改為將這些檔案儲存在外部儲存空間的共享儲存空間部分。
(2) 共享目錄:儲存其他應用可訪問檔案, 包含媒體檔案、文件檔案以及其他檔案,對應裝置DCIM、Pictures、Alarms, Music, Notifications,Podcasts, Ringtones、Movies、Download等目錄

2應用私有目錄檔案訪問


應用私有目錄檔案訪問方式與之前Android版本一致,可以通過file path獲取資源
(如需允許其他應用訪問儲存在內部儲存空間內此目錄中的檔案(如內部儲存分享檔案),請使用具有 FLAG_GRANT_READ_URI_PERMISSION 屬性的 FileProvider。)

3 共享目錄檔案訪問


許可權變更:應用讀寫自己沙箱和共享集合目錄中應用自己的檔案是不需要申請任何許可權的,但是如果應用需要讀取其他應用生成的多媒體檔案就需要申請許可權
共享目錄檔案需要通過MediaStoreAPI或者Storage Access Framework方式訪問
(1)MediaStoreAPI在共享目錄指定目錄下建立檔案或者訪問應用自己建立檔案,不需要申請儲存許可權
注:媒體檔案的應用歸因:
當以 Android 10 或更高版本為目標平臺的應用啟用了分割槽儲存時,系統會將每個媒體檔案歸因於一個應用,這決定了應用在未請求任何儲存許可權時可以訪問的檔案。每個檔案只能歸因於一個應用。因此,如果您的應用建立的媒體檔案儲存在照片、視訊或音訊檔案媒體集合中,應用便可以訪 問該檔案。
但是,如果使用者解除安裝並重新安裝您的應用,您必須請求 READ_EXTERNAL_STORAGE 才能訪問應用最初建立的檔案。此許可權請求是必需的,因為系統認為檔案歸因於以前安裝的應用版本,而不是新安裝的版本。
(2)MediaStore API訪問其他應用在共享目錄建立的媒體檔案(圖片、音訊、視訊), 需要申請儲存許可權,未申請儲存許可權,通過ContentResolver查詢不到檔案Uri,即使通過其他方式獲取到檔案Uri,讀取或建立檔案會丟擲異常;
讀取其他應用生成的多媒體檔案,需要通過MediaProvider的介面讀取,無法直接通過檔案路徑讀取;讀取其他應用存放在共享集合的圖片和視訊檔案,就需要分別申請READ_MEDIA_IMAGES和READ_MEDIA_VIDEO許可權,音樂型別檔案,就需要申請READ_MEDIA_AUDIO許可權,具體要申請哪個許可權取決於應用需要訪問的檔案型別;
系統只提供了多媒體檔案的讀許可權,沒有提供寫許可權,應用無法通過申請寫許可權修改其他應用生成的檔案;
(3)MediaStore API不能夠訪問其他應用建立的非媒體檔案(pdf、office、doc、txt等), 只能夠通過Storage Access Framework方式訪問;
下載目錄的檔案沒有增加對應的許可權,讀取下載目錄的檔案需要通過SAF的方式讀取。

其他:
寫其他應用的多媒體檔案,需要通過申請成為預設系統圖庫和音樂應用,或者讓使用者主動授權的方式實現。
需要讀寫指定的任意目錄的檔案只能通過SAF的方式實現

4 其他受影響變更

(1)圖片位置資訊 一些圖片會包含位置資訊,因為位置對於使用者屬於敏感資訊,Android 10應用在分割槽儲存模式下圖片位置資訊預設獲取不到,應用通過以下兩項設定可以獲取圖片位置資訊:
1.在manifest中申請ACCESS_MEDIA_LOCATION
2.呼叫MediaStore setRequireOriginal(Uri uri)介面更新圖片Uri
(2)MediaStore.Files應用分割槽儲存模式下,MediaStore.Files集合只能夠獲取媒體檔案資訊(圖片、音訊、視訊),獲取不到非media(pdf、office、doc、txt等)檔案
(3) File Path路徑訪問受影響介面
開啟分割槽儲存新特性, Andrioid 10不能夠通過File Path路徑直接訪問共享目錄下資源,以下介面通過File 路徑操作檔案資源,功能會受到影響,應用需要使用MediaStore或者SAF方式訪問

儲存特性Android版本差異概覽

分割槽適配方案

分割槽儲存適配方案概覽

分割槽儲存適配包含檔案遷移以及檔案訪問相容性適配兩個部分;

1 檔案遷移


檔案遷移是將應用共享目錄檔案遷移到應用私有目錄或者Android10要求的media集合目錄
(1)針對只有應用自己訪問並且應用解除安裝後允許刪除的檔案,需要遷移檔案到應用私有目錄檔案,可以通過File path方式訪問檔案資源,降低適配成本

(2)允許其他應用訪問,並且應用解除安裝後不允許刪除的檔案,檔案需要儲存在共享目錄,應用可以選擇是否進行目錄整改,將檔案遷移到Android10要求的media集合目錄

2 檔案訪問相容性適配

共享目錄檔案不能夠通過File path方式讀取,需要使用MediaStore API或者Storage Access Framework框架進行訪問

分割槽儲存適配指導

MediaStore API

1 MediaStore API簡述

系統會自動掃描外部儲存,新增檔案到系統已定義的Images、Videos、Audio files、Downloaded files集合中,Android 10通過MediaStore.Images、MediaStore.Video、MediaStore.Audio、MediaStore.Downloads訪問共享目錄檔案資源

2MediaStore API建立檔案


Android 10版本MeidaStore API只允許在共享目錄指定目錄建立檔案, 非指定目錄建立檔案會丟擲IllegalArgumentException, 建立檔案目錄彙總如下:

備註:MediaStore.Downloads.EXTERNAL_CONTENT_URI是Android10版本新增API,用於建立、訪問非媒體檔案;

3不同儲存許可權MediaStore API可訪問檔案區域

4 MediaStore API 檔案訪問

Storage Access Framework (google使用儲存訪問框架開啟檔案)

Android 4.4引入了Storage Access Framework框架,應用通過系統選擇器訪問DocumentsProvider提供檔案(包含外部儲存以及雲端儲存, 外部儲存包含應用私有目錄以及共享目錄), SAF機制不需要申請任何儲存許可權, 包含Document provider、Client app、Picker三部分
(1)Document provider:文件提供者是DocumentsProvider子類,資料模型是基於檔案層級進行設計的,文件提供者通過儲存服務(例如Google Drive)管理檔案
(2)Client app:通過呼叫ACTION_CREATE_DOCUMENT,ACTION_OPEN_DOCUMENT, andACTION_OPEN_DOCUMENT_TREEIntent獲取Document provider提供的檔案, 應用可以設定MIME type或者EXTRA_INITIAL_URI選擇需要獲取的檔案,onActivityResult介面會返回選擇檔案Uri
(3)Picker:系統UI,應用通過調起系統選擇器獲取Document provider提供的檔案資訊

分享場景適配

(1) APP主動分享檔案給其他應用,可以使用FileProvider方式賦予其他應用檔案讀取許可權, FileProvider 應用基於XML配置生成檔案Uri,其他應用不需要申請儲存許可權就可以通過接收Uri獲取檔案資源;
(2) Android 10 應用開啟分割槽儲存,通過File協議Uri或者MediaStore Uri分享檔案給其他應用, 功能會受到影響, 具體如下表格:

適配樣例經驗: 百度APP分割槽儲存適配

百度APP適配簡述

排查外部儲存共享目錄通過File方式訪問的資源,針對歷史遺留檔案, 業務方根據具體場景選擇是否進行檔案遷移;
針對只有應用自己訪問並且應用解除安裝後允許刪除的檔案,通過檔案遷移到外部儲存私有目錄方式進行適配;
針對允許其他應用訪問,並且應用解除安裝後不允許刪除的檔案, 通過使用MediaStore API或者SAF方式進行適配

補充

應用在解除安裝後,會將App-specific目錄下的資料刪除,如果在AndroidManifest.xml中宣告:android:hasFragileUserData="true"使用者可以選擇是否保留。

程式碼樣例

相關程式碼較多,具體程式碼樣例可參考 華為文件-儲存空間的限制 較為詳細


相關參考連結:
Google 儲存到專屬儲存空間 / 儲存到共享儲存檔案
Google 文件 Android10行為變更/遷移指南
SAF(Storage Access Framework)使用攻略
Android10 檔案儲存相關(圖片示例程式碼)
Android11(30)/Android10(29)分割槽儲存-適配方案(MANAGE_EXTERNAL_STORAGE)
Android 10分割槽儲存介紹及百度適配實踐
攜程Android 10適配踩坑指南 /華為文件
android 10 [API 29] 適配指南