• 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.(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 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    结果(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
    
    • 1

    布局文件

    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)
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    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)
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163

    权限请求

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    实际上, 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
    
    • 1
    • 2
    • 3
    • 4

    这里不作细述.


    对于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'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    参考

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

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

  • 相关阅读:
    【Java】Java核心要点总结:59
    1028 List Sorting
    03-链表(Linked List)应用分析
    九齐NY8BE62D/NY8B062D中硬件PWM的用法
    第十三届蓝桥杯Java、C++、Python组国赛真题——环境治理(三语言AC)
    Pod和容器设计模式
    Windows系统关机后自动重启的解决方法
    (七):jenkies构建vue
    关于VUE启动内存溢出
    一文搞懂Go读写Excel文件
  • 原文地址:https://blog.csdn.net/ansondroider/article/details/126655513