Dangerous Permission是Android中的一种权限类别,需要在应用程序运行期间通过运行时对话框获得用户的明确同意。根据Android Developer Guide, 2024,危险权限具有ProtectionLevel dangerous,可以访问敏感数据:摄像头、麦克风、地理位置和联系人。未经用户明确同意,应用程序无法使用这些功能。
要点
Dangerous Permission是Android系统权限的一个类别,提供对用户敏感数据的访问。与普通权限不同,危险权限不会在安装时自动授予——应用程序必须通过Android 6.0 Marshmallow(API 23)引入的运行时机制在运行时明确请求它们。
明确请求的必要性源于这些权限所保护的数据的性质:用户的地理位置、个人联系人、摄像头和麦克风内容、通话记录和短信。Android将这些数据视为敏感数据,并需要用户的知情同意。根据Android Privacy Sandbox(2024),用户平均拒绝约30%的运行时请求。
Dangerous Permission的关键特性——随时撤销的可能性。用户可以进入设置——应用程序——权限,切换任何危险权限的开关。应用程序必须准备好,已授予的权限可能随时被撤销而无需重启。
dangerous保护级别在操作系统级别的系统权限定义中设置。当应用程序使用此类protectionLevel声明uses-permission时,系统将权限标记为需要运行时请求。与normal不同,dangerous权限始终显示在系统权限管理UI中,并且可以撤销。
所有危险权限根据功能特征分组到Permission Group中。例如,CAMERA和CAMERA2在CAMERA组中,ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION——在LOCATION组中。如果用户授予了组中的一个权限,同一组的其他权限将自动授予,无需额外对话框。
运行时请求是应用程序调用系统API以显示权限请求对话框的机制。用户看到一个包含权限名称和Allow和Deny按钮的模态窗口。响应后,系统使用结果调用onRequestPermissionsResult回调。
完整周期包括三个步骤:通过checkSelfPermission检查状态,在权限不存在时调用requestPermissions,以及在onRequestPermissionsResult中处理结果。状态检查是强制性的,因为用户可以随时通过设置撤销权限,未经检查调用函数将导致SecurityException。
fun checkAndRequestCameraPermission() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
openCamera()
}
else -> {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA),
REQUEST_CAMERA_CODE
)
}
}
}
结果处理在ActivityResultLauncher或onRequestPermissionsResult中进行。推荐的现代方法是使用ActivityResultContracts.RequestPermission,它提供了更干净的API,无需显式的请求代码。此合约返回Boolean——权限是否已授予。
严格在功能使用的上下文中请求危险权限,而不是在应用程序启动时。如果用户按下了摄像头按钮——请求CAMERA。如果打开了地图——请求LOCATION。上下文请求比首次启动时请求所有权限多获得两倍的授权。还建议一次不要请求超过一个权限,以便用户了解哪个功能需要访问。
Android定义了几个危险权限组,每个组包含一到多个常量。最完整的列表在Manifest.permission类中。以下是开发中使用的主要组和权限。
| Permission Group组 | 权限 | 访问API |
|---|---|---|
| CAMERA | CAMERA | Camera API, CameraX |
| LOCATION | ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION | FusedLocationProvider, Geofence |
| MICROPHONE | RECORD_AUDIO | MediaRecorder, AudioRecord |
| PHONE | READ_PHONE_STATE, CALL_PHONE, READ_CALL_LOG | TelephonyManager |
| CONTACTS | READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS | ContactsContract |
| SMS | READ_SMS, SEND_SMS, RECEIVE_SMS | SmsManager |
| STORAGE | READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE | MediaStore, File API |
| CALENDAR | READ_CALENDAR, WRITE_CALENDAR | CalendarContract |
从Android 12开始,谷歌加强了对某些权限的要求。例如,BLUETOOTH_CONNECT和BLUETOOTH_SCAN已变为危险权限,需要运行时请求。还出现了用于后台访问传感器的BODY_SENSORS_BACKGROUND权限。开发人员必须更新targetSdkVersion并在当前操作系统版本上测试请求。
Android 13(API 33)为通知(POST_NOTIFICATIONS)和媒体文件(READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO)引入了新权限,取代了通用的READ_EXTERNAL_STORAGE。现在对照片、视频和音频的访问通过专门的权限单独请求,无需单一对话框。
Dangerous和Normal Permission在授予方式、撤销可能性和用户体验方面有根本区别。Normal在安装时自动授予,Dangerous需要显式的运行时对话框。Normal不能通过设置撤销,Dangerous可以随时关闭。这种不对称性导致了不同的开发模式。
从代码角度来看,危险权限需要更多工作:checkSelfPermission、requestPermissions、拒绝处理。对于Normal来说,AndroidManifest.xml中的一行就足够了。同时,Dangerous Permission给用户控制权,这增加了信任,特别是对于摄像头或地理位置等敏感功能。
类别之间的选择不在开发人员面前——由系统决定。开发人员仅声明uses-permission,系统根据protectionLevel确定类别。然而,危险权限的请求策略会影响用户体验:频繁或不适当的对话框会降低应用程序的评级。
现代方法在Kotlin中请求权限——使用ActivityResultContracts.RequestMultiplePermissions或RequestPermission。这些合约属于androidx.activity库,提供基于lambda的干净API,无需覆盖onRequestPermissionsResult。
class CameraActivity : AppCompatActivity() {
private val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
openCamera()
} else {
showPermissionDeniedMessage()
}
}
fun requestCamera() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED ->
openCamera()
ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.CAMERA
) ->
showRationaleDialog()
else ->
requestPermissionLauncher.launch(
Manifest.permission.CAMERA
)
}
}
}
当应用程序需要同时多个危险权限时,使用RequestMultiplePermissions。合约返回Map<String, Boolean>,其中键是权限名称,值是结果。这在首次启动时需要请求CAMERA和RECORD_AUDIO进行视频录制时很方便。
如果用户拒绝了请求,shouldShowRequestPermissionRationale方法返回true。这是显示解释为何需要权限的信号。最佳实践——显示带有解释和重试按钮的自定义对话框。如果用户勾选了Never Ask Again并再次拒绝了请求,shouldShowRequestPermissionRationale将返回false,需要重定向到设置。
Never Ask Again是用户在再次拒绝运行时对话框时可以设置的标志。之后,标准对话框窗口不再为此权限显示。提供访问的唯一方法——将用户重定向到系统应用程序设置。
开发人员需要区分两种拒绝场景:第一种——当shouldShowRequestPermissionRationale返回true时(用户拒绝了,但对话框仍然可以显示);第二种——当方法返回false时(Never Ask Again已激活或权限被策略阻止)。在第二种情况下,应显示打开设置按钮。
fun handlePermissionDenied(permission: String) {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this, permission
)) {
showRationaleDialog(permission)
} else {
showSettingsRedirectDialog(permission)
}
}
private fun showSettingsRedirectDialog(permission: String) {
AlertDialog.Builder(this)
.setTitle("访问被拒绝")
.setMessage(
"权限已被阻止。请打开设置。"
)
.setPositiveButton("设置") { _, _ ->
val intent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts(
"package", packageName, null
)
)
startActivity(intent)
}
.show()
}
重要的是,如果shouldShowRequestPermissionRationale返回了false,不要再次请求权限。在这种情况下重复调用requestPermissions不会显示对话框——结果会立即返回DENIED,没有解释。用户将遇到不可理解的行为,这将对应用程序的使用体验产生负面影响。
常见问题
Dangerous Permission包括具有ProtectionLevel dangerous的权限:CAMERA、RECORD_AUDIO、ACCESS_FINE_LOCATION、READ_CONTACTS、READ_SMS、READ_CALENDAR等。完整列表在Manifest.permission类中。
使用ContextCompat.checkSelfPermission,传递上下文和权限名称。该方法返回PERMISSION_GRANTED或PERMISSION_DENIED。每次调用需要危险权限的API之前都应进行检查。
Permission Group将相关的危险权限分组。如果用户授予了组中的一个权限,其他权限将自动授予。例如,LOCATION包括ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION。
拒绝后检查shouldShowRequestPermissionRationale。如果该方法返回false且权限仍未授予——表示Never Ask Again已激活。通过带有ACTION_APPLICATION_DETAILS_SETTINGS的Intent将用户重定向到设置。
是的,它们仍然是必需的。在Android 13+上,一些权限发生了变化:POST_NOTIFICATIONS成为了单独的运行时权限,READ_EXTERNAL_STORAGE被READ_MEDIA_IMAGES取代,用于精确访问媒体文件。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。