当前位置: 首页 > news >正文

Android 11 上的文件读写权限(MANAGE_EXTERNAL_STORAGE)

平台

     Android11 + RK3566 + AndroidStudio

Android 权限的变化, 几乎每个版本的SDK都会有, 其中最大的一次是在6.0时, 增加的动态权限申请
读写存储的权限也几经更迭, 对开发人员来说, 越来越难.比如, 本文所要讨论的:允许管理所有文件在这里插入图片描述
在这里插入图片描述

如何出现上面两种不同的文件权限选项?

  1. 首先是 targetSdkVersion 大于等于 30. (build.gradle)
    在这里插入图片描述
  2. 当声明了 READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE
    仅允许访问媒体文件
  3. 当声明了 MANAGE_EXTERNAL_STORAGE 会增加允许管理所有文件
  4. targetSdkVersion <= 28 时, 只有允许管理所有文件和 拒绝 选项.
    在这里插入图片描述

编写测试代码执行以下动作:

  1. 申请权限
  2. 获取内部存储下的1.txt文件
  3. 若文件存在, 删除并输出结果
  4. 尝试写入文件

读写失败:

2022-09-03 07:25:11.067 1262-10770/com.android.providers.media.module E/MediaProvider: insertFileIfNecessary failed
    java.lang.IllegalArgumentException: Primary directory null not allowed for content://media/external_primary/file; allowed directories are [Download, Documents]
        at com.android.providers.media.MediaProvider.ensureFileColumns(MediaProvider.java:2923)
        at com.android.providers.media.MediaProvider.ensureUniqueFileColumns(MediaProvider.java:2588)
        at com.android.providers.media.MediaProvider.insertFile(MediaProvider.java:3282)
        at com.android.providers.media.MediaProvider.insertInternal(MediaProvider.java:3826)
        at com.android.providers.media.MediaProvider.insert(MediaProvider.java:3537)
        at com.android.providers.media.MediaProvider.insertFileForFuse(MediaProvider.java:7187)
        at com.android.providers.media.MediaProvider.insertFileIfNecessaryForFuse(MediaProvider.java:7281)
2022-09-03 07:25:11.068 10710-10710/com.android.apitester W/System.err: java.io.FileNotFoundException: /storage/emulated/0/1.txt: open failed: EPERM (Operation not permitted)
2022-09-03 07:25:11.068 10710-10710/com.android.apitester W/System.err:     at libcore.io.IoBridge.open(IoBridge.java:492)
2022-09-03 07:25:11.068 10710-10710/com.android.apitester W/System.err:     at java.io.FileOutputStream.<init>(FileOutputStream.java:236)
2022-09-03 07:25:11.068 10710-10710/com.android.apitester W/System.err:     at java.io.FileOutputStream.<init>(FileOutputStream.java:186)
2022-09-03 07:25:11.069 10710-10710/com.android.apitester W/System.err:     at com.android.apitester.PermissionTest.fileOperation(PermissionTest.java:124)
2022-09-03 07:25:11.069 10710-10710/com.android.apitester W/System.err:     at com.android.apitester.PermissionTest.onClick(PermissionTest.java:50)
2022-09-03 07:25:11.069 10710-10710/com.android.apitester W/System.err:     at android.view.View.performClick(View.java:7448)
2022-09-03 07:25:11.069 10710-10710/com.android.apitester W/System.err:     at android.view.View.performClickInternal(View.java:7425)
2022-09-03 07:25:11.069 10710-10710/com.android.apitester W/System.err:     at android.view.View.access$3600(View.java:810)
2022-09-03 07:25:11.070 10710-10710/com.android.apitester W/System.err:     at android.view.View$PerformClick.run(View.java:28310)
2022-09-03 07:25:11.070 10710-10710/com.android.apitester W/System.err:     at android.os.Handler.handleCallback(Handler.java:938)
2022-09-03 07:25:11.070 10710-10710/com.android.apitester W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:99)
2022-09-03 07:25:11.070 10710-10710/com.android.apitester W/System.err:     at android.os.Looper.loop(Looper.java:223)
2022-09-03 07:25:11.070 10710-10710/com.android.apitester W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:7664)
2022-09-03 07:25:11.070 10710-10710/com.android.apitester W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
2022-09-03 07:25:11.071 10710-10710/com.android.apitester W/System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
2022-09-03 07:25:11.071 10710-10710/com.android.apitester W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
2022-09-03 07:25:11.071 10710-10710/com.android.apitester W/System.err: Caused by: android.system.ErrnoException: open failed: EPERM (Operation not permitted)
2022-09-03 07:25:11.071 10710-10710/com.android.apitester W/System.err:     at libcore.io.Linux.open(Native Method)
2022-09-03 07:25:11.072 10710-10710/com.android.apitester W/System.err:     at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
2022-09-03 07:25:11.072 10710-10710/com.android.apitester W/System.err:     at libcore.io.BlockGuardOs.open(BlockGuardOs.java:254)
2022-09-03 07:25:11.072 10710-10710/com.android.apitester W/System.err:     at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
2022-09-03 07:25:11.072 10710-10710/com.android.apitester W/System.err:     at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7550)
2022-09-03 07:25:11.072 10710-10710/com.android.apitester W/System.err:     at libcore.io.IoBridge.open(IoBridge.java:478)
2022-09-03 07:25:11.072 10710-10710/com.android.apitester W/System.err: 	... 15 more
2022-09-03 07:25:11.073 10710-10710/com.android.apitester E/PermissionTest: write /storage/emulated/0/1.txt failed
2022-09-03 07:25:11.094 1262-1367/com.android.providers.media.module I/MediaProvider: Deleted 1 items on external_primary due to com.android.apitester
2022-09-03 07:25:11.097 10710-10710/com.android.apitester D/PermissionTest: delete /storage/emulated/0/Download/1.txt success
2022-09-03 07:25:11.124 10710-10710/com.android.apitester D/PermissionTest: write /storage/emulated/0/Download/1.txt success
2022-09-03 07:25:11.131 10710-10710/com.android.apitester D/PermissionTest: delete /storage/emulated/0/Android/data/com.android.apitester/files/Documents/1.txt success
2022-09-03 07:25:11.137 10710-10710/com.android.apitester D/PermissionTest: write /storage/emulated/0/Android/data/com.android.apitester/files/Documents/1.txt success

