Android动态权限全面解析与最佳实践

1. Android动态权限的背景与必要性

在Android 6.0(API 23)之前,应用权限采用的是安装时授权模型。用户安装应用时,系统会一次性列出应用所需的所有权限,用户只能选择全部接受或放弃安装。这种"一刀切"的方式存在明显的用户体验问题:

权限过度索取:很多应用会申请大量与实际功能无关的权限

缺乏透明度:用户无法理解为什么一个手电筒应用需要读取通讯录

安全风险:恶意应用可以轻易获取敏感数据

2015年Google I/O大会上,Android团队宣布了全新的运行时权限模型(Runtime Permissions),这一改变从根本上重塑了Android的权限管理体系。作为开发者,我们必须理解几个关键时间节点:

Android 6.0(2015年):引入危险权限的运行时请求机制

Android 8.0(2017年):限制后台服务的位置访问

Android 10(2019年):细化存储权限,引入分区存储

Android 11(2020年):单次授权和自动重置权限

Android 13(2022年):进一步细化媒体和位置权限

提示:虽然动态权限从API 23开始引入,但Google Play要求自2018年11月起,所有新应用必须针对API 26+开发,2019年8月起所有更新应用也必须遵守此要求。这意味着现在开发的应用都必须实现动态权限处理。

2. 权限分类与关键概念解析

2.1 权限的三种类型

Android系统将权限分为三个保护级别:

普通权限(Normal)

不会直接威胁用户隐私

系统自动授予,无需用户操作

示例:INTERNET、BLUETOOTH、NFC

危险权限(Dangerous)

涉及用户隐私或设备操作

需要运行时显式请求

示例:CAMERA、LOCATION、CONTACTS

特殊权限(Special)

非常敏感的权限

需要通过特定Intent跳转设置页面授权

示例:SYSTEM_ALERT_WINDOW(悬浮窗)、WRITE_SETTINGS

2.2 权限组的概念

Android将相关权限组织成权限组,这在实际开发中有重要影响:

权限组

包含的权限示例

CALENDAR

READ_CALENDAR, WRITE_CALENDAR

CAMERA

CAMERA

CONTACTS

READ_CONTACTS, WRITE_CONTACTS

LOCATION

ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION

MICROPHONE

RECORD_AUDIO

PHONE

READ_PHONE_STATE, CALL_PHONE

SENSORS

BODY_SENSORS

SMS

SEND_SMS, READ_SMS

STORAGE

READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE

关键行为:

当应用请求同一组的多个权限时,系统会合并为一个对话框

用户授予组中某个权限后,同组的其他权限会自动授予(Android 10+有调整)

拒绝组中某个权限会影响同组的其他权限请求

3. 动态权限请求的基础实现

3.1 清单文件声明

无论是否动态请求,所有危险权限都必须在AndroidManifest.xml中声明:

XML

复制

1

2

package="com.example.myapp">

3

4

5

6

7

8

9

...

10

11

3.2 基本请求流程

一个完整的权限请求包含三个步骤:

检查权限状态

KOTLIN

复制

1

val permissionStatus = ContextCompat.checkSelfPermission(

2

context,

3

Manifest.permission.CAMERA

4

)

5

6

val isGranted = permissionStatus == PackageManager.PERMISSION_GRANTED

判断是否需要解释

KOTLIN

复制

1

val shouldShowRationale = ActivityCompat.shouldShowRequestPermissionRationale(

2

activity,

3

Manifest.permission.CAMERA

4

)

发起权限请求

KOTLIN

复制

1

ActivityCompat.requestPermissions(

2

activity,

3

arrayOf(Manifest.permission.CAMERA),

4

REQUEST_CODE_CAMERA

5

)

3.3 处理请求结果

在Activity/Fragment中重写onRequestPermissionsResult:

KOTLIN

复制

1

override fun onRequestPermissionsResult(

2

requestCode: Int,

3

permissions: Array,

4

grantResults: IntArray

5

) {

6

super.onRequestPermissionsResult(requestCode, permissions, grantResults)

7

8

when (requestCode) {

9

REQUEST_CODE_CAMERA -> {

10

if (grantResults.isNotEmpty() &&

11

grantResults[0] == PackageManager.PERMISSION_GRANTED) {

12

// 权限已授予

13

openCamera()

14

} else {

15

// 权限被拒绝

16

handlePermissionDenied()

17

}

18

}

19

}

20

}

