平臺
???? Android11 + RK3566 + AndroidStudio
Android 權(quán)限的變化, 幾乎每個版本的SDK都會有, 其中最大的一次是在6.0時, 增加的動態(tài)權(quán)限申請
讀寫存儲的權(quán)限也幾經(jīng)更迭, 對開發(fā)人員來說, 越來越難.比如, 本文所要討論的:允許管理所有文件
如何出現(xiàn)上面兩種不同的文件權(quán)限選項?
- 首先是 targetSdkVersion 大于等于 30. (build.gradle)
- 當聲明了 READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE
僅允許訪問媒體文件 - 當聲明了 MANAGE_EXTERNAL_STORAGE 會增加允許管理所有文件
-
targetSdkVersion <= 28 時, 只有允許管理所有文件和 拒絕 選項.
編寫測試代碼執(zhí)行以下動作:
- 申請權(quán)限
- 獲取內(nèi)部存儲下的1.txt文件
- 若文件存在, 刪除并輸出結(jié)果
- 嘗試寫入文件
讀寫失敗:
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
結(jié)果(FAILED:失敗, SUCCESS成功):
DIR | 僅允許訪問媒體文件 | 允許管理所有文件 |
---|---|---|
/storage/emulated/0 | FAILED | SUCCESS |
/storage/emulated/0/Download | FAILED | SUCCESS |
/storage/emulated/0/Android/data/com.android.apitester/files/Documents | SUCCESS | SUCCESS |
源碼中權(quán)限窗口
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
相關(guān)源碼
packages/apps/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
packages/apps/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
請求權(quán)限的交互
UI顯示內(nèi)容的判定
加載應(yīng)用存儲權(quán)限
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表示是否是舊的權(quán)限模式, UI會根據(jù)上面的代碼進行邏輯運算并更新對應(yīng)的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)
}
}
權(quán)限請求
當targetSdkVersion設(shè)置為高版本后, 下面的權(quán)限請求代碼, 只能申請到僅允許訪問媒體文件
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現(xiàn)傳統(tǒng)的讀寫權(quán)限有很大的區(qū)別, 它與浮窗的權(quán)限類似, 由AppOpsService進行管理, 上面的代碼, 不是能直接向AppOpsService申請權(quán)限.
開發(fā)者可以借助三方工具實現(xiàn)權(quán)限請求一般會通過調(diào)起系統(tǒng)的授權(quán)窗口, 引導(dǎo)用戶操作授權(quán):
- 方法 一
設(shè)置 > 應(yīng)用和通知 > 高級 特殊應(yīng)用權(quán)限 > 所有文件訪問權(quán)限 > App名稱 > 授予所有文件管權(quán)限 - 方法 二 (實際去到了PermissionController)
設(shè)置 > 應(yīng)用和通知 > 所有應(yīng)用 > App名稱 > 權(quán)限 > 文件和媒體 > 允許管理所有文件
//方法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試了下, 有兩點不習慣的地方:
- 要求支持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)
參考
-
Android 11 中的存儲機制更新
文章來源:http://www.zghlxwxcb.cn/news/detail-793998.html
-
分區(qū)存儲
Android權(quán)限適配
android grantRuntimePermission 詳解
XXPermissions文章來源地址http://www.zghlxwxcb.cn/news/detail-793998.html
到了這里,關(guān)于Android 11 上的文件讀寫權(quán)限(MANAGE_EXTERNAL_STORAGE)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!