结果(FAILED:失败, SUCCESS成功):

DIR仅允许访问媒体文件允许管理所有文件
/storage/emulated/0FAILEDSUCCESS
/storage/emulated/0/DownloadFAILEDSUCCESS
/storage/emulated/0/Android/data/com.android.apitester/files/DocumentsSUCCESSSUCCESS

源码中权限窗口

packages/apps/PermissionController/

START u0 {act=android.intent.action.MANAGE_APP_PERMISSIONS cmp=com.android.permissioncontroller/.permission.ui.ManagePermissionsActivity (has extras)} from uid 1000

布局文件

packages/apps/PermissionController/res/navigation/nav_graph.xml
packages/apps/PermissionController/res/layout/app_permission.xml
相关源码
packages/apps/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
packages/apps/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java

请求权限的交互

ManagePermissionsActivity AppPermissionFragment AppPermissionViewModel KotlinUtils PackageManagerService View requestChange grantRuntimePermission grantRuntimePermission ManagePermissionsActivity AppPermissionFragment AppPermissionViewModel KotlinUtils PackageManagerService

UI显示内容的判定

加载应用存储权限
packages/apps/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt

    data class FullStoragePackageState(
        val packageName: String,
        val user: UserHandle,
        val isLegacy: Boolean,
        val isGranted: Boolean
    )
    override suspend fun loadDataAndPostValue(job: Job) {
        val storagePackages = standardPermGroupsPackagesLiveData.value?.get(STORAGE) ?: return
        val appOpsManager = app.getSystemService(AppOpsManager::class.java) ?: return

        val fullStoragePackages = mutableListOf<FullStoragePackageState>()
        for ((user, packageInfoList) in AllPackageInfosLiveData.value ?: emptyMap()) {
            val userPackages = packageInfoList.filter {
                storagePackages.contains(it.packageName to user) ||
                    it.requestedPermissions.contains(MANAGE_EXTERNAL_STORAGE)
            }

            for (packageInfo in userPackages) {
                val sdk = packageInfo.targetSdkVersion
                if (sdk < Build.VERSION_CODES.P) {//targetSdkVersion 28
                    fullStoragePackages.add(FullStoragePackageState(packageInfo.packageName, user,
                        isLegacy = true, isGranted = true))
                    continue
                } else if (sdk <= Build.VERSION_CODES.Q &&//targetSdkVersion 29
                    appOpsManager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, packageInfo.uid,
                        packageInfo.packageName) == MODE_ALLOWED) {
                    fullStoragePackages.add(FullStoragePackageState(packageInfo.packageName, user,
                        isLegacy = true, isGranted = true))
                    continue
                }
                //存在MANAGE_EXTERNAL_STORAGE
                if (MANAGE_EXTERNAL_STORAGE in packageInfo.requestedPermissions) {
                    val mode = appOpsManager.unsafeCheckOpNoThrow(OPSTR_MANAGE_EXTERNAL_STORAGE,
                        packageInfo.uid, packageInfo.packageName)
                    val granted = mode == MODE_ALLOWED || mode == MODE_FOREGROUND ||
                        (mode == MODE_DEFAULT &&
                            MANAGE_EXTERNAL_STORAGE in packageInfo.grantedPermissions)
                    fullStoragePackages.add(FullStoragePackageState(packageInfo.packageName, user,
                        isLegacy = false, isGranted = granted))
                }
            }
        }

        postValue(fullStoragePackages)
    }

