Android 14 、15动态申请读写权限实现 (Java)

在 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 存储权限关键点

  1. 新增权限:

    • READ_MEDIA_VISUAL_USER_SELECTED: 允许用户选择特定媒体文件

    • 其他媒体权限与 Android 13 相同

  2. 权限策略变化:

    • 完全移除了 READ_EXTERNAL_STORAGE 对媒体文件的控制

    • 应用默认只能访问自己创建的文件

    • 访问其他媒体文件必须请求权限

  3. 最佳实践:

    • 优先使用系统照片选择器 (Photo Picker)

    • 只在真正需要时才请求权限

    • 提供清晰的权限解释

    • 正确处理所有可能的拒绝情况

  4. 兼容性考虑:

    • 使用 Build.VERSION.SDK_INT 检查系统版本

    • 为不同版本提供不同的权限请求策略

    • 测试在各种场景下的权限行为

这套实现方案完全符合 Android 14 的存储权限要求,同时保持了良好的向后兼容性。

Android 14 写权限关键点

  1. 主要变化:

    • Android 14 继续限制 WRITE_EXTERNAL_STORAGE 的使用

    • 需要 MANAGE_EXTERNAL_STORAGE 权限才能管理所有文件

    • 必须引导用户到系统设置手动开启"管理所有文件"权限

  2. 权限策略:

    • 应用默认只能写入自己的专属目录

    • 写入共享存储需要相应权限

    • 管理所有文件需要用户显式授权

  3. 最佳实践:

    • 优先使用 MediaStore API 来写入共享媒体文件

    • 使用 SAF (Storage Access Framework) 让用户选择保存位置

    • 只在绝对必要时请求管理所有文件权限

    • 提供清晰的权限解释和引导

  4. 兼容性处理:

    • 为不同 Android 版本提供不同的权限请求策略

    • 使用 Environment.isExternalStorageManager() 检查管理权限

    • 测试在各种情况下的权限行为

这套实现方案完全符合 Android 14 的写权限要求,同时保持了良好的向后兼容性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/901325.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小刚说C语言刷题——第23讲 字符数组

前面&#xff0c;我们学习了一维数组和二维数组的概念。今天我们学习一种特殊的数组&#xff0c;字符数组。 1.字符数组的概念 字符数组就是指元素类型为字符的数组。字符数组是用来存放字符序列或者字符串的。 2.字符数组的定义及语法 char ch[5]; 3.字符数组的初始化及赋…

用AI生成系统架构图