4. 权限封装的最佳实践

4.1 基础工具类封装

创建一个PermissionUtils工具类处理通用逻辑:

KOTLIN

复制

1

object PermissionUtils {

2

/**

3

* 检查单个权限状态

4

*/

5

fun isGranted(context: Context, permission: String): Boolean {

6

return ContextCompat.checkSelfPermission(

7

context,

8

permission

9

) == PackageManager.PERMISSION_GRANTED

10

}

11

12

/**

13

* 检查多个权限状态

14

*/

15

fun checkMultiplePermissions(

16

context: Context,

17

permissions: Array

18

): Map {

19

return permissions.associateWith {

20

isGranted(context, it)

21

}

22

}

23

24

/**

25

* 判断是否需要显示解释UI

26

*/

27

fun shouldShowRationale(

28

activity: Activity,

29

permission: String

30

): Boolean {

31

return ActivityCompat.shouldShowRequestPermissionRationale(

32

activity,

33

permission

34

)

35

}

36

37

/**

38

* 跳转到应用设置页面

39

*/

40

fun openAppSettings(context: Context) {

41

val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {

42

data = Uri.fromParts("package", context.packageName, null)

43

}

44

context.startActivity(intent)

45

}

46

}

4.2 链式调用封装

实现更优雅的API调用方式:

KOTLIN

复制

1

class PermissionRequest private constructor(

2

private val activity: FragmentActivity

3

) {

4

private var permissions: Array = emptyArray()

5

private var onGranted: (() -> Unit)? = null

6

private var onDenied: ((denied: List) -> Unit)? = null

7

private var onRationale: ((rationalePermissions: List) -> Unit)? = null

8

9

companion object {

10

fun with(activity: FragmentActivity): PermissionRequest {

11

return PermissionRequest(activity)

12

}

13

}

14

15

fun permissions(vararg permissions: String): PermissionRequest {

16

this.permissions = permissions.toTypedArray()

17

return this

18

}

19

20

fun onGranted(block: () -> Unit): PermissionRequest {

21

this.onGranted = block

22

return this

23

}

24

25

fun onDenied(block: (denied: List) -> Unit): PermissionRequest {

26

this.onDenied = block

27

return this

28

}

29

30

fun onRationale(block: (rationalePermissions: List) -> Unit): PermissionRequest {

31

this.onRationale = block

32

return this

33

}

34

35

fun execute() {

36

if (permissions.isEmpty()) {

37

throw IllegalArgumentException("至少需要请求一个权限")

38

}

39

40

val (granted, denied) = permissions.partition {

41

PermissionUtils.isGranted(activity, it)

42

}

43

44

if (denied.isEmpty()) {

45

onGranted?.invoke()

46

return

47

}

48

49

val (shouldRationale, notRationale) = denied.partition {

50

PermissionUtils.shouldShowRationale(activity, it)

51

}

52

53

if (shouldRationale.isNotEmpty()) {

54

onRationale?.invoke(shouldRationale)

55

return

56

}

57

58

val launcher = activity.registerForActivityResult(

59

ActivityResultContracts.RequestMultiplePermissions()

60

) { grants ->

61

val (success, failed) = grants.entries.partition { it.value }

62

63

if (failed.isEmpty()) {

64

onGranted?.invoke()

65

} else {

66

onDenied?.invoke(failed.map { it.key })

67

}

68

}

69

70

launcher.launch(denied.toTypedArray())

71

}

72

}

使用示例:

KOTLIN

复制

1

PermissionRequest.with(this)

2

.permissions(

3

Manifest.permission.CAMERA,

4

Manifest.permission.READ_CONTACTS

5

)

6

.onGranted {

7

// 所有权限都已授予

8

openCamera()

9

loadContacts()

10

}

11

.onRationale { permissions ->

12

// 需要向用户解释为什么需要这些权限

13

showRationaleDialog(permissions) {

14

// 用户理解后重新请求

15

execute()

16

}

17

}

18

