解决Android10版本以上外部存储权限的方案

项目场景:主要解决Android10版本以上的分区机制(socoped storage)

分区存储机制,又叫做沙盒存储机制,用于防止应用读取其他应用的数据。每个应用程序都应该有自己的存储空间。应用程序不能翻过自己的目录,去访问公共目录。因此,用用程序在请求数据时要通过权限检测,不符合要求的不会被放行。


问题描述

我在学习android过程中,由于我安装的是android11的版本,一次实践VideoView播放SD卡中存放的视频中发现了它不能简单地通过在AndroidManifest.xml文件中添加

<uses—permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

来实现对SD卡的外部存储实现数据读取。在我查找了许多博主的博客,发现好多都是老版本的方案,初学者表示很懵逼。最终功夫不负有心人,我最终在几个博主的博客里找到正确的解决方案。现把我的解决方案整理一下,供大家参考。


原因分析:

在发布android10的时候官方明确表态:

2020年,主要平台版本将要求所有应用都使用分区存储,无论应用的目标 sdk 级别是多少。因此,您应该提前确保您的应用能够使用分区存储。为此,请确保针对搭载 android 10(api 级别 29)及更高版本的设备启用了该行为。

以 android 10(api 级别 29)及更高版本为目标平台的应用在默认情况下被赋予了对外部存储设备的分区访问权限(即分区存储), 对外部存储文件访问方式重新设计,便于用户更好的管理外部存储文件。如果不符合条件的会以兼容模式运行,兼容模式跟以前一样,根据路径可以直接存储文件。

应用只能看到本应用专有的目录(通过 context.getexternalfilesdir() 访问)以及特定类型的媒体。除非您的应用需要访问存放在应用的专有目录以及 mediastore 之外的文件,否则最好使用分区存储。

具体分区存储权限的介绍

默认情况下,对于targetsdkversion大于等于29的应用,其访问权限范围限定为分区存储。此应用无需请求与存储相关的用户权限,即可以查看外部存储中以下类型的文件:

应用外部特定目录中的文件(使用getexternalfilesdir()访问)。

应用自己创建的照片、视频和音频(通过mediastore访问)。

分区存储将影响在android10系统首次安装启动、且targetsdkversion >=29的应用。需要访问和共享外部存储文件的应用会受到影响,需要进行兼容性适配。

影响范围:

在android 10上运行的应用:

1.targetsdkversion <= 28,不受影响

2.如果targetsdkversion >= 29,默认情况应用外部存储可见性将被过滤,应用需要对分区存储进行适配。

还有值得注意的是以下两种情况比较特殊,不会受到分区存储的影响:

如果应用最先安装在android 10以下的系统,

1) 然后系统通过fota升级到android 10

2) 应用通过更新升级到targetsdkversion >= 29


解决方案:

方法1:

通过安卓的第三方框架导入依赖:

【Android 中如何导入依赖包】https://mbd.baidu.com/ma/s/vdFk0yAe

buildscript {
        repositories {
            maven { url 'https://jitpack.io' }
        }
    }
    allprojects {
        repositories {
            maven { url 'https://jitpack.io' }
        }
    }
}
dependencies{

// 权限请求框架:https://github.com/getActivity/XXPermissions
implementation 'com.github.getActivity:XXPermissions:13.2'
}
 

