AndroidManifest Permissions 是 AndroidManifest.xml 文件中的权限声明,用于确定应用程序可以访问哪些系统资源和数据。Android 要求在使用相应 API 之前,必须在清单中声明每个权限:从相机和地理位置到发送短信和访问联系人。根据 Android Developer Documentation,每个权限都分为四个保护级别之一:normal、dangerous、signature 和 special。
要点
AndroidManifest Permissions 是 Android 的安全机制,用于控制应用对受保护数据和系统功能的访问。每个应用必须使用 <uses-permission> 元素在 AndroidManifest.xml 文件中声明所需的权限。如果不声明,调用相应的 API 将以安全错误 SecurityException 告终。
Android 的权限模型经历了几个发展阶段。在 Android 6.0(API 23)之前,所有权限都在安装时授予 — 用户看到完整列表并同意或拒绝安装应用。从 Android 6.0 开始,dangerous 级别的权限在运行时(Runtime Permissions)请求,为用户提供更灵活的控制。
权限分为四个保护级别:normal(安装时自动授予)、dangerous(需要运行时请求)、signature(仅对使用相同证书签名的应用可用)和 special(需要在设置中单独启用)。每个级别都有自己的授予和撤销机制。
根据 Google I/O 2024 的信息,Android 15 计划引入更细粒度的权限 — 用户可以只授予对媒体库中特定文件的访问权限,而不是整个库。这延续了 Android 默认情况下最小化提供数据量的趋势。
与所有权限都在运行时请求的 iOS 不同,Android 将权限分为安装时(install-time)和运行时(runtime)。normal 级别在安装时自动授予,无需通知用户。dangerous 级别需要明确的对话框,就像在 iOS 中一样。
另一个区别:在 Android 中,权限被分组为组(permission groups)。如果用户同意访问相机,应用会自动获得麦克风的访问权限 — 它们属于同一个 MICROPHONE 组。在 iOS 中,每个权限都独立于组单独请求。
| Android 版本 | 权限模型的变化 |
|---|---|
| Android 1.0–5.x | 所有权限在安装时授予(install-time) |
| Android 6.0(API 23) | 为 dangerous 级别引入 Runtime Permissions |
| Android 10(API 29) | Scoped Storage — 限制对文件系统的访问 |
| Android 11(API 30) | 权限自动重置 — 未使用的权限被重置 |
| Android 14(API 34) | 媒体访问的运行时权限(照片、视频、音频) |
Android 为权限定义了四个保护级别(protection levels),每个级别都有自己的授予规则。让我们详细查看每个级别。
Normal 权限在安装应用时自动授予,无需通知或请求用户。它们涵盖对低风险功能的访问,这些功能不会威胁用户隐私:INTERNET、ACCESS_NETWORK_STATE、VIBRATE、BLUETOOTH。用户看不到同意对话框 — 权限被视为通过安装事实而授予。
开发人员无需为 normal 权限在代码中处理请求 — 只需在清单中声明即可。但是,在 Android 12+ 中从 Google Play 安装时,用户会看到 “权限” 选项卡,其中列出了所有 normal 权限,这提高了透明度。根据 Statista(2024) 的数据,Google Play 中超过 90% 的应用都使用 INTERNET 作为最常见的 normal 权限。
Dangerous 权限涵盖对可能侵犯隐私的数据和功能的访问:相机、麦克风、地理位置、联系人、短信、电话、日历、身体传感器。这些权限需要两步机制:在清单中声明 + 通过 ActivityCompat.requestPermissions() 在运行时请求。
用户可以拒绝授予 dangerous 权限,应用必须正确处理此场景。在 Android 11+ 中,如果用户两次拒绝,后续请求不会显示系统对话框 — 系统自动返回 DENIED。在这种情况下,需要将用户引导至设置。
Signature 级别 — 如果应用与系统或定义权限的其他应用使用相同的证书签名,则权限会自动授予。用于系统和企业应用。示例:BIND_ACCESSIBILITY_SERVICE — 仅系统应用可用。
Special 级别(SYSTEM_ALERT_WINDOW、WRITE_SETTINGS、REQUEST_INSTALL_PACKAGES、MANAGE_EXTERNAL_STORAGE) — 需要用户通过系统设置单独操作。应用可以使用 Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)打开设置页面。Google Play 限制使用 special 权限,并在发布时要求在表单中提供理由。
从 Android 6.0 开始,所有 dangerous 权限都需要在运行时请求。让我们看看在 Kotlin 中使用运行时权限的完整工作流程。
在调用需要 dangerous 权限的 API 之前,始终通过 ContextCompat.checkSelfPermission() 检查当前状态。如果状态为 PERMISSION_GRANTED — 可以调用 API。如果为 PERMISSION_DENIED — 需要通过 ActivityResultContract RequestPermission(AndroidX)或已弃用的 requestPermissions() 请求权限。
建议使用 ActivityResultContracts.RequestMultiplePermissions 同时请求多个权限。Google 建议将相关权限(例如,用于录制视频的相机 + 麦克风)分组到一个对话框中,以便用户看到请求的完整上下文。
import android.Manifest
import android.content.pm.PackageManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
class CameraActivity : AppCompatActivity() {
private val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
openCamera()
} else {
explainWhyPermissionNeeded()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
checkCameraPermission()
}
private fun checkCameraPermission() {
when {
ContextCompat.checkSelfPermission(this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
openCamera()
}
shouldShowRequestPermissionRationale(
Manifest.permission.CAMERA
) -> {
showRationale()
}
else -> {
requestPermissionLauncher.launch(
Manifest.permission.CAMERA
)
}
}
}
}
如果用户两次拒绝权限,Android 将请求转为 “Never ask again” 状态。在这种情况下,shouldShowRequestPermissionRationale() 返回 false,系统对话框将不会显示。应用必须通过 Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)将用户引导至系统设置。
重要提示:不要在第一次拒绝后立即显示建议打开设置的对话框 — 这被视为攻击性行为。使用 shouldShowRequestPermissionRationale() 来确定是否需要显示解释。Material Design 指南建议显示一个包含访问价值解释的屏幕,而不仅仅是一个 “打开设置” 按钮。
private fun openAppSettings() {
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", packageName, null)
).also { intent ->
startActivity(intent)
}
}
private fun showPermissionSettings() {
AlertDialog.Builder(this)
.setTitle("相机访问权限")
.setMessage("请在设置中允许相机访问权限,"
+ "以便拍摄个人资料照片")
.setPositiveButton("打开设置") { _, _ ->
openAppSettings()
}
.setNegativeButton("取消", null)
.show()
}
AndroidManifest.xml 文件包含应用使用的每个权限的 <uses-permission> 元素。权限在 <manifest> 级别、<application> 元素之前声明。
每个权限都声明为一个单独的 <uses-permission> 元素,带有 android:name 属性,指示权限的完整名称。对于在特定 Android 版本中出现的权限,使用 maxSdkVersion 属性将声明限制为仅需要的版本 — 这提高了兼容性。
例如,WRITE_EXTERNAL_STORAGE 权限在 Android 10+(Scoped Storage)上不需要,因此指定 maxSdkVersion=“28”(Android 9)。这可以防止在新版本上来自用户的不必要问题。Android Studio 通过 Lint 警告推荐的 maxSdkVersion。
<!-- AndroidManifest.xml -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Normal permissions (install-time) -->
<uses-permission
android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Dangerous permissions (runtime) -->
<uses-permission
android:name="android.permission.CAMERA" />
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Legacy storage permission limited to API 28 and below -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<application>
<!-- ... -->
</application>
</manifest>
<uses-feature> 元素指定应用需要特定的硬件(相机、GPS、NFC)。android:required=“false” 属性允许在没有此硬件的设备上安装应用 — 可用性检查在代码中执行。如果 required=“true”,Google Play 会过滤应用,使其不适用于不合适的设备。
建议对所有硬件功能设置 required=“false”,并通过 PackageManager.hasSystemFeature() 以编程方式检查可用性。这可以扩展应用的受众。唯一的例外是当功能对应用运行至关重要时(没有 GPS 的出租车订车应用没有意义)。
正确处理权限是 Android 应用质量的关键方面。让我们看看主要建议和典型错误。
仅请求应用运行真正需要的权限。每个额外的权限都会降低安装转化率并增加拒绝数量。Google Play Console 显示有多少用户因权限集而放弃安装。根据 AppBrain(2024) 的数据,具有 10+ dangerous 权限的应用的安装量少 35%。
定期审查权限列表。删除未使用的权限,尤其是在过渡到某些权限变为可选的较新 Android 版本时。例如,随着 Android 13+ 中照片选择器(ActivityResultContracts.PickVisualMedia)的出现,无需 dangerous 权限 READ_MEDIA_IMAGES 即可访问媒体库。
在请求 dangerous 权限之前,向用户显示一个屏幕,解释为什么需要此权限及其提供的价值。Material Design 建议使用带有图标、简短文字和 “继续” 按钮的底部导航栏或对话框。与直接请求相比,解释可将同意率提高 20–30%。
在调用 launch() 之前检查 shouldShowRequestPermissionRationale()。如果为 true — 显示解释。如果为 false — 要么权限已授予,要么用户已永久拒绝(never ask again)。在后一种情况下,显示 “打开设置” 按钮,不要重复请求。
测试所有可能的场景:授予权限、拒绝、永久拒绝、在设置中撤销权限、重置权限(Android 11+ 自动重置)。每个场景都必须处理,不会崩溃且不会丢失数据。Android 测试指南建议使用 TestPermission 库来自动化测试。
特别注意用户在应用运行时撤销权限的场景(最小化应用 → 设置 → 撤销)。返回应用时,通过 onResume() 重新检查所有权限。不要依赖权限状态的缓存 — 用户可以随时更改它们。
常见问题
是的,如果 SDK 在其清单中包含权限,则在构建时会合并到应用的清单中。您可以使用 AndroidManifest.xml 中的 tools:node=“remove” 禁用 SDK 的不必要权限。
在没有权限的情况下调用 API 会导致 SecurityException,从而导致应用崩溃。在使用相应 API 之前始终检查权限状态,并正确处理拒绝。
在设备设置中:设置 → 应用 → [您的应用] → 权限。要重置所有权限,请使用 adb 命令:adb shell pm reset-permissions。
可以,使用 Fragment 或 Service 中的 ActivityResultLauncher。但是,请求对话框始终需要 Activity 的 UI 上下文。对于 Service,您可以显示带有 Intent 的通知,该 Intent 打开包含请求的 Activity。
例如,WRITE_EXTERNAL_STORAGE 在 Android 10+(Scoped Storage)上不需要。通过指定 android:maxSdkVersion=“28”,您排除了在新版本上的权限声明,这提高了兼容性并减少了请求的权限列表。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。