.onDenied { permissions ->

19

// 权限被拒绝(可能勾选了"不再询问")

20

if (permissions.any { !PermissionUtils.shouldShowRationale(activity, it) }) {

21

// 引导用户去设置页面

22

showGoToSettingsDialog()

23

} else {

24

// 普通拒绝

25

showPermissionDeniedToast()

26

}

27

}

28

.execute()

5. 高级场景与特殊处理

5.1 后台位置权限

Android 10+对后台位置访问有特殊要求:

KOTLIN

复制

1

fun requestBackgroundLocation(activity: Activity) {

2

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {

3

val backgroundLocationPermission =

4

Manifest.permission.ACCESS_BACKGROUND_LOCATION

5

6

if (!PermissionUtils.isGranted(activity, backgroundLocationPermission)) {

7

val foregroundPermissions = arrayOf(

8

Manifest.permission.ACCESS_FINE_LOCATION,

9

Manifest.permission.ACCESS_COARSE_LOCATION

10

)

11

12

val allPermissions = foregroundPermissions + backgroundLocationPermission

13

14

PermissionRequest.with(activity)

15

.permissions(*allPermissions)

16

.onGranted {

17

// 检查是否真正获得了后台权限

18

if (PermissionUtils.isGranted(activity, backgroundLocationPermission)) {

19

startBackgroundLocationUpdates()

20

} else {

21

showBackgroundLocationDeniedDialog()

22

}

23

}

24

.execute()

25

}

26

}

27

}

5.2 悬浮窗权限处理

SYSTEM_ALERT_WINDOW是特殊权限,需要特殊处理:

KOTLIN

复制

1

fun checkAndRequestOverlayPermission(context: Context): Boolean {

2

return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&

3

!Settings.canDrawOverlays(context)) {

4

val intent = Intent(

5

Settings.ACTION_MANAGE_OVERLAY_PERMISSION,

6

Uri.parse("package:${context.packageName}")

7

)

8

context.startActivity(intent)

9

false

10

} else {

11

true

12

}

13

}

5.3 权限自动重置处理

Android 11+引入了权限自动重置功能,用户长时间不使用应用后,系统会自动重置已授予的权限。我们需要:

在AndroidManifest.xml中添加:

XML

复制

1

2

android:requestLegacyExternalStorage="true"

3

android:preserveLegacyExternalStorage="true">

在应用启动时检查权限状态:

KOTLIN

复制

1

override fun onCreate(savedInstanceState: Bundle?) {

2

super.onCreate(savedInstanceState)

3

4

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

5

val revokedPermissions = packageManager.getRevokedPermissions(

6

packageName,

7

PackageManager.GET_PERMISSIONS

8

)

9

10

if (revokedPermissions.isNotEmpty()) {

11

// 处理被重置的权限

12

showPermissionResetDialog()

13

}

14

}

15

}

6. 测试与调试技巧

6.1 ADB命令管理权限

通过ADB可以模拟各种权限状态:

BASH

复制

1

# 授予权限

2

adb shell pm grant

3

# 例如:

4

adb shell pm grant com.example.myapp android.permission.CAMERA

5

6

# 撤销权限

7

adb shell pm revoke

8

9

# 重置所有权限

10

adb shell pm reset-permissions

6.2 测试用例设计

完整的权限测试应包含以下场景:

首次请求权限

用户点击允许

用户点击拒绝

用户点击拒绝并勾选"不再询问"

已授予权限的情况

正常使用功能

通过ADB撤销权限后检查应用行为

已拒绝权限的情况

再次请求时的处理

通过设置页面手动授予后的行为

权限被系统自动重置后的恢复流程

6.3 常见问题排查

问题1:权限请求对话框不显示

检查是否在AndroidManifest.xml中声明了权限

确认请求的是危险权限(普通权限不需要动态请求)

检查是否在后台线程调用了权限请求

问题2:shouldShowRequestPermissionRationale总是返回false

首次请求前确实应该返回false

如果用户勾选了"不再询问",也会返回false

确保在Activity中调用,而不是Application context

问题3:权限回调不执行

确认requestCode匹配

检查是否在Fragment中请求但在Activity中处理回调(或反之)

确保没有重复注册ActivityResultLauncher

7. 版本兼容性全面指南

7.1 Android 6.0以下(API < 23)

KOTLIN

复制

1

