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_LOCATIONACCESS_COARSE_LOCATION
mediaPlayback 媒体播放
mediaProjection 屏幕录制
phoneCall 通话相关 MANAGE_OWN_CALLS
dataSync 数据同步(Android 15 有超时限制)
health 健康设备数据同步 HIGH_SAMPLING_RATE_SENSORS
remoteMessaging 远程消息处理
systemExempted 系统豁免(需审核)

2.2 动态广播注册必须显式指定导出行为

变更说明: Android 14 要求使用 registerReceiver() 注册动态广播时,必须显式指定 RECEIVER_EXPORTEDRECEIVER_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 实现应用内事件通信。


五、参考资源


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

相关推荐
程序员陆业聪1 小时前
当AI学会了混淆代码:LLM辅助混淆 vs R8,Android安全的下一个十字路口
android
yubin12855709231 小时前
mysql正则函数REGEXP
android·数据库·mysql
我命由我123451 小时前
Android Framework P2 - 开机启动 Zygote 进程、Zygote 的预加载机制
android·java·开发语言·python·java-ee·intellij-idea·zygote
我命由我123451 小时前
Android Framework P1 - 低配学习 Framework 方案、开机启动 Init 进程
android·c语言·c++·学习·android jetpack·android-studio·android runtime
aqi001 小时前
FFmpeg开发笔记(一百零二)国产的音视频移动开源工具FFmpegAndroid
android·ffmpeg·kotlin·音视频·直播·流媒体
星间都市山脉2 小时前
Android 谷歌 CTS 完整测试
android
nianniannnn2 小时前
快应用day2项目架构
android·快应用
用户83352502537853 小时前
ViewModel详细解析
android
问心无愧05133 小时前
ctf show web入门91
android·前端·笔记
YF02113 小时前
Android App 高效升级指南:OkDownload 多线程断点续传与全版本安装适配
android·okhttp·app