在 Android 14、15 中,Google 進一步優化了存儲權限系統,特別是寫權限的管理。以下是完整的 Java 實現方案:
1. AndroidManifest.xml 聲明權限
<!-- Android 14 存儲權限 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <!-- 新增的視覺媒體選擇權限 --> <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" /> <!-- 兼容舊版本 (可選) --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- 寫入權限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- 僅用于兼容舊版本 -->
2. Java 權限請求實現
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class StoragePermissionHelper extends AppCompatActivity {
? ? // 多權限請求啟動器
? ? private final ActivityResultLauncher<String[]> requestPermissionsLauncher =
? ? ? ? ? ? registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(),
? ? ? ? ? ? ? ? ? ? this::handlePermissionResult);
? ? // 檢查并請求存儲權限
? ? public void checkAndRequestStoragePermissions() {
? ? ? ? List<String> permissionsToRequest = new ArrayList<>();
? ? ? ??
? ? ? ? // Android 14+ 需要的權限(讀、寫權限)
? ? ? ? if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
? ? ? ? ? ? if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES)?
? ? ? ? ? ? ? ? ? ? != PackageManager.PERMISSION_GRANTED) {
? ? ? ? ? ? ? ? permissionsToRequest.add(Manifest.permission.READ_MEDIA_IMAGES);
? ? ? ? ? ? }
? ? ? ? ? ? if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_VIDEO)?
? ? ? ? ? ? ? ? ? ? != PackageManager.PERMISSION_GRANTED) {
? ? ? ? ? ? ? ? permissionsToRequest.add(Manifest.permission.READ_MEDIA_VIDEO);
? ? ? ? ? ? }
? ? ? ? ? ? if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)?
? ? ? ? ? ? ? ? ? ? != PackageManager.PERMISSION_GRANTED) {
? ? ? ? ? ? ? ? permissionsToRequest.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED);
? ? ? ? ? ? }
? ? ? ? }?
? ? ? ? // Android 13(讀、寫權限)
? ? ? ? else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
? ? ? ? ? ? if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES)?
? ? ? ? ? ? ? ? ? ? != PackageManager.PERMISSION_GRANTED) {
? ? ? ? ? ? ? ? permissionsToRequest.add(Manifest.permission.READ_MEDIA_IMAGES);
? ? ? ? ? ? }
? ? ? ? ? ? if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_VIDEO)?
? ? ? ? ? ? ? ? ? ? != PackageManager.PERMISSION_GRANTED) {
? ? ? ? ? ? ? ? permissionsToRequest.add(Manifest.permission.READ_MEDIA_VIDEO);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // Android 10-12(讀、寫權限)
? ? ? ? else if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)?
? ? ? ? ? ? ? ? != PackageManager.PERMISSION_GRANTED) {
? ? ? ? ? ? permissionsToRequest.add(Manifest.permission.READ_EXTERNAL_STORAGE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {permissionsToRequest.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);} }
? ? ? ? if (permissionsToRequest.isEmpty()) {
? ? ? ? ? ? onStoragePermissionGranted();
? ? ? ? } else {
? ? ? ? ? ? requestPermissionsLauncher.launch(permissionsToRequest.toArray(new String[0]));
? ? ? ? }
? ? }
? ? // 處理權限請求結果
? ? private void handlePermissionResult(Map<String, Boolean> permissions) {
? ? ? ? List<String> deniedPermissions = new ArrayList<>();
? ? ? ??
? ? ? ? for (Map.Entry<String, Boolean> entry : permissions.entrySet()) {
? ? ? ? ? ? if (!entry.getValue()) {
? ? ? ? ? ? ? ? deniedPermissions.add(entry.getKey());
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? if (deniedPermissions.isEmpty()) {
? ? ? ? ? ? onStoragePermissionGranted();
? ? ? ? } else {
? ? ? ? ? ? handleDeniedPermissions(deniedPermissions);
? ? ? ? }
? ? }
? ? // 權限全部授予
? ? private void onStoragePermissionGranted() {
? ? ? ? Toast.makeText(this, "存儲權限已授予", Toast.LENGTH_SHORT).show();
? ? ? ? // 這里可以執行需要權限的操作
? ? }
? ? // 處理被拒絕的權限
? ? private void handleDeniedPermissions(List<String> deniedPermissions) {
? ? ? ? for (String permission : deniedPermissions) {
? ? ? ? ? ? if (shouldShowRequestPermissionRationale(permission)) {
? ? ? ? ? ? ? ? showRationaleDialog(permission);
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? showGoToSettingsDialog(permission);
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? // 顯示權限解釋對話框
? ? private void showRationaleDialog(String permission) {
? ? ? ? new AlertDialog.Builder(this)
? ? ? ? ? ? ? ? .setTitle("需要權限")
? ? ? ? ? ? ? ? .setMessage(getPermissionMessage(permission))
? ? ? ? ? ? ? ? .setPositiveButton("確定", (dialog, which) ->?
? ? ? ? ? ? ? ? ? ? checkAndRequestStoragePermissions())
? ? ? ? ? ? ? ? .setNegativeButton("取消", null)
? ? ? ? ? ? ? ? .show();
? ? }
? ? // 顯示前往設置對話框
? ? private void showGoToSettingsDialog(String permission) {
? ? ? ? new AlertDialog.Builder(this)
? ? ? ? ? ? ? ? .setTitle("權限被永久拒絕")
? ? ? ? ? ? ? ? .setMessage("請在應用設置中手動授予" + getPermissionName(permission) + "權限")
? ? ? ? ? ? ? ? .setPositiveButton("去設置", (dialog, which) -> {
? ? ? ? ? ? ? ? ? ? Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
? ? ? ? ? ? ? ? ? ? intent.setData(Uri.fromParts("package", getPackageName(), null));
? ? ? ? ? ? ? ? ? ? startActivity(intent);
? ? ? ? ? ? ? ? })
? ? ? ? ? ? ? ? .setNegativeButton("取消", null)
? ? ? ? ? ? ? ? .show();
? ? }
? ? // 獲取權限說明信息
? ? private String getPermissionMessage(String permission) {
? ? ? ? switch (permission) {
? ? ? ? ? ? case Manifest.permission.READ_MEDIA_IMAGES:
? ? ? ? ? ? ? ? return "需要訪問您的照片以提供完整功能";
? ? ? ? ? ? case Manifest.permission.READ_MEDIA_VIDEO:
? ? ? ? ? ? ? ? return "需要訪問您的視頻以提供完整功能";
? ? ? ? ? ? case Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED:
? ? ? ? ? ? ? ? return "需要訪問您選擇的媒體文件";
? ? ? ? ? ? case Manifest.permission.READ_EXTERNAL_STORAGE:
? ? ? ? ? ? ? ? return "需要訪問您的文件以提供完整功能";
? ? ? ? ? ? default:
? ? ? ? ? ? ? ? return "需要此權限以提供完整功能";
? ? ? ? }
? ? }
? ? // 獲取權限名稱
? ? private String getPermissionName(String permission) {
? ? ? ? switch (permission) {
? ? ? ? ? ? case Manifest.permission.READ_MEDIA_IMAGES:
? ? ? ? ? ? ? ? return "照片訪問";
? ? ? ? ? ? case Manifest.permission.READ_MEDIA_VIDEO:
? ? ? ? ? ? ? ? return "視頻訪問";
? ? ? ? ? ? case Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED:
? ? ? ? ? ? ? ? return "選擇的媒體文件訪問";
? ? ? ? ? ? case Manifest.permission.READ_EXTERNAL_STORAGE:
? ? ? ? ? ? ? ? return "文件訪問";
? ? ? ? ? ? default:
? ? ? ? ? ? ? ? return "存儲";
? ? ? ? }
? ? }
}
3. 使用示例
public class MainActivity extends StoragePermissionHelper {
? ? @Override
? ? protected void onCreate(Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.activity_main);
? ? ? ??
? ? ? ? findViewById(R.id.btn_request_storage).setOnClickListener(v -> {
? ? ? ? ? ? checkAndRequestStoragePermissions();
? ? ? ? });
? ? }
? ??
? ? @Override
? ? protected void onResume() {
? ? ? ? super.onResume();
? ? ? ? // 從設置返回后檢查權限狀態
? ? ? ? verifyStoragePermissions();
? ? }
? ??
? ? private void verifyStoragePermissions() {
? ? ? ? boolean hasPermissions;
? ? ? ??
? ? ? ? if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
? ? ? ? ? ? hasPermissions = ContextCompat.checkSelfPermission(this,?
? ? ? ? ? ? ? ? ? ? Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED
? ? ? ? ? ? ? ? ? ? && ContextCompat.checkSelfPermission(this,?
? ? ? ? ? ? ? ? ? ? Manifest.permission.READ_MEDIA_VIDEO) == PackageManager.PERMISSION_GRANTED;
? ? ? ? } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
? ? ? ? ? ? hasPermissions = ContextCompat.checkSelfPermission(this,?
? ? ? ? ? ? ? ? ? ? Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED;
? ? ? ? } else {
? ? ? ? ? ? hasPermissions = ContextCompat.checkSelfPermission(this,?
? ? ? ? ? ? ? ? ? ? Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
? ? ? ? }
? ? ? ??
? ? ? ? if (hasPermissions) {
? ? ? ? ? ? onStoragePermissionGranted();
? ? ? ? }
? ? }
}
Android 14 存儲權限關鍵點
-
新增權限:
-
READ_MEDIA_VISUAL_USER_SELECTED
: 允許用戶選擇特定媒體文件 -
其他媒體權限與 Android 13 相同
-
-
權限策略變化:
-
完全移除了?
READ_EXTERNAL_STORAGE
?對媒體文件的控制 -
應用默認只能訪問自己創建的文件
-
訪問其他媒體文件必須請求權限
-
-
最佳實踐:
-
優先使用系統照片選擇器 (Photo Picker)
-
只在真正需要時才請求權限
-
提供清晰的權限解釋
-
正確處理所有可能的拒絕情況
-
-
兼容性考慮:
-
使用?
Build.VERSION.SDK_INT
?檢查系統版本 -
為不同版本提供不同的權限請求策略
-
測試在各種場景下的權限行為
-
這套實現方案完全符合 Android 14 的存儲權限要求,同時保持了良好的向后兼容性。
Android 14 寫權限關鍵點
-
主要變化:
-
Android 14 繼續限制?
WRITE_EXTERNAL_STORAGE
?的使用 -
需要?
MANAGE_EXTERNAL_STORAGE
?權限才能管理所有文件 -
必須引導用戶到系統設置手動開啟"管理所有文件"權限
-
-
權限策略:
-
應用默認只能寫入自己的專屬目錄
-
寫入共享存儲需要相應權限
-
管理所有文件需要用戶顯式授權
-
-
最佳實踐:
-
優先使用 MediaStore API 來寫入共享媒體文件
-
使用 SAF (Storage Access Framework) 讓用戶選擇保存位置
-
只在絕對必要時請求管理所有文件權限
-
提供清晰的權限解釋和引導
-
-
兼容性處理:
-
為不同 Android 版本提供不同的權限請求策略
-
使用?
Environment.isExternalStorageManager()
?檢查管理權限 -
測試在各種情況下的權限行為
-
這套實現方案完全符合 Android 14 的寫權限要求,同時保持了良好的向后兼容性。