
要在 Google Play 上發(fā)布,開發(fā)者需要將應用的 目標 API 級別 (targetSdkVersion) 更新到 API 級別 30 (Android 11) 或者更高版本。針對新上架的應用,這個政策自 8 月開始生效;現(xiàn)有應用更新新的版本,這個政策的要求將自 11 月開始生效。
API 30 所帶來的一個巨大變更是,應用需要使用分區(qū)存儲 (Scoped Storage)。
變更之大,對于大型應用來說堪稱恐怖。更糟糕的是,我們在網(wǎng)上看到的有關如何適配分區(qū)存儲的建議,有一些建議十分令人迷惑,甚至會誤導我們。
為了幫您排憂解難,我們收集了一些有關分區(qū)存儲的常見問題,同時也為如何適配您的應用提供了一些建議和可能的替代方案。
Q: android:requestLegacyStorage 會被移除嗎?
A: 部分移除。
如果您的應用當前已經(jīng)設置了 android:requestLegacyStorage="true",就應該在 targetSdkVersion 設置為 30 后保持現(xiàn)狀。該標記在 Android 11 設備中沒有任何效果,但是可以繼續(xù)讓應用在 Android 10 設備上以舊的方式訪問存儲。
如果您需要針對 Android 10 設備在 AndroidManifest.xml 中設置 android:requestLegacyStorage="true",那在應用的目標版本改為 Android 11 后應當保留此設置。它仍會在 Android 10 設備上生效。
Q: android:preserveLegacyStorage 是如何工作的?
A: 如果您的應用安裝在 Android 10 設備上,并設置了 android:requestLegacyStorage="true",那在設備升級至 Android 11 后,此設置會繼續(xù)保持舊的存儲訪問方式。
?? 如果應用被卸載,或者是第一次在 Android 11 上安裝,那么就無法使用舊的存儲訪問方式。此標記僅適用于進一步幫助設備從傳統(tǒng)存儲升級到分區(qū)存儲。
Q: 如果我的應用沒有訪問照片、視頻或音頻文件,是否仍然需要請求 READ_EXTERNAL_STORAGE 權(quán)限?
A: 不需要,從 Android 11 開始,僅在訪問其他應用所屬的媒體文件時才需要請求 READ_EXTERNAL_STORAGE 權(quán)限。如果您的應用僅使用自身創(chuàng)建的非媒體文件 (或自身創(chuàng)建的媒體文件),那么就不再需要請求該權(quán)限。
如需在 Android 11 后停止請求該權(quán)限,僅需修改應用 AndroidManifest.xml 文件中的 <uses-permission> 標簽,添加 android:maxSdkVersion="29" 即可:
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
Q: 我想要訪問不屬于我應用的照片、視頻或一段音頻,我必須使用系統(tǒng)文件選擇器嗎?
A: 不。但如果您想用就可以用,ACTION_OPEN_DOCUMENT 最早可支持至 Android KitKat (API 19),而 ACTION_GET_CONTENT 則支持至 API 1,二者使用的都是系統(tǒng)文件選擇器。由于不需要任何權(quán)限,這仍然是首選的解決方案。
如果您不想使用系統(tǒng)文件選擇器,您仍然可以請求 READ_EXTERNAL_STORAGE 權(quán)限,它會使您的應用可以訪問所有的照片、視頻以及音頻文件,同時也包含訪問 File API 的權(quán)限!
如果您需要使用 File API 訪問媒體內(nèi)容,記得設置 android:requestLegacyStorage="true",否則 File API 在 Android 10 中將無法工作。
Q: 我想保存非媒體文件,但我不想在卸載我的應用時刪除它們。我需要使用 SAF 嗎?
A: 也許需要。
如果這些文件允許在應用外打開而無需通過您的應用,那么系統(tǒng)文件選擇器是較好的選擇。您可以使用 ACTION_CREATE_DOCUMENT 創(chuàng)建文件。當然也可以使用 ACTION_OPEN_DOCUMENT 來打開一個現(xiàn)有文件。
如果應用曾經(jīng)創(chuàng)建了一個目錄用于存儲所有這些文件,那最好的選擇就是使用系統(tǒng)文件選擇器和 ACTION_OPEN_DOCUMENT_TREE,以便用戶可以選擇要使用的特定文件夾。
如果這些文件只對您的應用有意義,可以考慮在應用 AndroidManifest.xml 文件的 <application> 標簽中設置 android:hasFragileUserData="true"。這將使用戶可以保留這些數(shù)據(jù),即使在卸載應用時亦是如此。

