Android 权限申请现代化指南
一、核心概念:权限分类
Android 将权限分为三大类,申请方式各不相同:
- 普通权限 (Normal Permissions)
· 范围:涉及应用沙盒外部但对用户隐私或设备操作风险极低的操作。
· 示例:网络访问 (INTERNET)、振动 (VIBRATE)、设置壁纸 (SET_WALLPAPER)。
· 授权方式:在 AndroidManifest.xml 中声明后,系统在安装时自动授予。 - 危险权限 (Dangerous Permissions)
· 范围:涉及用户隐私或设备敏感数据的操作。
· 示例:相机 (CAMERA)、精确位置 (ACCESS_FINE_LOCATION)、联系人 (READ_CONTACTS)、麦克风 (RECORD_AUDIO)。
· 授权方式:必须在运行时明确向用户请求。这是本指南的重点。 - 特殊权限 (Special Permissions)
· 范围:拥有系统级影响的极高特权权限。
· 示例:在其他应用上方绘制 (SYSTEM_ALERT_WINDOW)、修改系统设置 (WRITE_SETTINGS)。
· 授权方式:通常需要引导用户至系统特定页面进行授权,不通过标准 API 请求。
二、现代化实现方式
Google 推荐使用 Activity Result API 配合 ActivityResultContracts 来请求权限。这种方式取代了传统的重写 onRequestPermissionsResult() 的方法,具有以下优势:
· 生命周期安全:自动管理生命周期,避免内存泄漏。
· 代码清晰:请求逻辑和结果处理集中在一起,易于维护。
· 无需请求码:避免了手动管理 requestCode 的繁琐和错误。
- 声明权限
在 AndroidManifest.xml 中声明所有需要的权限,这是任何方式的前提。
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 其他权限 -->
<application ...> ... </application>
</manifest>
- 请求单个权限(以相机权限为例)
在 Activity 或 Fragment 中:
class MainActivity : AppCompatActivity() {
// 1. 注册权限请求启动器(Launcher)
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission() // 使用请求单个权限的合约
) { isGranted: Boolean ->
// 权限请求结果的回调
handlePermissionResult(isGranted)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.btn_request)
button.setOnClickListener {
checkCameraPermission()
}
}
private fun checkCameraPermission() {
// 2. 检查权限是否已授予
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
// 已有权限,执行操作
openCamera()
}
// 3. 判断是否需要向用户解释原因
shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
showPermissionRationaleDialog()
}
else -> {
// 直接发起请求(首次或用户未选择"不再询问")
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
}
private fun handlePermissionResult(isGranted: Boolean) {
if (isGranted) {
openCamera()
} else {
// 处理拒绝逻辑,特别是“不再询问”的情况
showGoToSettingsDialog()
}
}
private fun openCamera() {
Toast.makeText(this, "Camera opened!", Toast.LENGTH_SHORT).show()
}
private fun showPermissionRationaleDialog() {
AlertDialog.Builder(this)
.setTitle("Need Permission")
.setMessage("This app needs camera access to take pictures.")
.setPositiveButton("OK") { _, _ ->
// 用户理解后,再次发起请求
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
.setNegativeButton("Cancel", null)
.show()
}
private fun showGoToSettingsDialog() {
AlertDialog.Builder(this)
.setTitle("Permission Denied")
.setMessage("You have permanently denied this permission. Please enable it in app settings.")
.setPositiveButton("Open Settings") { _, _ ->
// 跳转到本应用的系统设置页
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
}
startActivity(intent)
}
.setNegativeButton("Cancel", null)
.show()
}
}
- 请求多个权限(同时请求相机和麦克风)
使用 RequestMultiplePermissions Contract。
class MainActivity : AppCompatActivity() {
// 1. 注册多权限请求启动器
private val requestMultiplePermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions: Map<String, Boolean> -> // 结果是一个映射表
// 检查所有权限是否都被授予
val allGranted = permissions.all { it.value }
if (allGranted) {
setupAppWithAllPermissions()
} else {
// 处理未全部授予的情况
showSomePermissionsDenied()
}
}
private fun requestMultiplePermissions() {
// 2. 定义权限数组
val permissionsToRequest = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
)
// 3. 发起请求
requestMultiplePermissionsLauncher.launch(permissionsToRequest)
}
private fun setupAppWithAllPermissions() {
Toast.makeText(this, "All permissions granted!", Toast.LENGTH_SHORT).show()
}
// ... 其他方法
}
三、最佳实践与关键提示
- 按需请求:在用户即将使用需要权限的功能时才请求权限,而不是一进入应用就请求所有权限。
- 善用解释 (shouldShowRequestPermissionRationale):
· 此方法返回 true:用户之前拒绝过权限,你应该弹窗解释为什么需要它。
· 返回 false:可能是第一次请求,或者用户已选择“不再询问”。 - 妥善处理“不再询问”:当用户勾选“不再询问”并拒绝后,再次调用 launch() 会直接拒绝。此时应引导用户前往系统设置手动开启权限。
- 测试所有路径:务必测试用户允许、拒绝、拒绝并不再询问等多种场景,确保应用行为正确。
通过遵循这套现代化方案,你的应用可以以一种用户友好、稳定且易于维护的方式处理Android运行时权限。