Android 13/14 通知权限与前台服务适配指南

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 实现应用内事件通信。


五、参考资源


如有遗漏或错误,欢迎在评论区指正。如果本文对你有帮助,欢迎点赞收藏。

相关推荐
Dovis(誓平步青云)41 分钟前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码2 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程6 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程7 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程7 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程8 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓8 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程8 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc1238 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium