授予相片和影片的部分存取權

Android 14 導入了選取的相片存取權,可讓使用者授予應用程式程式庫中特定圖片和影片的存取權,而不必授予指定類型所有媒體的存取權。

只有在應用程式指定 Android 14 (API 級別 34) 以上版本時,系統才會啟用這項變更。如果尚未使用相片挑選工具,建議在應用程式中實作以提供一致的體驗,如此一來在選取圖片和影片時,也可以加強使用者隱私,而不需要要求任何儲存空間權限。

如果您使用儲存空間權限維護自己的圖片庫挑選器,且需要全面掌控實作項目,請調整實作方式,使用新的 READ_MEDIA_VISUAL_USER_SELECTED 權限。如果應用程式並未使用新權限,系統會以相容性模式執行應用程式。

目標 SDK 版本 已宣告 READ_MEDIA_VISUAL_USER_SELECTED 已啟用所選相片存取權 使用者體驗行為
SDK 33
由應用程式控管
SDK 34 由系統控制 (相容行為)
由應用程式控管

建立自己的圖片庫挑選器需要大量開發及維護,且應用程式必須要求儲存空間權限,才能取得使用者明確同意。使用者可以拒絕這些要求,或者,如果應用程式在搭載 Android 14 的裝置上執行,且應用程式指定 Android 14 (API 級別 34) 以上版本,也可以限制對所選媒體的存取權。下圖顯示要求權限及使用新選項選取媒體的範例。

he .
圖 1.新的對話方塊可讓使用者選取想要提供給應用程式的特定相片和影片,以及平常授予完整存取權或拒絕所有存取權的選項。

本節說明使用 MediaStore 建立自己的圖片庫挑選工具的建議方法。如果您的應用程式已有圖片庫挑選器,且需要保有完整的控制權,可以使用這些範例調整實作方式。如果您沒有更新實作以處理所選相片存取權,系統會以相容模式執行應用程式。

要求權限

首先,視 OS 版本而定,在 Android 資訊清單中要求正確的儲存空間權限:

<!-- Devices running Android 12L (API level 32) or lower  -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

<!-- Devices running Android 13 (API level 33) or higher -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- To handle the reselection within the app on devices running Android 14
     or higher if your app targets Android 14 (API level 34) or higher.  -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />

接著,根據 OS 版本,要求正確的執行階段權限:

// Register ActivityResult handler
val requestPermissions = registerForActivityResult(RequestMultiplePermissions()) { results ->
    // Handle permission requests results
    // See the permission example in the Android platform samples: https://github.com/android/platform-samples
}

// Permission request logic
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
    requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED))
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO))
} else {
    requestPermissions.launch(arrayOf(READ_EXTERNAL_STORAGE))
}

部分應用程式不需要權限

自 Android 10 (API 級別 29) 起,應用程式不再需要儲存空間權限,即可將檔案新增至共用儲存空間。也就是說,應用程式不必要求儲存空間權限,就可以將圖片新增至圖片庫、錄製影片並儲存至共用儲存空間,或下載 PDF 月結單。如果您的應用程式只將檔案新增至共用儲存空間,而且不會查詢圖片或影片,您應停止要求儲存空間權限,並在 AndroidManifest.xml 中將 API 28 設為 maxSdkVersion

<!-- No permission is needed to add files to shared storage on Android 10 (API level 29) or higher  -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />

處理媒體重新選取作業

使用 Android 14 的選取相片存取權功能,應用程式應採用新的 READ_MEDIA_VISUAL_USER_SELECTED 權限控制媒體重新選取功能,並更新應用程式介面,讓使用者能授予應用程式對其他圖片和影片組合的存取權。下圖顯示要求權限並重新選取媒體的範例:

he .
圖 2.新的對話方塊也可讓使用者重新選取要用於應用程式的相片和影片。

開啟選項對話方塊時,系統會根據要求的權限顯示相片、影片,或同時顯示兩者。舉例來說,如果您在沒有 READ_MEDIA_IMAGES 權限的情況下要求 READ_MEDIA_VIDEO 權限,則只有影片會顯示在使用者介面中,讓使用者選取檔案。

// Allow the user to select only videos
requestPermissions.launch(arrayOf(READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED))

您可以檢查應用程式能否完整、部分或拒絕存取裝置相片庫,並據此更新介面。在應用程式需要儲存空間存取權時 (而非啟動時) 要求這些權限。請注意,權限授予功能可以在 onStartonResume 應用程式生命週期回呼之間變更,因為使用者可在不關閉應用程式的情況下在設定中變更存取權。