fun checkPermission(context: Context, permission: String): Boolean {

2

return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

3

ContextCompat.checkSelfPermission(context, permission) ==

4

PackageManager.PERMISSION_GRANTED

5

} else {

6

// 对于旧版本,只要在清单中声明就视为已授予

7

true

8

}

9

}

7.2 Android 11(API 30)变化

单次授权:ACCESS_BACKGROUND_LOCATION必须单独请求

权限可见性:限制查询未声明的权限

KOTLIN

复制

1

fun isPermissionAvailable(context: Context, permission: String): Boolean {

2

return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

3

try {

4

val info = context.packageManager.getPermissionInfo(permission, 0)

5

info.isNonConfigurable

6

} catch (e: PackageManager.NameNotFoundException) {

7

false

8

}

9

} else {

10

true

11

}

12

}

7.3 Android 13(API 33)新特性

细化媒体权限:

READ_MEDIA_IMAGES

READ_MEDIA_VIDEO

READ_MEDIA_AUDIO

替代旧的READ_EXTERNAL_STORAGE

附近Wi-Fi设备权限:

NEARBY_WIFI_DEVICES替代ACCESS_FINE_LOCATION用于Wi-Fi扫描

适配示例:

KOTLIN

复制

1

fun getStoragePermissions(): Array {

2

return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {

3

arrayOf(

4

Manifest.permission.READ_MEDIA_IMAGES,

5

Manifest.permission.READ_MEDIA_VIDEO

6

)

7

} else {

8

arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)

9

}

10

}

8. 用户体验优化建议

8.1 权限请求时机

延迟请求:在用户真正需要使用功能时请求权限

KOTLIN

复制

1

btnTakePhoto.setOnClickListener {

2

if (!hasCameraPermission()) {

3

requestCameraPermission()

4

} else {

5

takePhoto()

6

}

7

}

分批请求:不要一次性请求所有权限,按功能模块逐步请求

8.2 解释对话框设计

好的解释应该包含:

明确说明需要什么权限

解释为什么需要这个权限

说明权限将如何被使用

强调不会滥用权限

示例实现:

KOTLIN

复制

1

fun showRationaleDialog(

2

permissions: List,

3

onContinue: () -> Unit,

4

onCancel: () -> Unit

5

) {

6

val permissionNames = permissions.map {

7

when (it) {

8

Manifest.permission.CAMERA -> "相机"

9

Manifest.permission.READ_CONTACTS -> "读取联系人"

10

// 其他权限的本地化名称

11

else -> it

12

}

13

}.joinToString("、")

14

15

AlertDialog.Builder(this)

16

.setTitle("需要$permissionNames权限")

17

.setMessage("我们需要这些权限来为您提供完整的拍照和分享功能。您的数据只会用于此目的,不会与他人共享。")

18

.setPositiveButton("继续") { _, _ -> onContinue() }

19

.setNegativeButton("取消") { _, _ -> onCancel() }

20

.show()

21

}

8.3 被永久拒绝后的引导

当用户勾选"不再询问"后,应该:

解释无法继续使用相关功能

提供明确的引导到设置页面

确保设置页面的跳转顺畅

KOTLIN

复制

1

fun showGoToSettingsDialog() {

2

AlertDialog.Builder(this)

3

.setTitle("权限被禁用")

4

.setMessage("您已永久拒绝所需权限。如需使用该功能,请前往设置中手动开启权限。")

5

.setPositiveButton("去设置") { _, _ ->

6

PermissionUtils.openAppSettings(this)

7

}

8

.setNegativeButton("取消", null)

9

.setCancelable(false)

10

.show()

11

}

9. 完整示例项目结构

一个良好的权限处理模块可以这样组织:

TEXT

复制

1

com.example.app.permission/

2

├── PermissionChecker.kt # 基础权限检查工具

3

├── PermissionRequester.kt # 权限请求封装

4

├── PermissionResult.kt # 请求结果数据类

5

├── extensions/

6

│ └── ContextExtensions.kt # 扩展函数

7

└── ui/

8

├── RationaleDialog.kt # 解释对话框

9

└── SettingsGuideDialog.kt # 设置引导对话框

核心类实现:

PermissionResult.kt

KOTLIN

复制

1

