文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2023-08-21 06:20

关注

平台

     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.(FileOutputStream.java:236)2022-09-03 07:25:11.068 10710-10710/com.android.apitester W/System.err:     at java.io.FileOutputStream.(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 more2022-09-03 07:25:11.073 10710-10710/com.android.apitester E/PermissionTest: write /storage/emulated/0/1.txt failed2022-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.apitester2022-09-03 07:25:11.097 10710-10710/com.android.apitester D/PermissionTest: delete /storage/emulated/0/Download/1.txt success2022-09-03 07:25:11.124 10710-10710/com.android.apitester D/PermissionTest: write /storage/emulated/0/Download/1.txt success2022-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 success2022-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 = truedeniedState.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名称 > 权限 > 文件和媒体 > 允许管理所有文件
//方法1START u0 {act=android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION dat=package:com.android.apitester cmp=com.android.settings/.Settings$AppManageExternalStorageActivity}方法2START 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 FragmentXXPermissions.with(this)             ^  class file for android.support.v4.app.Fragment not found//所以还得增加依赖包implementation 'com.android.support:appcompat-v7:27.1.1'

异常

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

来源地址:https://blog.csdn.net/ansondroider/article/details/126655513

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