if (
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
    (
        ContextCompat.checkSelfPermission(context, READ_MEDIA_IMAGES) == PERMISSION_GRANTED ||
        ContextCompat.checkSelfPermission(context, READ_MEDIA_VIDEO) == PERMISSION_GRANTED
    )
) {
    // Full access on Android 13 (API level 33) or higher
} else if (
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
    ContextCompat.checkSelfPermission(context, READ_MEDIA_VISUAL_USER_SELECTED) == PERMISSION_GRANTED
) {
    // Partial access on Android 14 (API level 34) or higher
}  else if (ContextCompat.checkSelfPermission(context, READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
    // Full access up to Android 12 (API level 32)
} else {
    // Access denied
}

查詢裝置資料庫

您驗證您有權存取正確的儲存空間權限後,可以與 MediaStore 互動以查詢裝置程式庫 (不論授予的存取權是部分或完整權限,系統都會採用相同方法):

data class Media(
    val uri: Uri,
    val name: String,
    val size: Long,
    val mimeType: String,
)

// Run the querying logic in a coroutine outside of the main thread to keep the app responsive.
// Keep in mind that this code snippet is querying only images of the shared storage.
suspend fun getImages(contentResolver: ContentResolver): List<Media> = withContext(Dispatchers.IO) {
    val projection = arrayOf(
        Images.Media._ID,
        Images.Media.DISPLAY_NAME,
        Images.Media.SIZE,
        Images.Media.MIME_TYPE,
    )

    val collectionUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Query all the device storage volumes instead of the primary only
        Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
    } else {
        Images.Media.EXTERNAL_CONTENT_URI
    }

    val images = mutableListOf<Media>()

    contentResolver.query(
        collectionUri,
        projection,
        null,
        null,
        "${Images.Media.DATE_ADDED} DESC"
    )?.use { cursor ->
        val idColumn = cursor.getColumnIndexOrThrow(Images.Media._ID)
        val displayNameColumn = cursor.getColumnIndexOrThrow(Images.Media.DISPLAY_NAME)
        val sizeColumn = cursor.getColumnIndexOrThrow(Images.Media.SIZE)
        val mimeTypeColumn = cursor.getColumnIndexOrThrow(Images.Media.MIME_TYPE)

        while (cursor.moveToNext()) {
            val uri = ContentUris.withAppendedId(collectionUri, cursor.getLong(idColumn))
            val name = cursor.getString(displayNameColumn)
            val size = cursor.getLong(sizeColumn)
            val mimeType = cursor.getString(mimeTypeColumn)

            val image = Media(uri, name, size, mimeType)
            images.add(image)
        }
    }

    return@withContext images
}

這個程式碼片段經過簡化,說明如何與 MediaStore 互動。在可以投入實際工作環境的應用程式中,使用分頁和分頁程式庫等方式可確保良好的效能。

裝置升級後,系統會保留相片和影片的存取權

如果應用程式所在裝置是從舊版 Android 升級至 Android 14,則系統會完整保留使用者的相片和影片存取權,並自動授予應用程式部分權限。實際行為取決於裝置在升級至 Android 14 之前所授予應用程式的權限組合。

Android 13 的權限

請考慮以下狀況:

  1. 您的應用程式已安裝在執行 Android 13 的裝置上。
  2. 使用者已授予應用程式 READ_MEDIA_IMAGES 權限和 READ_MEDIA_VIDEO 權限。
  3. 接著,您為安裝有該應用程式的裝置升級至 Android 14。
  4. 您的應用程式會開始指定 Android 14 (API 級別 34) 以上版本。

在此情況下,您的應用程式仍可完整存取使用者的相片和影片。 系統也會自動保留授予應用程式的 READ_MEDIA_IMAGESREAD_MEDIA_VIDEO 權限。

Android 12 以下版本的權限

請考慮以下狀況:

  1. 您的應用程式已安裝在執行 Android 13 的裝置上。
  2. 使用者已授予應用程式 READ_EXTERNAL_STORAGE 權限或 WRITE_EXTERNAL_STORAGE 權限。
  3. 接著,您為安裝有該應用程式的裝置升級至 Android 14。
  4. 您的應用程式會開始指定 Android 14 (API 級別 34) 以上版本。