sealed class PermissionResult {

2

data class Granted(val permissions: List) : PermissionResult()

3

data class Denied(

4

val permissions: List,

5

val permanently: Boolean

6

) : PermissionResult()

7

}

PermissionRequester.kt

KOTLIN

复制

1

class PermissionRequester private constructor(

2

private val activity: FragmentActivity

3

) {

4

// ... 之前的链式调用实现 ...

5

6

suspend fun await(): PermissionResult {

7

return suspendCoroutine { continuation ->

8

onGranted {

9

continuation.resume(

10

PermissionResult.Granted(permissions.toList())

11

)

12

}

13

onDenied { denied ->

14

val permanently = denied.any {

15

!shouldShowRationale(activity, it)

16

}

17

continuation.resume(

18

PermissionResult.Denied(denied, permanently)

19

)

20

}

21

execute()

22

}

23

}

24

}

使用示例(协程中):

KOTLIN

复制

1

lifecycleScope.launch {

2

when (val result = PermissionRequester.with(this@MainActivity)

3

.permissions(Manifest.permission.CAMERA)

4

.await()) {

5

is PermissionResult.Granted -> openCamera()

6

is PermissionResult.Denied -> {

7

if (result.permanently) {

8

showGoToSettingsDialog()

9

} else {

10

showRationaleDialog()

11

}

12

}

13

}

14

}

10. 与其他架构组件的整合

10.1 与ViewModel整合

在ViewModel中处理权限逻辑:

KOTLIN

复制

1

class CameraViewModel : ViewModel() {

2

private val _permissionState = MutableStateFlow(PermissionState.Idle)

3

val permissionState: StateFlow = _permissionState

4

5

fun checkCameraPermission(context: Context) {

6

_permissionState.value = if (PermissionUtils.isGranted(

7

context,

8

Manifest.permission.CAMERA

9

)) {

10

PermissionState.Granted

11

} else {

12

PermissionState.RequiresPermission

13

}

14

}

15

}

16

17

sealed class PermissionState {

18

object Idle : PermissionState()

19

object Granted : PermissionState()

20

object RequiresPermission : PermissionState()

21

data class Denied(val showRationale: Boolean) : PermissionState()

22

}

10.2 与Compose整合

Jetpack Compose中的权限处理:

KOTLIN

复制

1

@Composable

2

fun CameraScreen(

3

viewModel: CameraViewModel = viewModel(),

4

onPermissionResult: (Boolean) -> Unit

5

) {

6

val context = LocalContext.current

7

val activity = context as ComponentActivity

8

9

val launcher = rememberLauncherForActivityResult(

10

ActivityResultContracts.RequestPermission()

11

) { isGranted ->

12

onPermissionResult(isGranted)

13

}

14

15

LaunchedEffect(Unit) {

16

viewModel.checkCameraPermission(context)

17

}

18

19

when (val state = viewModel.permissionState.collectAsState().value) {

20

PermissionState.Idle -> { /* 加载中 */ }

21

PermissionState.Granted -> { CameraView() }

22

PermissionState.RequiresPermission -> {

23

Button(onClick = {

24

launcher.launch(Manifest.permission.CAMERA)

25

}) {

26

Text("请求相机权限")

27

}

28

}

29

is PermissionState.Denied -> {

30

if (state.showRationale) {

31

RationaleDialog {

32

launcher.launch(Manifest.permission.CAMERA)

33

}

34

} else {

35

SettingsGuideDialog()

36

}

37

}

38

}

39

}

10.3 使用ActivityResult API

替代传统的onRequestPermissionsResult:

KOTLIN

复制

1

class MainActivity : AppCompatActivity() {

2

private val permissionLauncher = registerForActivityResult(

3

ActivityResultContracts.RequestPermission()

4

) { isGranted ->

5

if (isGranted) {

6

openCamera()

7

} else {

8

handlePermissionDenied()

9

}

10

}

11

12

fun requestCameraPermission() {

13

permissionLauncher.launch(Manifest.permission.CAMERA)

14

}

15

}

对于多个权限请求:

KOTLIN

复制

1

private val multiPermissionLauncher = registerForActivityResult(

2

ActivityResultContracts.RequestMultiplePermissions()

3

) { grants ->

4

val allGranted = grants.all { it.value }

5

if (allGranted) {

6

// 所有权限都已授予

7

} else {

8

// 处理被拒绝的权限

9

}

10

}

