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
5
private var onGranted: (() -> Unit)? = null
6
private var onDenied: ((denied: List
7
private var onRationale: ((rationalePermissions: List
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
26
this.onDenied = block
27
return this
28
}
29
30
fun onRationale(block: (rationalePermissions: List
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 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 3 val permissionState: StateFlow 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 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 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