Android14?、15動(dòng)態(tài)申請(qǐng)讀寫權(quán)限實(shí)現(xiàn)方法示例代碼?(Java)
在 Android 14、15 中,Google 進(jìn)一步優(yōu)化了存儲(chǔ)權(quán)限系統(tǒng),特別是寫權(quán)限的管理。以下是完整的 Java 實(shí)現(xiàn)方案:
1. AndroidManifest.xml 聲明權(quán)限
<strong><!-- Android 14 存儲(chǔ)權(quán)限 --> <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" /> <!-- 新增的視覺(jué)媒體選擇權(quán)限 --> <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" /></strong> <!-- 兼容舊版本 (可選) --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- 寫入權(quán)限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- 僅用于兼容舊版本 -->
2. Java 權(quán)限請(qǐng)求實(shí)現(xiàn)
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 { // 多權(quán)限請(qǐng)求啟動(dòng)器 private final ActivityResultLauncher<String[]> requestPermissionsLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), this::handlePermissionResult); // 檢查并請(qǐng)求存儲(chǔ)權(quán)限 public void checkAndRequestStoragePermissions() { List<String> permissionsToRequest = new ArrayList<>(); // Android 14+ 需要的權(quán)限(讀、寫權(quán)限) 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(讀、寫權(quán)限) 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(讀、寫權(quán)限) 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])); } } // 處理權(quán)限請(qǐng)求結(jié)果 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); } } // 權(quán)限全部授予 private void onStoragePermissionGranted() { Toast.makeText(this, "存儲(chǔ)權(quán)限已授予", Toast.LENGTH_SHORT).show(); // 這里可以執(zhí)行需要權(quán)限的操作 } // 處理被拒絕的權(quán)限 private void handleDeniedPermissions(List<String> deniedPermissions) { for (String permission : deniedPermissions) { if (shouldShowRequestPermissionRationale(permission)) { showRationaleDialog(permission); } else { showGoToSettingsDialog(permission); } } } // 顯示權(quán)限解釋對(duì)話框 private void showRationaleDialog(String permission) { new AlertDialog.Builder(this) .setTitle("需要權(quán)限") .setMessage(getPermissionMessage(permission)) .setPositiveButton("確定", (dialog, which) -> checkAndRequestStoragePermissions()) .setNegativeButton("取消", null) .show(); } // 顯示前往設(shè)置對(duì)話框 private void showGoToSettingsDialog(String permission) { new AlertDialog.Builder(this) .setTitle("權(quán)限被永久拒絕") .setMessage("請(qǐng)?jiān)趹?yīng)用設(shè)置中手動(dòng)授予" + getPermissionName(permission) + "權(quán)限") .setPositiveButton("去設(shè)置", (dialog, which) -> { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.fromParts("package", getPackageName(), null)); startActivity(intent); }) .setNegativeButton("取消", null) .show(); } // 獲取權(quán)限說(shuō)明信息 private String getPermissionMessage(String permission) { switch (permission) { case Manifest.permission.READ_MEDIA_IMAGES: return "需要訪問(wèn)您的照片以提供完整功能"; case Manifest.permission.READ_MEDIA_VIDEO: return "需要訪問(wèn)您的視頻以提供完整功能"; case Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED: return "需要訪問(wèn)您選擇的媒體文件"; case Manifest.permission.READ_EXTERNAL_STORAGE: return "需要訪問(wèn)您的文件以提供完整功能"; default: return "需要此權(quán)限以提供完整功能"; } } // 獲取權(quán)限名稱 private String getPermissionName(String permission) { switch (permission) { case Manifest.permission.READ_MEDIA_IMAGES: return "照片訪問(wèn)"; case Manifest.permission.READ_MEDIA_VIDEO: return "視頻訪問(wèn)"; case Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED: return "選擇的媒體文件訪問(wèn)"; case Manifest.permission.READ_EXTERNAL_STORAGE: return "文件訪問(wèn)"; default: return "存儲(chǔ)"; } } }
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(); // 從設(shè)置返回后檢查權(quán)限狀態(tài) 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 存儲(chǔ)權(quán)限關(guān)鍵點(diǎn)
新增權(quán)限:
READ_MEDIA_VISUAL_USER_SELECTED
: 允許用戶選擇特定媒體文件其他媒體權(quán)限與 Android 13 相同
權(quán)限策略變化:
完全移除了
READ_EXTERNAL_STORAGE
對(duì)媒體文件的控制應(yīng)用默認(rèn)只能訪問(wèn)自己創(chuàng)建的文件
訪問(wèn)其他媒體文件必須請(qǐng)求權(quán)限
最佳實(shí)踐:
優(yōu)先使用系統(tǒng)照片選擇器 (Photo Picker)
只在真正需要時(shí)才請(qǐng)求權(quán)限
提供清晰的權(quán)限解釋
正確處理所有可能的拒絕情況
兼容性考慮:
使用
Build.VERSION.SDK_INT
檢查系統(tǒng)版本為不同版本提供不同的權(quán)限請(qǐng)求策略
測(cè)試在各種場(chǎng)景下的權(quán)限行為
這套實(shí)現(xiàn)方案完全符合 Android 14 的存儲(chǔ)權(quán)限要求,同時(shí)保持了良好的向后兼容性。
Android 14 寫權(quán)限關(guān)鍵點(diǎn)
主要變化:
Android 14 繼續(xù)限制
WRITE_EXTERNAL_STORAGE
的使用需要
MANAGE_EXTERNAL_STORAGE
權(quán)限才能管理所有文件必須引導(dǎo)用戶到系統(tǒng)設(shè)置手動(dòng)開啟"管理所有文件"權(quán)限
權(quán)限策略:
應(yīng)用默認(rèn)只能寫入自己的專屬目錄
寫入共享存儲(chǔ)需要相應(yīng)權(quán)限
管理所有文件需要用戶顯式授權(quán)
最佳實(shí)踐:
優(yōu)先使用 MediaStore API 來(lái)寫入共享媒體文件
使用 SAF (Storage Access Framework) 讓用戶選擇保存位置
只在絕對(duì)必要時(shí)請(qǐng)求管理所有文件權(quán)限
提供清晰的權(quán)限解釋和引導(dǎo)
兼容性處理:
為不同 Android 版本提供不同的權(quán)限請(qǐng)求策略
使用
Environment.isExternalStorageManager()
檢查管理權(quán)限測(cè)試在各種情況下的權(quán)限行為
這套實(shí)現(xiàn)方案完全符合 Android 14 的寫權(quán)限要求,同時(shí)保持了良好的向后兼容性。
總結(jié)
到此這篇關(guān)于Android14 、15動(dòng)態(tài)申請(qǐng)讀寫權(quán)限實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)Android動(dòng)態(tài)申請(qǐng)讀寫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Android6.0動(dòng)態(tài)申請(qǐng)權(quán)限所遇到的問(wèn)題小結(jié)
- Android中不支持動(dòng)態(tài)申請(qǐng)權(quán)限的原因
- Android registerForActivityResult動(dòng)態(tài)申請(qǐng)權(quán)限案例詳解
- Android 如何實(shí)現(xiàn)動(dòng)態(tài)申請(qǐng)權(quán)限
- android6.0權(quán)限動(dòng)態(tài)申請(qǐng)框架permissiondispatcher的方法
- android12?SD如何動(dòng)態(tài)申請(qǐng)讀寫權(quán)限
相關(guān)文章
SpringBoot實(shí)現(xiàn)短信驗(yàn)證碼登錄功能(案例)
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)短信驗(yàn)證碼登錄功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-08-08Flutter實(shí)現(xiàn)底部導(dǎo)航欄效果
這篇文章主要為大家詳細(xì)介紹了Flutter實(shí)現(xiàn)底部導(dǎo)航欄效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08Android實(shí)現(xiàn)ListView分頁(yè)加載數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)ListView分頁(yè)加載數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11Android checkbox的listView(多選,全選,反選)具體實(shí)現(xiàn)方法
由于listview的一些特性,剛開始寫這種需求的功能的時(shí)候都會(huì)碰到一些問(wèn)題,重點(diǎn)就是存儲(chǔ)每個(gè)checkbox的狀態(tài)值,在這里分享出了完美解決方法:2013-06-06詳解Android中Fragment的兩種創(chuàng)建方式
本篇文章主要介紹了Android中Fragment的兩種創(chuàng)建方式,具有一定的參考價(jià)值,有興趣的可以了解一下。2016-12-12Android客制化adb shell進(jìn)去后顯示shell@xxx的標(biāo)識(shí)
今天小編就為大家分享一篇關(guān)于Android客制化adb shell進(jìn)去后顯示shell@xxx的標(biāo)識(shí),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12android開發(fā)權(quán)限詢問(wèn)的示例代碼
這篇文章主要介紹了android開發(fā)權(quán)限詢問(wèn)的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01