11. 性能优化与注意事项

11.1 减少权限请求次数

使用SharedPreferences记录用户选择:

KOTLIN

复制

1

fun shouldRequestPermission(context: Context, permission: String): Boolean {

2

val prefs = PreferenceManager.getDefaultSharedPreferences(context)

3

val key = "permission_$permission"

4

5

return if (prefs.getBoolean(key, false)) {

6

false

7

} else {

8

prefs.edit().putBoolean(key, true).apply()

9

true

10

}

11

}

11.2 处理权限请求的配置变更

使用ViewModel保存权限请求状态:

KOTLIN

复制

1

class PermissionViewModel : ViewModel() {

2

private val _pendingPermissionRequest = MutableLiveData()

3

val pendingPermissionRequest: LiveData = _pendingPermissionRequest

4

5

fun setPendingPermission(permission: String?) {

6

_pendingPermissionRequest.value = permission

7

}

8

}

在Activity中观察:

KOTLIN

复制

1

viewModel.pendingPermissionRequest.observe(this) { permission ->

2

permission?.let {

3

requestPermission(it)

4

viewModel.setPendingPermission(null)

5

}

6

}

11.3 避免内存泄漏

在Fragment中请求权限时,使用viewLifecycleOwner:

KOTLIN

复制

1

class CameraFragment : Fragment() {

2

private lateinit var permissionLauncher: ActivityResultLauncher

3

4

override fun onCreate(savedInstanceState: Bundle?) {

5

super.onCreate(savedInstanceState)

6

7

permissionLauncher = registerForActivityResult(

8

ActivityResultContracts.RequestPermission(),

9

::handlePermissionResult

10

)

11

}

12

13

private fun requestPermission() {

14

lifecycleScope.launchWhenStarted {

15

permissionLauncher.launch(Manifest.permission.CAMERA)

16

}

17

}

18

}

12. 安全与隐私最佳实践

12.1 最小权限原则

只申请应用必须的权限

优先使用更宽松的权限:

使用ACCESS_COARSE_LOCATION替代ACCESS_FINE_LOCATION

使用READ_EXTERNAL_STORAGE替代WRITE_EXTERNAL_STORAGE

12.2 敏感数据保护

及时清除权限获取的数据:

KOTLIN

复制

1

override fun onPause() {

2

super.onPause()

3

if (!isChangingConfigurations) {

4

// 清除内存中的敏感数据

5

clearSensitiveData()

6

}

7

}

使用AndroidX Security Crypto加密敏感数据:

KOTLIN

复制

1

fun encryptData(context: Context, data: String): ByteArray {

2

val masterKey = MasterKey.Builder(context)

3

.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)

4

.build()

5

6

val encryptedData = EncryptedFile.Builder(

7

context,

8

File(context.filesDir, "secure_data.txt"),

9

masterKey,

10

EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB

11

).build()

12

13

return encryptedData.openFileOutput().use {

14

it.write(data.toByteArray())

15

}

16

}

12.3 权限使用日志

记录权限使用情况以便审计:

KOTLIN

复制

1

fun logPermissionUsage(permission: String, action: String) {

2

Timber.d("Permission usage: $permission for $action")

3

4

// 或者上报到服务器

5

analytics.logEvent("permission_usage", bundleOf(

6

"permission" to permission,

7

"action" to action

8

))

9

}

使用示例:

KOTLIN

复制

1

fun openCamera() {

2

logPermissionUsage(Manifest.permission.CAMERA, "take_photo")

3

// 实际打开相机逻辑

4

}

13. 常见问题深度解析

13.1 为什么shouldShowRationale返回false?

可能原因:

首次请求权限前(用户从未见过对话框)

用户勾选了"不再询问"

在非Activity Context中调用

权限策略限制(如企业设备管理)

解决方案:

KOTLIN

复制

1

fun shouldShowRationaleSmart(

2

activity: Activity,

3

permission: String

4

): Boolean {

5

return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

6

if (PermissionUtils.isGranted(activity, permission)) {

7

false

8

} else {

9

ActivityCompat.shouldShowRequestPermissionRationale(activity, permission) ||

10

// 检查是否是首次请求

11

!PreferenceManager.getDefaultSharedPreferences(activity)

12

.contains("permission_requested_$permission")

13

}

14

} else {

15

false

16

}