XXPermissions.with(this)
        // 不适配 Android 11 可以这样写
        //.permission(Permission.Group.STORAGE)
        // 适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE
        .permission(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
        .request(new OnPermissionCallback() {

            @Override
            public void onGranted(List<String> permissions, boolean all) {
                if (all) {
                    toast("获取存储权限成功");
                }
            }

            @Override
            public void onDenied(List<String> permissions, boolean never) {
                if (never) {
                    toast("被永久拒绝授权,请手动授予存储权限");
                    // 如果是被永久拒绝就跳转到应用权限系统设置页面
                    XXPermissions.startPermissionActivity(MainActivity.this, permissions);
                } else {
                    toast("获取存储权限失败");
                }
            }
        });

 然后在进行对外部存储如SD卡的访问,就当笔者以为终于大功告成时,但在在进行调试时又报了个错误android10打开文件异常 open failed: EACCES (Permission denied) android:requestLegacyExternalStorage=“true“,查阅资料后,在AndroidManifest.xml文件中application节点中加上android:requestLegacyExternalStorage="true"属性就可以了,如下:

<application
    android:requestLegacyExternalStorage="true"

运行项目,即可访问到SD卡中/storage/emulated/0/video.mp4"(或/mnt/sdcard/video.mp4)中的视频。

方法2:申请外部存储权限适配

Android 6.0 以上——10.0以下的外部存储权限适配

Android 10.0 以下外部存储权限适配

  • 升级 targetSdkVersion
    android 
        defaultConfig {
            targetSdkVersion 23
        }
    }

  • 添加清单权限
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

  • 代码动态申请
    public final class PermissionActivity extends AppCompatActivity {
    
        private static final int REQUEST_CODE = 1024;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            requestPermission();
        }
    
        private void requestPermission() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                // 先判断有没有权限
                if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
                        ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                    writeFile();
                } else {
                    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
                }
            } else {
                writeFile();
            }
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            if (requestCode == REQUEST_CODE) {
                if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
                        ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                    writeFile();
                } else {
                    ToastUtils.show("存储权限获取失败");
                }
            }
        }
    
        /**
         * 模拟文件写入
         */
        private void writeFile() {
            ToastUtils.show("写入文件成功");
        }
    }

  • 需要注意的是,如果 targetSdkVersion >= 29 上,还需要在清单文件中加上
    <application
        android:requestLegacyExternalStorage="true">

    否则就算申请了存储权限,在安卓 10.0 的设备上将无法正常读写外部存储上的文件

  • Android 11 及以上申请外部存储权限

  • 升级 targetSdkVersion
  • android 
        defaultConfig {
            targetSdkVersion 30
        }
    }

 添加清单权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

 

  • 代码动态申请
public final class PermissionActivity extends AppCompatActivity {

    private static final int REQUEST_CODE = 1024;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestPermission();
    }

    private void requestPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            // 先判断有没有权限
            if (Environment.isExternalStorageManager()) {
                writeFile();
            } else {
                Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                intent.setData(Uri.parse("package:" + context.getPackageName()));
                startActivityForResult(intent, REQUEST_CODE);
            }
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // 先判断有没有权限
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
                    ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                writeFile();
            } else {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
            }
        } else {
            writeFile();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE) {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
                    ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                writeFile();
            } else {
                ToastUtils.show("存储权限获取失败");
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if (Environment.isExternalStorageManager()) {
                writeFile();
            } else {
                ToastUtils.show("存储权限获取失败");
            }
        }
    }

    /**
     * 模拟文件写入
     */
    private void writeFile() {
        ToastUtils.show("写入文件成功");
    }
}

调试项目后可以正常访问到外部存储中如SD卡中的视频文件了。

附上我的源码供大家参考:http://链接:https://pan.baidu.com/s/1AWlUsL0oZlZxzghqbAt0_w

 提取码:78c0

Android系统在访问外部存储设备时,需要获取相应的权限外部存储权限允许应用程序访问设备上的SD卡、USB存储器等外部存储设备,以便应用程序可以读取、写入和删除存储设备上的文件。 要在Android应用程序中请求外部存储权限,首先需要在AndroidManifest.xml文件中声明相应的权限: <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 在Android 6.0及以上版本中,还需要在运行时向用户请求外部存储权限。可以在应用程序中通过检查权限是否被授予来确定用户是否已经允许了外部存储权限。如果权限未被授予,可以通过以下步骤向用户请求权限: 1. 创建一个权限请求对话框,向用户解释为何需要外部存储权限。 2. 使用requestPermissions()方法向用户请求权限。 3. 在onRequestPermissionsResult()方法中处理权限请求的结果,根据用户的选择对应用程序进行响应。 一旦应用程序被授予了外部存储权限,就可以使用相关的API来访问外部存储设备上的文件。例如,可以使用File类或者DocumentFile类来读取、写入和删除文件。另外,还可以使用SAF(Storage Access Framework)来允许用户通过文件选择器来选择外部存储中的文件。 需要注意的是,在访问外部存储设备时,开发人员需要小心谨慎,以避免对用户数据造成破坏或泄露。所以在写入或删除文件时,应该确保用户已经授予相应的权限,并且要谨慎处理文件操作,以避免不必要的损失。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值