• Android 11及以上授予文件管理权限


    背景

    安卓11改变了此前安卓系统对于文件管理的规则,在安卓11上,文件读写变成了特殊权限。应用默认只能读写自己的目录/android/data/包名

    gradle配置

    Android11系统对应用写入权限做了严格的限制。本文介绍如何获取文件读写权限。

    项目中 build.gradle 的targetSdkVersion >= 29 ,会出现读写问题

    • 当targetSdkVersion = 29,通过设置requestLegacyExternalStorage=“true”,还能解决
    • 当targetSdkVersion = 30后,需要申请所有文件权限才能获取到写入权限

    为了能直接usb安装,gradle.properties 需要设置(否则,在安装时会报异常:-15)

    android.injected.testOnly=false
    
    • 1

    AndroidManifest添加权限设置

    
    
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    语法说明

    申请权限,主要用到如下4个函数

    1. 检查权限
    int checkSelfPermission(String)
    
    • 1
    1. 申请权限
    void requestPermissions(int, String...)
    
    • 1
    1. 是否应该显示请求权限的说明
    boolean shouldShowRequestPermissionRationale(String)
    
    • 1
    1. 处理权限结果回调
    void onRequestPermissionsResult(int,String[],int[])
    
    • 1

    上述四个方法中,前三个方法在support-v4的ActivityCompat中都有,建议使用兼容库中的方法。最后一个方法是用户授权或者拒绝某个权限组时系统会回调Activity或者Fragment中的方法。

    checkSelfPermission的返回值有如下两种

    1. 已拒绝 PackageManager.PERMISSION_DENIED
    2. 已授权 PackageManager.PERMISSION_GRANTED

    动态权限申请

    Android 6.0之上Android11以下申请权限

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0
    	String[] perms = {
    			Manifest.permission.READ_EXTERNAL_STORAGE,
    			Manifest.permission.WRITE_EXTERNAL_STORAGE,
    			Manifest.permission.READ_PHONE_STATE};
    	for (String p : perms) {
    		int ret = ContextCompat.checkSelfPermission(activity, p);
    		if (ret != PackageManager.PERMISSION_GRANTED) {
    			//TODO 跳转到权限页,请求权限
    			return;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Android11申请权限

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        //判断是否有管理外部存储的权限
    	if (!Environment.isExternalStorageManager()) {
    		//TODO 跳转到权限页,请求权限
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    跳转系统授权页面

    跳转到“应用信息”页面

    安卓默认只能跳转到 "应用信息"页面,但是国内手机厂商大多支持各自自定义的Intent,直接跳到应用程序权限页面

    /**
     * 当前应用详情页面(在该页面单击权限,进入的是权限组页面)
     */
    public static void goAppDetailsSettings(Context context) {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(Uri.fromParts("package", context.getPackageName(), null));
        context.startActivity(intent);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    跳转到应用信息页面,页面包含如下内容

    1. 存储
    2. 电量
    3. 流量
    4. 通知
    5. 权限
    6. 默认打开

    需要手动点击“权限”按钮,进入“应用权限”页面,然后开始设置相应的数据

    在这里插入图片描述
    在这里插入图片描述

    跳转到“所有文件访问权限”

    谷歌官方的安卓11在“应用权限”页面隐藏了对“存储”权限的设置(当然,很多国产机,即使到了安卓12,依旧支持原先的存储权限;但也存在部分不支持的,比如:OPPO Reno9 Pro+)

    取而代之的是“所有文件访问权限”页面,该页面用来授予“所有文件的管理权限”,允许此应用读取、修改和删除此设备或任意已连接存储卷上的所有文件。如果您授予该权限,应用无需明确通知您即可访问文件

    /**
     * 进入Android 11或更高版本的文件访问权限页面
     */
    private void goManagerFileAccess(AppCompatActivity activity) {
        // Android 11 (Api 30)或更高版本的写文件权限需要特殊申请,需要动态申请管理所有文件的权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            Intent appIntent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
            appIntent.setData(Uri.parse("package:" + getPackageName()));
            //appIntent.setData(Uri.fromParts("package", activity.getPackageName(), null));
            try {
                activity.startActivity(appIntent);
            } catch (ActivityNotFoundException ex) {
                ex.printStackTrace();
                Intent allFileIntent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                activity.startActivity(allFileIntent);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这里面涉及到两个Settings

    1. 当前应用的文件访问权限:ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
    2. 所有需要文件访问权限的应用:ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION

    切记,不要在安卓11以下调用该Intent,会导致闪退

    在这里插入图片描述

    测试存储权限是否授权

    安卓11及以后,有如下几个函数判断是否赋予存储权限

    1. 老版读权限
    ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
    
    • 1
    1. 老版写权限
    ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
    
    • 1
    1. 安卓11新出的管理外置存储权限
    ActivityCompat.checkSelfPermission(context, Manifest.permission.MANAGE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
    
    • 1
    1. 安卓11新出的判断是否是外置存储管理器
    Environment.isExternalStorageManager()
    
    • 1

    具体实验(VIVO Y77,安卓12)

    1. 初始化状态
    isExternalStorageManager => false
    READ_EXTERNAL_STORAGE   => false
    READ_EXTERNAL_STORAGE   => false
    MANAGE_EXTERNAL_STORAGE  => false
    
    • 1
    • 2
    • 3
    • 4
    1. 仅在“应用权限”页面,打开存储权限中的“允许管理所有文件”
    isExternalStorageManager => true
    READ_EXTERNAL_STORAGE   => true
    WRITE_EXTERNAL_STORAGE   => true
    MANAGE_EXTERNAL_STORAGE  => false
    
    • 1
    • 2
    • 3
    • 4
    1. 仅在“应用权限”页面,打开存储权限中的“仅允许访问媒体文件”
    isExternalStorageManager => false
    READ_EXTERNAL_STORAGE   => true
    WRITE_EXTERNAL_STORAGE   => true
    MANAGE_EXTERNAL_STORAGE  => false
    
    • 1
    • 2
    • 3
    • 4
    1. 仅在“所有文件访问权限”页面,授予权限
    isExternalStorageManager => true
    READ_EXTERNAL_STORAGE   => false
    WRITE_EXTERNAL_STORAGE   => false
    MANAGE_EXTERNAL_STORAGE  => false
    
    • 1
    • 2
    • 3
    • 4

    因此,可以得出如下结论

    1. 不要尝试判断 MANAGE_EXTERNAL_STORAGE 权限,因为其永远为false
    2. isExternalStorageManagerACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSIONACTION_MANAGE_ALL_FILES_ACCESS_PERMISSIONAPI,只能在安卓11及以上版本调用(低版本调用会闪退)
    3. “应用权限”页面,打开存储权限中的“允许管理所有文件”会使得isExternalStorageManager变为true;但反之在“所有文件访问权限”页面授予权限,并不会改变WRITE_EXTERNAL_STORAGE的允许情况

    因此,为了兼容 安卓4.4-安卓12,在11以下,建议使用checkSelfPermission方法进行存储权限判断;而安卓11及以上使用Environment.isExternalStorageManager即可

  • 相关阅读:
    ICML2021 | RSD: 一种基于几何距离的可迁移回归表征学习方法
    一、高频题集
    Win11如何添加默认打印机?
    Node的web编程(三)
    springboot毕设项目大学生请假系统 fq91k(java+VUE+Mybatis+Maven+Mysql)
    机器视觉软件破解的背后是道高一尺,魔高一丈
    elasticsearch入门
    ​创业15年,50岁回到农村过上退休的生活,上班和创业是两难的选择。
    搭建自己的OCR服务,第一步:选择合适的开源OCR项目
    「面试逆袭」MySQL六十六问大汇总!
  • 原文地址:https://blog.csdn.net/chy555chy/article/details/128204682