DeepSeek+Drawio+SVG绘制架构图-找到一种真正可行实用的方法和思路 1、使用DeepSeek生成SVG文件,导入drawio工具的方法 🔥 问题根源分析 错误现象: • 导入时报错包含 data:image/SVG;base64 和 %20 等 URL 编码字符 • 代码被错误转换为 Base64 格式(适用于网页嵌入,但…

免费干净!付费软件的平替款!

今天给大家分享一款超棒的电脑录屏软件&#xff0c;简直不要太好用&#xff01;它的界面特别干净&#xff0c;没有一点儿广告&#xff0c;看起来特别清爽。 电脑录屏 无广告的录屏软件 这个软件超方便&#xff0c;根本不用安装&#xff0c;打开就能直接用。 它功能也很强大&am…

【XCP实战】AUTOSAR架构下XCP从0到1开发配置实践

目录 前言 正文 1.CAN功能开发 1.1 DBC的制作及导入 1.2 CanTrcv模块配置 1.3 Can Controller模块配置 1.4 CanIf模块配置 2.XCP模块集成配置配置 2.1.XCP模块配置 2.2.XCP模块的Task Mapping 2.3.XCP模块的初始化 3.在链接文件中定义标定段 4.编写标定相关的测试…

Vitis: 使用自定义IP时 Makefile错误 导致编译报错

参考文章: 【小梅哥FPGA】 Vitis开发中自定义IP的Makefile路径问题解决方案 Vitis IDE自定义IP Makefile错误&#xff08;arm-xilinx-eabi-gcc.exe: error: *.c: Invalid argument&#xff09;解决方法 Vitis 使用自定义IP时: Makefile 文件里的语句是需要修改的&#xff0c;…

Python中NumPy的统计运算

在数据分析和科学计算领域&#xff0c;Python凭借其丰富的库生态系统成为首选工具之一&#xff0c;而NumPy作为Python数值计算的核心库&#xff0c;凭借其高效的数组操作和强大的统计运算功能&#xff0c;广泛应用于机器学习、信号处理、统计分析等场景。本文将系统介绍NumPy在…

C语言程序环境和预处理详解

本章重点&#xff1a; 程序的翻译环境 程序的执行环境 详解&#xff1a;C语言程序的编译链接 预定义符号介绍 预处理指令 #define 宏和函数的对比 预处理操作符#和##的介绍 命令定义 预处理指令 #include 预处理指令 #undef 条件编译 程序的翻译环境和执行环…

智能工厂调度系统设计方案研究报告

一、系统架构设计 1.1 物理部署架构 设备层&#xff1a;部署大量搭载多传感器阵列的 AGV 智能循迹车&#xff0c;这些传感器包括激光雷达、视觉相机、超声波传感器等&#xff0c;用于感知周围环境信息&#xff0c;实现自主导航与避障功能&#xff1b;在每个工序节点处设置 RF…

全新突破 | 更全面 · 更安全 · 更灵活

xFile 高可用存储网关 2.0 重磅推出&#xff0c;新增多空间隔离功能从根源上防止数据冲突&#xff0c;保障各业务数据的安全性与独立性。同时支持 NFS、CIFS、FTP 等多种主流文件协议&#xff0c;无需繁琐的数据拷贝转换&#xff0c;即可与现有系统无缝对接&#xff0c;降低集成…

C# js 判断table中tr否存在相同的值

html 中如&#xff1a; 实现&#xff1a;table数据表格中&#xff0c;点击删除按钮时&#xff0c;验证相同子订单号条数是否大于1&#xff0c;大于允许删除。保证数据表格中只有唯一的一条子订单号数据。 <table style"width: 100%; background-color: #fff;" ce…

操作系统基础:07 我们的任务

课程回顾与后续规划 上节课我们探讨了操作系统的历史。了解历史能让我们明智&#xff0c;从操作系统的发展历程中&#xff0c;我们总结出两个核心的里程碑式图像&#xff1a;多进程&#xff08;多任务切换&#xff09;图像和文件操作图像 。Unix和Windows等系统的成功&#xf…

16.【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--微服务的部署与运维

部署与运维是微服务架构成功实施的关键环节。一个良好的部署与运维体系能够保障微服务的高可用性、可扩展性和可靠性。在这一阶段&#xff0c;重点包括微服务的容器化与编排、API 网关的实现以及日志与监控体系的建设。 一、容器化与编排 1.1 使用 Docker 容器化微服务 容器…

MCP基础学习计划详细总结

MCP基础学习计划详细总结 1.MCP概述与基础 • MCP&#xff08;Model Context Protocol&#xff09;&#xff1a;由Anthropic公司于2024年11月推出&#xff0c;旨在实现大型语言模型&#xff08;LLM&#xff09;与外部数据源和工具的无缝集成。 • 核心功能&#xff1a; • 资…

NoSQL入门指南:Redis与MongoDB的Java实战

一、为什么需要NoSQL&#xff1f; 在传统SQL数据库中&#xff0c;数据必须严格遵循预定义的表结构&#xff0c;就像把所有物品整齐摆放在固定尺寸的货架上。而NoSQL&#xff08;Not Only SQL&#xff09;数据库则像一个灵活的储物间&#xff0c;允许存储各种类型的数据&#x…

Java 列表初始化全解析:7种方式详解与最佳实践

文章目录 **引言****1. 传统逐个添加元素****特点****注意事项** **2. Arrays.asList() 构造函数****特点****注意事项** **3. 双括号初始化&#xff08;匿名内部类&#xff09;****特点****注意事项** **4. Java 9 List.of()&#xff08;不可变列表&#xff09;****特点****注…

最大公约数和最小倍数 java

在Java中&#xff0c;计算两个数的最大公约数&#xff08;Greatest Common Divisor, GCD&#xff09;和最小公倍数&#xff08;Least Common Multiple, LCM&#xff09;是常见的编程问题。以下是具体的实现方法和代码示例。 --- ### **1. 最大公约数 (GCD)** 最大公约数是指…

数据库——视图

一、视图的定义与核心特性 1.基本概念 (1)视图(View)是基于一个或多个底层表(或视图)的虚拟表,其本身不存储数据,仅保存查询语句的定义。当用户查询视图时,数据库会动态执行其封装的SQL语句,生成结果集。 (2)本质:视图是底层表的逻辑映射,结构与表相同(由行和列…

【Proteus仿真】【32单片机-A008】MPX4115压力检测系统设计

目录 一、主要功能 二、使用步骤 三、硬件资源 四、软件设计 五、实验现象 联系作者 一、主要功能 1、压力检测与LCD显示 2、超过上限&#xff0c;降压模块启动 3、压力检测范围15kpa-115kpa 4、压力阈值设置 5、超限报警 二、使用步骤 系统运行后&#xff0c;LCD160…

java和c#的相似及区别基础对比

用过十几种语言&#xff0c;但是java和c#是最为重要的两门。c#发明人曾主导开发了pascal和delphi&#xff0c;加入微软后&#xff0c;参考了c和java完成了c#和net。大家用过java或c#任意一种的&#xff0c;可以通过本篇文章快速掌握另外一门语言。 基础语法 变量声明&#xf…

OpenBayes 一周速览|1分钟生成完整音乐,DiffRhythm人声伴奏一键搞定; Stable Virtual Camera重塑3D视频创作

公共资源速递 5 个数据集&#xff1a; * 302 例罕见病病例数据集 * DRfold2 RNA 结构测试数据集 * NaturalReasoning 自然推理数据集 * VenusMutHub 蛋白质突变小样本数据集 * Bird Vs Drone 鸟类与无人机图像分类数据集 2 个模型&#xff1a; * Qwen2.5-0mni * Llama…