isLegacy表示是否是旧的权限模式, UI会根据上面的代码进行逻辑运算并更新对应的UI信息.

packages/apps/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt

        override fun onUpdate() {
            val group = appPermGroupLiveData.value ?: return

            val admin = RestrictedLockUtils.getProfileOrDeviceOwner(app, user)

            val couldPackageHaveFgCapabilities =
                    foregroundCapableType != Utils.ForegroundCapableType.NONE

            val allowedState = ButtonState()
            val allowedAlwaysState = ButtonState()
            val allowedForegroundState = ButtonState()
            val askOneTimeState = ButtonState()
            val askState = ButtonState()
            val deniedState = ButtonState()
            val deniedForegroundState = ButtonState() // when bg is fixed as granted and fg is flex

            askState.isShown = Utils.supportsOneTimeGrant(permGroupName) &&
                    !(group.foreground.isGranted && group.isOneTime)
            deniedState.isShown = true

            if (group.hasPermWithBackgroundMode) {
                // Background / Foreground / Deny case
                allowedForegroundState.isShown = true
                if (group.hasBackgroundGroup) {
                    allowedAlwaysState.isShown = true
                }

                allowedAlwaysState.isChecked = group.background.isGranted &&
                    group.foreground.isGranted
                allowedForegroundState.isChecked = group.foreground.isGranted &&
                    !group.background.isGranted && !group.isOneTime
                askState.isChecked = !group.foreground.isGranted && group.isOneTime
                askOneTimeState.isChecked = group.foreground.isGranted && group.isOneTime
                askOneTimeState.isShown = askOneTimeState.isChecked
                deniedState.isChecked = !group.foreground.isGranted && !group.isOneTime
                var detailId = 0
                if (applyFixToForegroundBackground(group, group.foreground.isSystemFixed,
                        group.background.isSystemFixed, allowedAlwaysState,
                        allowedForegroundState, askState, deniedState,
                        deniedForegroundState) ||
                    applyFixToForegroundBackground(group, group.foreground.isPolicyFixed,
                        group.background.isPolicyFixed, allowedAlwaysState,
                        allowedForegroundState, askState, deniedState,
                        deniedForegroundState)) {
                    showAdminSupportLiveData.value = admin
                    detailId = getDetailResIdForFixedByPolicyPermissionGroup(group,
                        admin != null)
                    if (detailId != 0) {
                        detailResIdLiveData.value = detailId to null
                    }
                } else if (Utils.areGroupPermissionsIndividuallyControlled(app, permGroupName)) {
                    val detailPair = getIndividualPermissionDetailResId(group)
                    detailId = detailPair.first
                    detailResIdLiveData.value = detailId to detailPair.second
                }
                if (couldPackageHaveFgCapabilities) {
                    // Correct the UI in case the app can access bg location with only fg perm
                    allowedAlwaysState.isShown = true
                    allowedAlwaysState.isChecked =
                            allowedAlwaysState.isChecked || allowedForegroundState.isChecked
                    // Should be enabled && is denied enabled for the user to be able to switch to.
                    allowedAlwaysState.isEnabled =
                            ((allowedAlwaysState.isEnabled && allowedAlwaysState.isShown) ||
                                    allowedForegroundState.isEnabled) &&
                                    ((deniedState.isEnabled && deniedState.isShown) ||
                                            (deniedForegroundState.isEnabled &&
                                                    deniedForegroundState.isShown))
                    allowedForegroundState.isChecked = false
                    allowedForegroundState.isEnabled = false
                    deniedState.isChecked = deniedState.isChecked || askState.isChecked
                    deniedForegroundState.isChecked = deniedState.isChecked
                    askState.isEnabled = false

                    if (detailId == 0) {
                        detailId = getForegroundCapableDetailResId(foregroundCapableType)
                        if (detailId != 0) {
                            detailResIdLiveData.value = detailId to null
                        }
                    }
                }
            } else {
                // Allow / Deny case
                allowedState.isShown = true

                allowedState.isChecked = group.foreground.isGranted
                askState.isChecked = !group.foreground.isGranted && group.isOneTime
                askOneTimeState.isChecked = group.foreground.isGranted && group.isOneTime
                askOneTimeState.isShown = askOneTimeState.isChecked
                deniedState.isChecked = !group.foreground.isGranted && !group.isOneTime

                var detailId = 0
                if (group.foreground.isPolicyFixed || group.foreground.isSystemFixed) {
                    allowedState.isEnabled = false
                    askState.isEnabled = false
                    deniedState.isEnabled = false
                    showAdminSupportLiveData.value = admin
                    val detailId = getDetailResIdForFixedByPolicyPermissionGroup(group,
                        admin != null)
                    if (detailId != 0) {
                        detailResIdLiveData.value = detailId to null
                    }
                }
                if (isForegroundGroupSpecialCase(permGroupName)) {
                    allowedForegroundState.isShown = true
                    allowedState.isShown = false
                    allowedForegroundState.isChecked = allowedState.isChecked
                    allowedForegroundState.isEnabled = allowedState.isEnabled
                    if (couldPackageHaveFgCapabilities || (Utils.isEmergencyApp(app, packageName) &&
                                    isMicrophone(permGroupName))) {
                        allowedAlwaysState.isShown = true
                        allowedAlwaysState.isChecked = allowedForegroundState.isChecked
                        allowedAlwaysState.isEnabled = allowedForegroundState.isEnabled
                        allowedForegroundState.isChecked = false
                        allowedForegroundState.isEnabled = false
                        deniedState.isChecked = deniedState.isChecked || askState.isChecked
                        askState.isEnabled = false

                        if (detailId == 0) {
                            detailId = getForegroundCapableDetailResId(foregroundCapableType)
                            if (detailId != 0) {
                                detailResIdLiveData.value = detailId to null
                            }
                        }
                    }
                }
            }
            if (group.packageInfo.targetSdkVersion < Build.VERSION_CODES.M) {
                // Pre-M app's can't ask for runtime permissions
                askState.isShown = false
                deniedState.isChecked = askState.isChecked || deniedState.isChecked
                deniedForegroundState.isChecked = askState.isChecked ||
                    deniedForegroundState.isChecked
            }

            val storageState = fullStorageStateLiveData.value
            if (isStorage && storageState?.isLegacy != true) {
                val allowedAllFilesState = allowedAlwaysState
                val allowedMediaOnlyState = allowedForegroundState
                if (storageState != null) {
                        // Set up the tri state permission for storage
                        allowedAllFilesState.isEnabled = allowedState.isEnabled
                        allowedAllFilesState.isShown = true
                        if (storageState.isGranted) {
                            allowedAllFilesState.isChecked = true
                            deniedState.isChecked = false
                        }
                } else {
                    allowedAllFilesState.isEnabled = false
                    allowedAllFilesState.isShown = false
                }
                allowedMediaOnlyState.isShown = true
                allowedMediaOnlyState.isEnabled = allowedState.isEnabled
                allowedMediaOnlyState.isChecked = allowedState.isChecked &&
                    storageState?.isGranted != true
                allowedState.isChecked = false
                allowedState.isShown = false
            }

            value = mapOf(ALLOW to allowedState, ALLOW_ALWAYS to allowedAlwaysState,
                ALLOW_FOREGROUND to allowedForegroundState, ASK_ONCE to askOneTimeState,
                ASK to askState, DENY to deniedState, DENY_FOREGROUND to deniedForegroundState)
        }
    }