在此情況下,您的應用程式仍可完整存取使用者的相片和影片。 系統也會自動為您的應用程式授予 READ_MEDIA_IMAGES 權限和 READ_MEDIA_VIDEO 權限。

最佳做法

本節包含使用 READ_MEDIA_VISUAL_USER_SELECTED 權限的若干最佳做法。詳情請參閱權限最佳做法

不要永久儲存權限狀態

請勿以永久方式儲存權限狀態,包括 SharedPreferencesDataStore。儲存的狀態可能不會與實際狀態同步。權限狀態可能在權限重設應用程式休眠、使用者對應用程式設定進行變更,或應用程式進入背景執行狀態後改變。請改為使用 ContextCompat.checkSelfPermission() 檢查儲存空間權限。

不要為相片和影片假定完整存取權限

根據 Android 14 所導入的變更,您的應用程式可能只具備裝置相片庫的部分存取權。如果應用程式在使用 ContentResolver 進行查詢時快取 MediaStore 資料,則快取可能不是最新版本。

  • 一律使用 ContentResolver 查詢 MediaStore,勿依賴儲存的快取。
  • 在應用程式於前景運作時,將結果保留在記憶體中。
  • 在應用程式經歷 onResume 應用程式生命週期期間,請重新整理結果,因為使用者可能會透過權限設定,從完整存取權切換為部分存取權。

將 URI 存取權視為臨時權限處理

如果使用者在系統權限對話方塊中選擇「選取相片和影片」,您的應用程式對所選相片和影片的存取權最終就會失效。應用程式應該要能處理無法存取任何 Uri 的情況,也不顧及授權。

依權限篩選可選取的媒體類型

選項對話方塊與要求的權限類型有關:

  • 僅要求 READ_MEDIA_IMAGES 僅顯示可選取的圖片。
  • 如果只要求 READ_MEDIA_VIDEO,則只有影片可供選取。
  • 要求 READ_MEDIA_IMAGESREAD_MEDIA_VIDEO 時,會顯示整個照片圖庫可供選取。

根據應用程式的用途,請務必要求適當的權限,以免使用者體驗不佳。如果某項功能只希望選取影片,請務必只要求 READ_MEDIA_VIDEO

在單一作業中要求權限

如要避免使用者看到多個系統執行階段對話方塊,請在單次操作中,提出 READ_MEDIA_VISUAL_USER_SELECTEDACCESS_MEDIA_LOCATION 以及「讀取媒體」權限 (READ_MEDIA_IMAGESREAD_MEDIA_VIDEO 或同時指定兩者)。

允許使用者管理所選項目

使用者選擇部分存取模式時,應用程式不應假設裝置的相片庫中是空的,且應允許使用者授予更多檔案。

使用者可以選擇透過權限設定從完整存取權改為部分存取權,但不授予某些視覺媒體檔案的存取權。

相容性模式

如果您使用儲存空間權限維護自己的圖片庫挑選器,但尚未調整應用程式使用新的 READ_MEDIA_VISUAL_USER_SELECTED 權限,每當使用者需要選取或重新選取媒體時,系統都會以相容性模式執行應用程式。

初始媒體選擇期間的行為

在初始選取期間,如果使用者選擇「選取相片和影片」(請參閱圖 1),系統會在應用程式工作階段期間授予 READ_MEDIA_IMAGESREAD_MEDIA_VIDEO 權限,為使用者所選的相片和影片提供臨時授權與臨時存取權。當應用程式移至背景,或使用者主動終止應用程式時,系統最終會拒絕這些權限。原理就和其他一次性權限一樣。

重新選取媒體時的行為

如果您的應用程式稍後需要存取其他相片和影片,您必須再次以手動方式要求 READ_MEDIA_IMAGES 權限或 READ_MEDIA_VIDEO 權限。系統會按照與初始權限要求相同的流程,提示使用者選取相片和影片 (請參閱圖 2)。

如果您的應用程式遵循權限最佳做法,這項變更應該不應破壞應用程式。如果應用程式並未假設其保留 URI 存取權、儲存系統權限狀態,或在權限變更後重新整理顯示的圖片組合,就更是如此。但是,視應用程式的用途而定,此行為可能並不理想。為了提供使用者最佳體驗,建議您導入相片挑選工具,或調整應用程式的圖片庫挑選器,直接使用 READ_MEDIA_VISUAL_USER_SELECTED 權限處理此行為。