設置了該標記后,存儲文件的最佳位置將取決于其內(nèi)容。包含敏感或私人信息的文件應當存儲在 Context#getFilesDir() 所返回的目錄中;而不敏感的數(shù)據(jù)則應存儲于 Context#getExternalFilesDir() 所返回的目錄中。
Q: 我可以將非媒體文件放置于其他文件夾中 (例如 Downloads 文件夾),而無需任何權(quán)限。這是一個 Bug 嗎?
A: 不是。應用可能會向這類集合提供文件,而且最好的方式是對非媒體文件同時使用 Downloads 和 Documents 集合。不過請記得,默認情況下只有創(chuàng)建該文件的應用才可以訪問它們。其他應用需要通過系統(tǒng)文件選擇器獲得訪問權(quán)限或者擁有對外部存儲的廣泛訪問權(quán)限 (即: MANAGE_EXTERNAL_STORAGE 權(quán)限) 才行。
?? 對 MANAGE_EXTERNAL_STORAGE 權(quán)限的訪問受到 Play 政策 監(jiān)管。
Q: 如果我需要保存一個文檔,是否需要使用 SAF?
A: 不用。應用可以向 Documents 與 Downloads 集合提供非媒體文件,而無需任何特殊權(quán)限。只要沒被卸載,那么向這些集合提供文檔的應用擁有這些文檔的完全訪問權(quán)限。
?? 如果您的應用為了上面提到的方式保存文檔而請求 READ_EXTERNAL_STORAGE 權(quán)限的話,在 Android 11 及更高版本中將不必再請求該權(quán)限。您可以參考下面的示例修改對該權(quán)限的請求 (設定 maxSdkVersion 為 API 版本 29):
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
如要訪問其他應用添加的文檔,或者在您的應用卸載重裝后訪問其卸載前添加的文檔,就需要通過 ACTION_OPEN_DOCUMENT Intent 來使用系統(tǒng)文件選擇器。
Q: 我想要與其他應用共享文件,是否需要使用 SAF?
A: 不需要。如下是一些與其他應用共享文件的方式:
-
直接分享: 使用
Intent.ACTION_SEND可以讓您的用戶通過各種格式與設備上的其他應用共享數(shù)據(jù)。如果您使用這種方式,使用 AndroidX 的 FileProvider 來將 file:// Uri 自動轉(zhuǎn)換為 content:// Uri 可能會對您有所幫助。 - 創(chuàng)建您自己的 DocumentProvider: 這可以讓您的應用繼續(xù)處理應用的私有目錄 ( Context#getFilesDirs() 或 Context#getExternalFilesDirs()) 中內(nèi)容的同時,仍可以向使用系統(tǒng)文件選擇器的其他應用提供訪問權(quán)限。(請注意,可以在卸載應用后繼續(xù)保存這些文件——參閱上文中的 android:hasFragileUserData="true" 設置來了解其使用方式。)
最后的思考
Scoped Storage 是一項旨在改善用戶隱私保護的重大變更。不過仍然有很多方法可以處理不依賴使用存儲訪問框架 (Storage Access Framework) 的內(nèi)容。
如果要存儲的數(shù)據(jù)僅適用于您的應用,那么我們強烈建議使用 應用特定目錄。
如果數(shù)據(jù)是媒體文件,例如照片、視頻或者音頻,那么可以 使用 MediaStore。注意,從 Android 10 開始,提供內(nèi)容 不再需要請求權(quán)限。
也別忘了可以通過 ACTION_SEND 來與 其他應用共享數(shù)據(jù) (或允許它們 與您的應用共享數(shù)據(jù))!