Android14?、15動態(tài)申請讀寫權(quán)限實(shí)現(xiàn)方法示例代碼?(Java)
在 Android 14、15 中,Google 進(jìn)一步優(yōu)化了存儲權(quán)限系統(tǒng),特別是寫權(quán)限的管理。以下是完整的 Java 實(shí)現(xiàn)方案:
1. AndroidManifest.xml 聲明權(quán)限
<strong><!-- Android 14 存儲權(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" />
<!-- 新增的視覺媒體選擇權(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)限請求實(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)限請求啟動器
private final ActivityResultLauncher<String[]> requestPermissionsLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(),
this::handlePermissionResult);
// 檢查并請求存儲權(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)限請求結(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, "存儲權(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)限解釋對話框
private void showRationaleDialog(String permission) {
new AlertDialog.Builder(this)
.setTitle("需要權(quán)限")
.setMessage(getPermissionMessage(permission))
.setPositiveButton("確定", (dialog, which) ->
checkAndRequestStoragePermissions())
.setNegativeButton("取消", null)
.show();
}
// 顯示前往設(shè)置對話框
private void showGoToSettingsDialog(String permission) {
new AlertDialog.Builder(this)
.setTitle("權(quán)限被永久拒絕")
.setMessage("請在應(yīng)用設(shè)置中手動授予" + 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)限說明信息
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 "需要此權(quán)限以提供完整功能";
}
}
// 獲取權(quán)限名稱
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();
// 從設(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 存儲權(quán)限關(guān)鍵點(diǎn)
新增權(quán)限:
READ_MEDIA_VISUAL_USER_SELECTED: 允許用戶選擇特定媒體文件其他媒體權(quán)限與 Android 13 相同
權(quán)限策略變化:
完全移除了
READ_EXTERNAL_STORAGE對媒體文件的控制應(yīng)用默認(rèn)只能訪問自己創(chuàng)建的文件
訪問其他媒體文件必須請求權(quán)限
最佳實(shí)踐:
優(yōu)先使用系統(tǒng)照片選擇器 (Photo Picker)
只在真正需要時才請求權(quán)限
提供清晰的權(quán)限解釋
正確處理所有可能的拒絕情況
兼容性考慮:
使用
Build.VERSION.SDK_INT檢查系統(tǒng)版本為不同版本提供不同的權(quán)限請求策略
測試在各種場景下的權(quán)限行為
這套實(shí)現(xiàn)方案完全符合 Android 14 的存儲權(quán)限要求,同時保持了良好的向后兼容性。
Android 14 寫權(quán)限關(guān)鍵點(diǎn)
主要變化:
Android 14 繼續(xù)限制
WRITE_EXTERNAL_STORAGE的使用需要
MANAGE_EXTERNAL_STORAGE權(quán)限才能管理所有文件必須引導(dǎo)用戶到系統(tǒng)設(shè)置手動開啟"管理所有文件"權(quán)限
權(quán)限策略:
應(yīng)用默認(rèn)只能寫入自己的專屬目錄
寫入共享存儲需要相應(yīng)權(quán)限
管理所有文件需要用戶顯式授權(quán)
最佳實(shí)踐:
優(yōu)先使用 MediaStore API 來寫入共享媒體文件
使用 SAF (Storage Access Framework) 讓用戶選擇保存位置
只在絕對必要時請求管理所有文件權(quán)限
提供清晰的權(quán)限解釋和引導(dǎo)
兼容性處理:
為不同 Android 版本提供不同的權(quán)限請求策略
使用
Environment.isExternalStorageManager()檢查管理權(quán)限測試在各種情況下的權(quán)限行為
這套實(shí)現(xiàn)方案完全符合 Android 14 的寫權(quán)限要求,同時保持了良好的向后兼容性。
總結(jié)
到此這篇關(guān)于Android14 、15動態(tài)申請讀寫權(quán)限實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)Android動態(tài)申請讀寫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Flutter?點(diǎn)擊兩次退出app的實(shí)現(xiàn)示例
本文主要介紹了Flutter?點(diǎn)擊兩次退出app的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
Android 6.0 藍(lán)牙搜索不到設(shè)備原因,MIUI權(quán)限申請機(jī)制方法
今天小編就為大家分享一篇Android6.0 藍(lán)牙搜索不到設(shè)備原因,MIUI權(quán)限申請機(jī)制方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07
Android ContentProvider實(shí)現(xiàn)手機(jī)聯(lián)系人讀取和插入
這篇文章主要為大家詳細(xì)介紹了Android ContentProvider實(shí)現(xiàn)手機(jī)聯(lián)系人讀取和插入,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-05-05
淺談關(guān)于Android路由的實(shí)現(xiàn)
本篇文章主要介紹了淺談關(guān)于Android路由的實(shí)現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10
Android開發(fā)者常見的UI組件總結(jié)大全
Android開發(fā)中UI組件是構(gòu)建用戶界面的基本元素,下面這篇文章主要給大家介紹了關(guān)于Android開發(fā)者常見的UI組件總結(jié)的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04
Android開發(fā)之滑動數(shù)值選擇器NumberPicker用法示例
這篇文章主要介紹了Android開發(fā)之滑動數(shù)值選擇器NumberPicker用法,結(jié)合實(shí)例形式分析了Android滑動數(shù)值選擇器NumberPicker的功能、相關(guān)函數(shù)、事件監(jiān)聽、界面布局等操作技巧,需要的朋友可以參考下2019-03-03
Android 微信小視頻錄制功能實(shí)現(xiàn)詳細(xì)介紹
這篇文章主要介紹了Android 微信小視頻錄制功能實(shí)現(xiàn)詳解的相關(guān)資料,這里提供了具體的實(shí)現(xiàn)思路及代碼,需要的朋友可以參考下2016-11-11

