
當(dāng)前Android Q中啟用了沙盒存儲(chǔ)模式,限制了APP向SDcard中讀寫文件,當(dāng)前谷歌尚未將該限制強(qiáng)制執(zhí)行,可以通過設(shè)置屬性啟用舊存儲(chǔ)特性。[原文]
沙盒模式下,每個(gè)APP在訪問sdcard時(shí)會(huì)進(jìn)入過濾視圖,只能訪問私有路徑(Context.getExternalFilesDir())和公共存儲(chǔ)空間(多媒體,MediaStore)。
官方文檔給對應(yīng)的解決方案,但是在我看來并不詳盡,目前可以看到有以下三個(gè)解決方案:
| 文件位置 | 所需權(quán)限 | 訪問方法 (*) | 卸載應(yīng)用時(shí)是否移除文件? | 是否需要權(quán)限 |
|---|---|---|---|---|
| 特定于應(yīng)用的目錄 | 無 | getExternalFilesDir() |
是 | 否 |
| 媒體集合 (照片、視頻、音頻) |
READ_EXTERNAL_STORAGE (僅當(dāng)訪問其他應(yīng)用的文件時(shí)) |
MediaStore |
否 | 是 |
| 下載內(nèi)容 (文檔和 電子書籍) | 無 | 存儲(chǔ)訪問框架 (加載系統(tǒng)的文件選擇器) | 否 | 否 |
除了第一種寫在“/sdcard/Android/data/packageName/file”路徑中可以正常使用InputStream&OutputStream讀寫文件,另外兩種方法都無法直接對文件IO操作。
所以后續(xù)有向SDcard中寫文件的需求優(yōu)先向APP私有路徑中寫入。
但是,需要解決APP之間共享文件的場景,比如手機(jī)唯一標(biāo)識配置文件(UUID啥的),這種需求場景需要對MediaStore和SAF存儲(chǔ)訪問框架做調(diào)研。
一、存儲(chǔ)訪問框架
訪問存儲(chǔ)框架是一個(gè)基于intent發(fā)起的文件檢索UI,一般只能在View存在時(shí)調(diào)用系統(tǒng)的文件檢索框架,在startActivityForResult() 的回調(diào)中收到文件對象。
/**
* Fires an intent to spin up the "file chooser" UI and select an image.
* 視圖檢索文件
*/
public void performFileSearch() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, READ_REQUEST_CODE);
}
但咱是寫SDK的,不能在FileManager單例或者靜態(tài)方法中讀文件的話,對咱毫無意義
二、MediaStore
MediaStore是外部存儲(chǔ)空間的公共媒體集合,存放的都是多媒體文件,在API >= 29后加入了download集合
-
照片:存儲(chǔ)在
MediaStore.Images中。 -
視頻:存儲(chǔ)在
MediaStore.Video中。 -
音樂文件:存儲(chǔ)在
MediaStore.Audio中。 -
下載文件:存儲(chǔ)在
MediaStore.Downloads中。 -
所有文件:存儲(chǔ)在
MediaStore.Files中。
一般Android中通過手機(jī)數(shù)據(jù)庫查詢Uri來對MediaStore中的文件做查詢,然后讀取文件,下面以MediaStore.Files查詢所有文件為例:
private void testFiles() {
ContentResolver contentResolver= this.getApplicationContext().getContentResolver();
String [] photoColumns=new String[]{
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.DATA,
MediaStore.Files.FileColumns.TITLE,
MediaStore.Files.FileColumns.MIME_TYPE,
MediaStore.Files.FileColumns.SIZE
};
Cursor cursor;
cursor=contentResolver.query
(MediaStore.Files.getContentUri("external"), photoColumns,
null,
null,
null);
while (cursor.moveToNext()) {
String _id=
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID));
String filePath=
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA));
String title=
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.TITLE));
String mime_type=
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE));
Log.d(TAG, "_id = " + _id);
Log.d(TAG, "title = " + title);
Log.d(TAG, "filePath = " + filePath);
Log.d(TAG, "mime_type = " + mime_type);
}
Log.d(TAG, "Finish Cursor Search");
}
通過上述代碼可以在所有文件中找到指定文件,和絕對路徑。同理,換成Image、Video、Audio、Downloads都一樣,只是在數(shù)據(jù)庫查詢時(shí)把Uri替換掉。
PS: 但是查詢有個(gè)致命傷,在開啟沙盒模式時(shí),只能在數(shù)據(jù)庫中查詢到Image、Video、Audio三個(gè)分類視圖中的文件,那么我SDK向SDcard中寫入的配置文件就不能在沙盒模式下通過數(shù)據(jù)庫查詢到。
只能獲取到mime_type == {“image/”, “video/”, “audio/*”}這三類文件
這可怎么辦呀
上邊代碼中通過ContentResolver.query查詢到文件詳情,但如果需要修改文件內(nèi)容,則使用ContentResolver.openFileDescriptor()獲取單個(gè)文件描述符。配置文件拿不到,先不管這個(gè)文件讀寫了。
小結(jié)
目前只能先使用官方支持的規(guī)避方案:通過設(shè)置android:requestLegacyExternalStorage="false"屬性來關(guān)閉APP的沙盒機(jī)制。
(我還會(huì)回來的)
