1. 程式人生 > >【譯】如何在 Android 5.0 上獲取 SD卡 的讀寫許可權

【譯】如何在 Android 5.0 上獲取 SD卡 的讀寫許可權

因為最近專案需要,涉及到 SD卡 的讀寫操作,然而申請

<!-- 讀寫許可權 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

許可權只能對 SD卡 進行讀操作,而沒有寫許可權,也就是說,Android 在某個版本中對 SD卡 的讀寫許可權進行了限制。後在 StackoverFlow 上找到一篇相關問答,解了心中疑惑。在此,對該問答進行翻譯並附上相關 Demo,已做備忘。

背景

在 Android 4.4(KitKat) 中,Google 對 SD卡 的訪問已經做了嚴格的限制。
在 Android 5.0(Lollipop) 中,開發者可以使用 新API 要求使用者對某個指定的資料夾進行訪問授權,詳見:Android 4.4 Samsung Galaxy s4 external sd card is now read only, Remove or option to edit non app files.(譯者注:開頭挺搞笑的,都是開發者吐槽 Google 對 SD卡 做了限制)

問題

上述文章中有兩個連結:

  1. 這是 新API 的官方文件,但是並沒有多少如何使用的細節。(譯者注:這份文件其實還是有很多內容的,後面會具體細講。至於為什麼會有這種差別,可能作者提問時,該文件尚未完善吧~)

    If you really do need full access to an entire subtree of documents, start by launching ACTION_OPEN_DOCUMENT_TREE to let the user pick a directory. Then pass the resulting getData() into fromTreeUri(Context, Uri) to start working with the user selected tree.
    As you navigate the tree of DocumentFile instances, you can always use getUri() to obtain the Uri representing the underlying document for that object, for use with openInputStream(Uri), etc.
    To simplify your code on devices running KITKAT or earlier, you can use fromFile(File) which emulates the behavior of a DocumentsProvider.

對於新 API 我有以下問題:

  1. 新 API 的正確使用方式?
  2. 根據文件,系統會記錄 app 被授予訪問許可權的檔案和資料夾。那麼,我該如何檢測我對某個檔案或者資料夾是否有訪問許可權?是否有方法獲取可訪問的檔案或資料夾列表呢?
  3. 在 Android 4.4 上如何處理這個問題?Support Library 是否包含相應的解決方案
  4. 系統中是否有對應的介面可以檢視哪些 App 可以訪問哪些檔案。
  5. 在多使用者的裝置上授權該如何處理?
  6. 是否有其它關於新 API 的文件?
  7. 對 SD卡 的授權是否可以被取消?如果是,那對應的意圖是什麼?
  8. 對於資料夾授權是否是遞迴授權?指代資料夾內還巢狀有資料夾。
  9. SD 授權是否支援多選?或該應用程式需要專門告訴意圖要允許的檔案/資料夾嗎?
  10. 模擬器可以測試新 API 嘛?我的意思是,模擬器具有 SD 卡的分割槽,但它的作用是主要的外部儲存,簡單使用 android.permission.WRITE_EXTERNAL_STORAGE 是否足夠?
  11. 當用戶替換 SD卡 是會發生什麼?

這些問題問的都非常好,讓我們來深入挖掘下

如何使用新的 API

在 Kitkat 中有一份非常好的關於與 Storage Access Framework 互動的文件:Document provider.

新 API 的使用與之很相似。通過傳送以下 Intent ,讓使用者在文件樹(Directory Tree)中選擇授權目錄。

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, 42);

onActivityResult() 中,將使用者選擇的 Uri 傳遞給輔助類 DocumentFile。以下程式碼片段展示瞭如何列出選中目錄下的檔案和如何建立一個檔案。

public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
    if (resultCode == RESULT_OK) {
        Uri treeUri = resultData.getData();
        DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);

        // List all existing files inside picked directory
        for (DocumentFile file : pickedDir.listFiles()) {
            Log.d(TAG, "Found file " + file.getName() + " with size " + file.length());
        }

        // Create a new file and write into it
        DocumentFile newFile = pickedDir.createFile("text/plain", "My Novel");
        OutputStream out = getContentResolver().openOutputStream(newFile.getUri());
        out.write("A long time ago...".getBytes());
        out.close();
    }
}

DocumentFile.getUri() 返回的 Uri 使用非常靈活,可以與不同的 API 搭配使用。例如,你可以通過 Inetnt.setData() 將 Uri 分享出去,不過得將 Intent 的 flag 設定為 Intent.FLAG_GRANT_READ_URI_PERMISSION

如何檢測是否對某個檔案/資料夾有訪問許可權

預設情況下,通過 Storage Access Framework 獲取的 Uri 授權並不是永久的,裝置重啟後就會消失。不過,系統提供了相關的介面讓授權永久化,如果需要的話可自行設定。在上述程式碼,你可以如此設定:

getContentResolver().takePersistableUriPermission(treeUri,
            Intent.FLAG_GRANT_READ_URI_PERMISSION |
            Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

之後,你就可以通過 ContentResolver.getPersistedUriPermissions() 來獲取 APP 已經被永久授予許可權的 Uri。如果不在需要某個 Uri 的許可權,可以通過 ContentResolver.releasePersistableUriPermission() 來釋放。

能否在 Kitkat 中使用

不能,因為該 API 是在 Lollipop 中新增的

能否知道有哪些 APP 擁有該許可權

能。但是目前是沒有 UI 介面的,你得通過 adb shell dumpsys activity providers 來獲取。

在多使用者的裝置上授權該如何處理?

與多使用者系統的其它功能一樣,Uri 授權也是使用者獨立的。因此,同一個 APP 的 Uri 授權對每個使用者是透明的。

授權是否可以被取消?

DocumentProvider 支援隨時撤銷授權。取消授權最常見的方法就是通過上面提到 ContentResolver.releasePersistableUriPermission()

當清除應用的資料時,應用相關的授權也都會被清除。

對於資料夾授權是否是遞迴授權的?

是的,通過 ACTION_OPEN_DOCUMENT_TREE 的 Intent 獲取到授權之後,對該 Uri 下的所有檔案都有讀寫許可權。

授權是否支援多選操作?

從 Android 4.4(Kitkat) 起就支援了。您可以在啟動 ACTION_OPEN_DOCUMENT Intent 時通過設定 EXTRA_ALLOW_MULTIPLE 來實現。您可以通過使用 Intent.setType() 或者 EXTRA_MIME_TYPES 來設定可選檔案型別。具體參考:ACTION_OPEN_DOCUMENT

是否可以在模擬器上嘗試新 API

可以的。如果你的 APP 只使用 Storage Access Framework 訪問共享儲存,你甚至不再需要 READ/WRITE_EXTERNAL_STORAGE 許可權或者使用 android:maxSdkVersion 在較舊的版本上使用它們。

當用戶替換 SD卡 時會發生什麼?

當涉及物理介質時,底層媒體的 UUID(例如FAT序列號)總是被燒錄到返回的 Uri 中。The system uses this to connect you to the media that the user originally selected, even if the user swaps the media around between multiple slots.(翻譯不了)

如果使用者替換了新的 SD卡,您需要重新申請 SD卡 授權。 由於系統會記住基於每個UUID的授權,如果使用者以後重新插入,您將繼續先前授予對原始卡的訪問許可權。

參考:磁碟序列號