权限请求

targetSdkVersion设置为高版本后, 下面的权限请求代码, 只能申请到仅允许访问媒体文件

		String[] perms = {
				//"android.permission.MANAGE_EXTERNAL_STORAGE",
				Manifest.permission.MANAGE_EXTERNAL_STORAGE,
				Manifest.permission.READ_EXTERNAL_STORAGE,
				Manifest.permission.WRITE_EXTERNAL_STORAGE,
		};
		requestPermissions(perms, 0x01);

实际上, MANAGE_EXTERNAL_STORAGE现传统的读写权限有很大的区别, 它与浮窗的权限类似, 由AppOpsService进行管理, 上面的代码, 不是能直接向AppOpsService申请权限.

开发者可以借助三方工具实现权限请求一般会通过调起系统的授权窗口, 引导用户操作授权:

  1. 方法 一
    设置 > 应用和通知 > 高级 特殊应用权限 > 所有文件访问权限 > App名称 > 授予所有文件管权限
  2. 方法 二 (实际去到了PermissionController)
    设置 > 应用和通知 > 所有应用 > App名称 > 权限 > 文件和媒体 > 允许管理所有文件
//方法1
START u0 {act=android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION dat=package:com.android.apitester cmp=com.android.settings/.Settings$AppManageExternalStorageActivity}
方法2
START u0 {act=android.intent.action.MANAGE_APP_PERMISSIONS cmp=com.android.permissioncontroller/.permission.ui.ManagePermissionsActivity (has extras)} from uid 1000