17

}

13.2 如何处理权限被系统自动撤销?

监听权限变化:

KOTLIN

复制

1

private val permissionObserver = object : OnPermissionsChangedListener() {

2

override fun onPermissionsChanged(reason: Int) {

3

if (reason == PERMISSION_REVOKED_BY_SYSTEM) {

4

runOnUiThread {

5

showPermissionRevokedDialog()

6

}

7

}

8

}

9

}

10

11

override fun onCreate(savedInstanceState: Bundle?) {

12

super.onCreate(savedInstanceState)

13

14

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

15

val permissionManager =

16

getSystemService(PERMISSION_SERVICE) as PermissionManager

17

permissionManager.addOnPermissionsChangeListener(permissionObserver)

18

}

19

}

20

21

override fun onDestroy() {

22

super.onDestroy()

23

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

24

val permissionManager =

25

getSystemService(PERMISSION_SERVICE) as PermissionManager

26

permissionManager.removeOnPermissionsChangeListener(permissionObserver)

27

}

28

}

13.3 多Fragment中的权限协调

BaseFragment解决方案:

KOTLIN

复制

1

abstract class BaseFragment : Fragment() {

2

private val permissionCallbacks = mutableMapOf Unit>()

3

private var currentRequestCode = 1000

4

5

protected fun requestPermission(

6

permission: String,

7

callback: (Boolean) -> Unit

8

): Boolean {

9

val requestCode = ++currentRequestCode

10

permissionCallbacks[requestCode] = callback

11

12

registerForActivityResult(

13

ActivityResultContracts.RequestPermission()

14

) { granted ->

15

permissionCallbacks.remove(requestCode)?.invoke(granted)

16

}.launch(permission)

17

18

return true

19

}

20

}

使用示例:

KOTLIN

复制

1

class MyFragment : BaseFragment() {

2

fun onCameraClick() {

3

requestPermission(Manifest.permission.CAMERA) { granted ->

4

if (granted) {

5

openCamera()

6

}

7

}

8

}

9

}

14. 进阶:自定义权限系统

14.1 定义自定义权限

在AndroidManifest.xml中:

XML

复制

1

2

android:name="com.example.myapp.permission.ACCESS_PREMIUM"

3

android:label="Premium Access"

4

android:description="Allows access to premium features"

5

android:protectionLevel="signature" />

14.2 检查自定义权限

KOTLIN

复制

1

fun checkCustomPermission(context: Context): Boolean {

2

return try {

3

val info = context.packageManager.getPermissionInfo(

4

"com.example.myapp.permission.ACCESS_PREMIUM",

5

PackageManager.GET_META_DATA

6

)

7

info.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE

8

} catch (e: PackageManager.NameNotFoundException) {

9

false

10

}

11

}

14.3 使用自定义权限

在组件声明中添加:

XML

复制

1

2

android:name=".PremiumActivity"

3

android:permission="com.example.myapp.permission.ACCESS_PREMIUM" />

15. 调试与性能监控

15.1 使用Android Studio的Profiler

打开Profiler (View > Tool Windows > Profiler)

选择要分析的设备和应用进程

查看"Permissions"选项卡中的权限状态变化

15.2 添加权限使用监控

KOTLIN

复制

1

class PermissionMonitorInterceptor : Interceptor {

2

override fun intercept(chain: Interceptor.Chain): Response {

3

val request = chain.request()

4

5

if (request.tag(PermissionCheck::class.java) != null) {

6

val startTime = System.currentTimeMillis()

7

val response = chain.proceed(request)

8

val duration = System.currentTimeMillis() - startTime

9

10

logPermissionCheckDuration(

11

request.tag(PermissionCheck::class.java).permission,

12

duration

13

)

14

15

return response

16

}

17

18

return chain.proceed(request)

19

}

20

}

21

22

data class PermissionCheck(val permission: String)

15.3 关键性能指标

监控以下指标:

权限请求成功率

用户拒绝率(首次/永久)

权限请求耗时

权限使用频率

KOTLIN

复制

1

