Android 13/14 通知权限与前台服务适配指南
Android 13(API 33)引入了通知运行时权限,Android 14(API 34)进一步强化了对前台服务类型的管控。本文结合实际开发中的常见问题,系统梳理这两个版本的适配要点。
一、Android 13(API 33)核心变更
1.1 通知权限(POST_NOTIFICATIONS)
背景: Android 13 之前,应用只需在 Manifest 中声明 USE_FULL_SCREEN_INTENT 即可发送通知,用户无法按应用粒度控制通知。Android 13 引入了运行时通知权限,用户可以选择拒绝接收某个应用的通知。
权限声明:
xml
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
权限申请时机:
建议在用户能够直观理解为什么需要通知的场景下请求权限,例如用户点击"开启通知"按钮时,而不是应用启动时立即请求。
kotlin
// 检查并请求通知权限(Activity 中)
private fun requestNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
// 使用 Activity Result API(推荐)
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
// 权限已授予,可以发送通知
showNotification()
} else {
// 权限被拒绝,引导用户到设置中开启
showPermissionDeniedDialog()
}
}
用户拒绝后的处理:
kotlin
private fun showPermissionDeniedDialog() {
AlertDialog.Builder(this)
.setTitle("需要通知权限")
.setMessage("开启通知后可以及时收到消息提醒")
.setPositiveButton("去设置") { _, _ ->
// 跳转到应用设置页面
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
}
startActivity(intent)
}
.setNegativeButton("取消", null)
.show()
}
重要行为变化:
| 场景 | 行为说明 |
|---|---|
| 升级到 Android 13 的设备,应用已获得通知权限 | 系统自动授予新的通知权限,无需重新申请 |
| 全新安装的应用 | 必须主动申请,系统不会自动授予 |
| 权限被用户永久拒绝("不再询问") | 只能引导用户到系统设置中手动开启 |
调用 notify() 但没有权限 |
通知不会显示,不会抛异常(静默失败) |
1.2 精细化媒体权限
Android 13 将原来的 READ_EXTERNAL_STORAGE 拆分为三个精细化权限:
xml
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
兼容处理(同时支持旧版本和新版本):
xml
<!-- 使用 maxSdkVersion 兼容旧版本 -->
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<!-- Android 13+ 使用新权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
1.3 Wi-Fi 权限变更
Android 13 引入了 NEARBY_WIFI_DEVICES 权限,替代原来需要 ACCESS_FINE_LOCATION 才能使用 Wi-Fi API 的限制。
xml
<!-- AndroidManifest.xml -->
<uses-permission
android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />
二、Android 14(API 34)核心变更
2.1 前台服务类型必须声明
变更说明: Android 14 要求所有前台服务必须在 Manifest 中显式声明 foregroundServiceType,否则会抛异常。
Manifest 配置示例:
xml
<service
android:name=".MyForegroundService"
android:foregroundServiceType="location|dataSync"
android:exported="false" />
支持的前台服务类型:
| 类型 | 用途 | 需要额外权限 |
|---|---|---|
location |
持续获取位置(如导航) | ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION |
mediaPlayback |
媒体播放 | 无 |
mediaProjection |
屏幕录制 | 无 |
phoneCall |
通话相关 | MANAGE_OWN_CALLS |
dataSync |
数据同步(Android 15 有超时限制) | 无 |
health |
健康设备数据同步 | HIGH_SAMPLING_RATE_SENSORS |
remoteMessaging |
远程消息处理 | 无 |
systemExempted |
系统豁免(需审核) | 无 |
2.2 动态广播注册必须显式指定导出行为
变更说明: Android 14 要求使用 registerReceiver() 注册动态广播时,必须显式指定 RECEIVER_EXPORTED 或 RECEIVER_NOT_EXPORTED,否则会抛 SecurityException。
kotlin
// ✅ 正确:显式指定导出行为
// 场景1:接收其他应用发送的广播(需要导出)
val filter1 = IntentFilter("com.example.MY_ACTION")
context.registerReceiver(myReceiver, filter1, Context.RECEIVER_EXPORTED)
// 场景2:只接收应用内部广播(不需要导出,更安全)
val filter2 = IntentFilter("com.example.INTERNAL_ACTION")
context.registerReceiver(myReceiver, filter2, Context.RECEIVER_NOT_EXPORTED)
// ❌ 错误:不指定导出行为(Android 14 上会崩溃)
context.registerReceiver(myReceiver, filter)
// java.lang.SecurityException: registerReceiver must specify exported behavior
LocalBroadcastManager 已废弃的替代方案:
kotlin
// 使用 LocalBroadcastManager 的方式(已废弃)
LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter)
// ✅ 推荐替代方案1:使用 RECEIVER_NOT_EXPORTED
context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED)
// ✅ 推荐替代方案2:使用 LiveData / StateFlow(应用内通信)
// ✅ 推荐替代方案3:使用 SharedFlow(事件总线)
2.3 OpenJDK 17 行为变更
Android 14 核心库对齐 OpenJDK 17,以下行为需要注意:
UUID 生成变化
kotlin
// Android 14 之前:UUID 生成使用随机数字
// Android 14+:UUID 生成使用了新的实现,但仍然是符合 RFC 4122 的
// 一般不影响业务,但如果依赖 UUID 的具体格式做解析,需要测试
val uuid = UUID.randomUUID().toString()
正则表达式变化
kotlin
// Android 14 使用了更新的正则表达式引擎
// 某些边缘情况的正则匹配行为可能变化
// 建议对依赖正则的核心逻辑进行回归测试
val regex = Regex("\\s+")
val result = regex.replace(input, " ")
2.4 更严格的 Intent 过滤规则
Android 14 对隐式 Intent 和 Intent 过滤器加强了限制:
xml
<!-- Android 14 要求 Intent 过滤器必须显式声明 exported -->
<receiver
android:name=".MyReceiver"
android:exported="true"> <!-- 必须显式声明 -->
<intent-filter>
<action android:name="com.example.MY_ACTION" />
</intent-filter>
</receiver>
三、版本适配策略建议
3.1 版本判断的最佳实践
kotlin
// ✅ 推荐:使用 VERSION_CODES 常量,可读性好
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Android 13+ 的逻辑
}
// ❌ 不推荐:直接使用数字,可读性差
if (Build.VERSION.SDK_INT >= 33) {
// ...
}
3.2 Gradle 配置建议
gradle
// gradle.properties
android.useAndroidX=true
android.enableJetifier=true
// app/build.gradle
android {
compileSdk 34 // 或 35,建议紧跟最新稳定版
defaultConfig {
minSdk 21
targetSdk 34 // 逐步升级,不要跨太多版本
}
buildFeatures {
viewBinding true
dataBinding true
}
}
3.3 分阶段升级建议
| 阶段 | targetSdk 版本 | 说明 |
|---|---|---|
| 第一阶段 | 33(Android 13) | 适配通知权限,风险较小 |
| 第二阶段 | 34(Android 14) | 适配前台服务类型 + 广播注册 |
| 第三阶段 | 35(Android 15) | 适配 Edge-to-Edge + 16KB 页面 |
四、常见问题 FAQ
Q:通知权限被用户拒绝后,如何再次请求?
A:如果用户只是拒绝(没有勾选"不再询问"),下次调用 requestPermissionLauncher.launch() 时会再次弹出系统对话框。如果用户勾选了"不再询问",只能通过 Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 跳转到应用设置页面,引导用户手动开启。
Q:Android 14 的前台服务类型可以设置多个吗?
A:可以。在 Manifest 中使用竖线分隔,例如 android:foregroundServiceType="location|dataSync"。但需要在启动前台服务时通过 ServiceInfo.FOREGROUND_SERVICE_TYPE_ 常量指定本次启动的具体类型。
Q:迁移到 Android 14 后,以前用 LocalBroadcastManager 的代码怎么办?
A:LocalBroadcastManager 已废弃,推荐用 RECEIVER_NOT_EXPORTED 注册广播,或者迁移到 StateFlow / SharedFlow 实现应用内事件通信。
五、参考资源
如有遗漏或错误,欢迎在评论区指正。如果本文对你有帮助,欢迎点赞收藏。