这里不作细述.


对于XXPermissions试了下, 有两点不习惯的地方:

  1. 要求支持android.support.v4.app.Fragment
ApiTester/src/main/java/com/android/apitester/PermissionTest.java:78: error: cannot access Fragment
		XXPermissions.with(this)
		             ^
  class file for android.support.v4.app.Fragment not found
//所以还得增加依赖包
implementation 'com.android.support:appcompat-v7:27.1.1'

2.异常

Caused by: java.lang.IllegalArgumentException: If you have applied for MANAGE_EXTERNAL_STORAGE permissions, do not apply for the READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions
   at com.hjq.permissions.PermissionChecker.optimizeDeprecatedPermission(PermissionChecker.java:239)
   at com.hjq.permissions.XXPermissions.request(XXPermissions.java:167)
   at com.android.apitester.PermissionTest.onCreate(PermissionTest.java:34)
   at android.app.Activity.performCreate(Activity.java:8022)
   at android.app.Activity.performCreate(Activity.java:8006)
   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3404)
   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595) 
   at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
   at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
   at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) 
   at android.os.Handler.dispatchMessage(Handler.java:106) 
   at android.os.Looper.loop(Looper.java:223) 
   at android.app.ActivityThread.main(ActivityThread.java:7664) 
   at java.lang.reflect.Method.invoke(Native Method) 
   at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 

参考

  1. Android 11 中的存储机制更新
    在这里插入图片描述

  2. 分区存储
    在这里插入图片描述
    Android权限适配
    android grantRuntimePermission 详解
    XXPermissions

相关文章:

  • Vue模板语法(01)
  • 世界第一台通用计算机:ENIAC
  • JAVA学习----HashSet类
  • 文章组合生成-免费文章组合生成软件
  • 华为面试应该怎么准备?
  • JDBC如何记忆
  • C语言之预处理
  • Flutter 系列---入门篇
  • 全球与中国汽车多楔带行业发展趋向分析及投资前景预测报告2022-2028年
  • Java IO流详解
  • 迷宫_Sarsa算法_边做边学深度强化学习:PyTorch程序设计实践(2)
  • iOS视频捕获入门篇
  • 数据可视化系列教程|六大组件基础知识
  • 《MySQL技术内幕:InnoDB存储引擎》学习笔记-第二章
  • 无线能量传输(WPT)中收发线圈的等效电路模型
  • [rust! #004] [译] Rust 的内置 Traits, 使用场景, 方式, 和原因
  • Fastjson的基本使用方法大全
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • iBatis和MyBatis在使用ResultMap对应关系时的区别
  • javascript从右向左截取指定位数字符的3种方法
  • Linux链接文件
  • PAT A1092
  • 如何使用 JavaScript 解析 URL
  • 学习JavaScript数据结构与算法 — 树
  • 一个JAVA程序员成长之路分享
  • 一个完整Java Web项目背后的密码
  • 以太坊客户端Geth命令参数详解
  • 原生js练习题---第五课
  • 运行时添加log4j2的appender
  • 交换综合实验一
  • ​3ds Max插件CG MAGIC图形板块为您提升线条效率!
  • #162 (Div. 2)
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (二)构建dubbo分布式平台-平台功能导图
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (四)搭建容器云管理平台笔记—安装ETCD(不使用证书)
  • (四)鸿鹄云架构一服务注册中心
  • (一)80c52学习之旅-起始篇
  • (一)认识微服务
  • (原+转)Ubuntu16.04软件中心闪退及wifi消失
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • .NET Core 通过 Ef Core 操作 Mysql
  • .NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划
  • .Net Framework 4.x 程序到底运行在哪个 CLR 版本之上
  • .net redis定时_一场由fork引发的超时,让我们重新探讨了Redis的抖动问题
  • .NET Standard / dotnet-core / net472 —— .NET 究竟应该如何大小写?
  • .NET 常见的偏门问题
  • .net项目IIS、VS 附加进程调试
  • .Net中wcf服务生成及调用
  • /etc/fstab和/etc/mtab的区别
  • [ Linux Audio 篇 ] 音频开发入门基础知识
  • [20160807][系统设计的三次迭代]
  • [ACL2022] Text Smoothing: 一种在文本分类任务上的数据增强方法
  • [CF407E]k-d-sequence