class PermissionMetrics {

2

fun logRequest(permission: String) {

3

// 记录到Analytics

4

}

5

6

fun logResult(permission: String, granted: Boolean, isPermanentDenial: Boolean) {

7

// 记录结果

8

}

9

10

fun logUsage(permission: String) {

11

// 记录权限实际使用情况

12

}

13

}

16. 测试策略与自动化

16.1 单元测试

测试权限工具类:

KOTLIN

复制

1

@RunWith(AndroidJUnit4::class)

2

class PermissionUtilsTest {

3

@get:Rule

4

val grantPermissionRule = GrantPermissionRule.grant(

5

Manifest.permission.CAMERA

6

)

7

8

@Test

9

fun testCheckPermission() {

10

val context = InstrumentationRegistry.getInstrumentation().targetContext

11

assertTrue(PermissionUtils.isGranted(context, Manifest.permission.CAMERA))

12

}

13

}

16.2 UI自动化测试

使用Espresso测试权限流程:

KOTLIN

复制

1

@RunWith(AndroidJUnit4::class)

2

class CameraPermissionTest {

3

@get:Rule

4

val activityRule = ActivityScenarioRule(MainActivity::class.java)

5

6

@Test

7

fun testPermissionGranted() {

8

// 模拟用户点击需要权限的按钮

9

onView(withId(R.id.btn_open_camera)).perform(click())

10

11

// 验证预期的UI变化

12

onView(withId(R.id.camera_preview)).check(matches(isDisplayed()))

13

}

14

}

16.3 使用Mockito模拟权限状态

KOTLIN

复制

1

@RunWith(MockitoJUnitRunner::class)

2

class PermissionViewModelTest {

3

@Mock

4

private lateinit var mockContext: Context

5

6

@Mock

7

private lateinit var mockPackageManager: PackageManager

8

9

private lateinit var viewModel: PermissionViewModel

10

11

@Before

12

fun setup() {

13

whenever(mockContext.packageManager).thenReturn(mockPackageManager)

14

viewModel = PermissionViewModel(mockContext)

15

}

16

17

@Test

18

fun testPermissionGrantedState() {

19

whenever(mockPackageManager.checkPermission(

20

eq(Manifest.permission.CAMERA),

21

anyString()

22

)).thenReturn(PackageManager.PERMISSION_GRANTED)

23

24

viewModel.checkCameraPermission()

25

26

assertEquals(PermissionState.Granted, viewModel.permissionState.value)

27

}

28

}

17. 未来趋势与适配建议

17.1 Android 14+的预测变化

更细化的照片/视频选择器

健康数据权限的进一步限制

后台传感器访问限制

17.2 隐私沙盒的影响

替代广告ID的新方案

更严格的跨应用数据共享限制

需要适配新的API获取设备信息

17.3 准备策略

逐步替换READ_EXTERNAL_STORAGE:

KOTLIN

复制

1

fun getStoragePermission(): String {

2

return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {

3

Manifest.permission.READ_MEDIA_IMAGES

4

} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

5

Manifest.permission.READ_EXTERNAL_STORAGE

6

} else {

7

Manifest.permission.WRITE_EXTERNAL_STORAGE

8

}

9

}

使用Photo Picker API:

KOTLIN

复制

1

val pickMedia = registerForActivityResult(PickVisualMedia()) { uri ->

2

uri?.let {

3

// 处理用户选择的媒体文件

4

}

5

}

6

7

fun selectImage() {

8

pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))

9

}

关注Google Play政策变化:

定期检查目标API级别要求

关注新权限的申报要求

准备隐私标签的准确描述

18. 替代方案与权限最小化

18.1 使用系统选择器替代直接权限

照片选择:使用Intent.ACTION_GET_CONTENT

KOTLIN

复制

1

val intent = Intent(Intent.ACTION_GET_CONTENT).apply {

2

type = "image/*"

3

}

4

startActivityForResult(intent, REQUEST_PICK_IMAGE)

联系人选择:使用ContactsContract.CommonDataKinds.Phone.CONTENT_URI

18.2 使用临时授权

对于一次性操作:

KOTLIN

复制

1

val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture

Copyright © 2088 网络游戏活动中心 - 最新游戏活动汇总 All Rights Reserved.
友情链接