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

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

平台


    Android11 + RK3566 + AndroidStudio


Android 权限的变化, 几乎每个版本的SDK都会有, 其中最大的一次是在6.0时, 增加的动态权限申请

读写存储的权限也几经更迭, 对开发人员来说, 越来越难.比如, 本文所要讨论的:允许管理所有文件

image.png

image.png


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


1.首先是 targetSdkVersion 大于等于 30. (build.gradle)

image.png

2.当声明了 READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE

仅允许访问媒体文件

3.当声明了 MANAGE_EXTERNAL_STORAGE 会增加允许管理所有文件

4.targetSdkVersion <= 28 时, 只有允许管理所有文件和 拒绝 选项.

image.png

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


1.申请权限

2.获取内部存储下的1.txt文件

3.若文件存在, 删除并输出结果

4.尝试写入文件

5.读写失败:


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成功):

image.png


源码中权限窗口


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


请求权限的交互

image.png


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 中的存储机制更新

0a2653c851af460fa595bd959398a8f1.png


2.分区存储

image.png

Android权限适配

android grantRuntimePermission 详解

XXPermissions


相关文章
|
6月前
|
存储 Android开发
如何查看Flutter应用在Android设备上已被撤销的权限?
如何查看Flutter应用在Android设备上已被撤销的权限?
272 64
|
6月前
|
存储 Android开发 数据安全/隐私保护
如何在Android设备上撤销Flutter应用程序的所有权限?
如何在Android设备上撤销Flutter应用程序的所有权限?
357 64
|
5月前
|
Android开发 开发者
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
本文详细介绍了如何通过自定义 `attrs.xml` 文件实现 Android 自定义 View 的属性配置。以一个包含 TextView 和 ImageView 的 DemoView 为例,讲解了如何使用自定义属性动态改变文字内容和控制图片显示隐藏。同时,通过设置布尔值和点击事件,实现了图片状态的切换功能。代码中展示了如何在构造函数中解析自定义属性,并通过方法 `setSetting0n` 和 `setbackeguang` 实现功能逻辑的优化与封装。此示例帮助开发者更好地理解自定义 View 的开发流程与 attrs.xml 的实际应用。
101 2
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
|
5月前
|
Java Android开发
Android studio中build.gradle文件简单介绍
本文解析了Android项目中build.gradle文件的作用,包括jcenter仓库配置、模块类型定义、包名设置及依赖管理,涵盖本地、库和远程依赖的区别。
468 19
|
5月前
|
存储 XML Java
Android 文件数据储存之内部储存 + 外部储存
简介:本文详细介绍了Android内部存储与外部存储的使用方法及核心原理。内部存储位于手机内存中,默认私有,适合存储SharedPreferences、SQLite数据库等重要数据,应用卸载后数据会被清除。外部存储包括公共文件和私有文件,支持SD卡或内部不可移除存储,需申请权限访问。文章通过代码示例展示了如何保存、读取、追加、删除文件以及将图片保存到系统相册的操作,帮助开发者理解存储机制并实现相关功能。
1179 2
|
8月前
|
移动开发 安全 Java
Android历史版本与APK文件结构
通过以上内容,您可以全面了解Android的历史版本及其主要特性,同时掌握APK文件的结构和各部分的作用。这些知识对于理解Android应用的开发和发布过程非常重要,也有助于在实际开发中进行高效的应用管理和优化。希望这些内容对您的学习和工作有所帮助。
742 83
|
12月前
|
ARouter Android开发
Android不同module布局文件重名被覆盖
Android不同module布局文件重名被覆盖
|
10月前
|
存储 监控 API
app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
894 11
|
存储 Java API
Android系统 文件访问权限笔记
Android系统 文件访问权限笔记
1456 1
|
XML Android开发 数据安全/隐私保护
android 11后文件读写访问权限申请
android 11后文件读写访问权限申请
588 0

热门文章

最新文章