android 11正式发布后实战项目避坑指南
刚把网上抄的 Android 11 适配代码粘进工程,编译报错,运行闪退。那种“复制来的代码跑不通不知道怎么调”的绝望感,每个做安卓的老兵都经历过。别慌,这不是你的错,是 Android 11 的隐私权限模型变了,老教程里的写法在实战项目里已经失效。今天不讲虚的,直接拿一个真实的后台服务监控项目为例,手把手教你怎么从零搭建一个能跑通、过审、不出错的 Android 11 适配模块。
项目目标
我们要做一个轻量级的实战项目:App 在后台保活,并在 Android 11 环境下正常显示通知。很多开发者卡在“权限申请”和“通知渠道”这两块。Android 11 强制要求 POST_NOTIFICATIONS 运行时权限,且通知必须指定 NotificationChannel。
目标明确:权限合规:动态申请通知权限,处理用户拒绝的情况。
通知兼容:适配 Android 8.0+ 的 Channel 机制,重点解决 Android 11 的默认行为变更。
后台稳定:确保在 Android 11 的电池优化策略下,服务不轻易被杀。目录结构
为了代码清晰,我们采用标准的模块化结构。以下是核心文件路径:
app/
├── src/main/java/com/example/android11demo/
│ ├── MainActivity.kt # 入口,处理权限请求
│ ├── NotificationHelper.kt # 通知工具类,核心适配逻辑
│ └── BackgroundService.kt # 后台服务,模拟保活
├── res/layout/
│ └── activity_main.xml # 简单布局
└── AndroidManifest.xml # 权限声明重点看 NotificationHelper.kt,这是整个实战项目中最容易踩坑的地方。
核心代码实现
1. AndroidManifest.xml 配置
在 AndroidManifest.xml 中,除了传统的 INTERNET 等权限,必须显式声明通知权限。虽然 Android 8.0 引入了 Channel,但 Android 11 对权限检查更严格。
uses-permission android:name=android.permission.INTERNET /
!-- Android 13+ 强制,但为了兼容 Android 11 的预检逻辑,建议加上 --
uses-permission android:name=android.permission.POST_NOTIFICATIONS /
uses-permission android:name=android.permission.FOREGROUND_SERVICE /
uses-permission android:name=android.permission.WAKE_LOCK /applicationandroid:allowBackup=trueandroid:icon=@mipmap/ic_launcherandroid:label=@string/app_nameandroid:supportsRtl=trueandroid:theme=@style/Theme.Android11Demoactivity android:name=.MainActivity android:exported=trueintent-filteraction android:name=android.intent.action.MAIN /category android:name=android.intent.category.LAUNCHER //intent-filter/activityservice android:name=.BackgroundService android:exported=false /
/application注意 android:exported 属性。Android 12 开始强制要求声明,但在 Android 11 的实战项目中,提前加上可以避免后续升级时的崩溃。
2. NotificationHelper.kt:通知适配核心
这是关键。很多教程直接 notify(1, builder.build()),在 Android 11 上如果没初始化 Channel,通知直接不显示。
package com.example.android11demoimport android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompatobject NotificationHelper {private const val CHANNEL_ID = android11_demo_channelprivate const val CHANNEL_NAME = 后台任务通知private const val NOTIFICATION_ID = 1001/*** 初始化通知渠道。* 必须在创建通知前调用。* 注意:Channel 一旦创建,用户修改后无法代码覆盖,只能新建 ID。*/fun createNotificationChannel(context: Context) {if (Build.VERSION.SDK_INT = Build.VERSION_CODES.O) {val importance = NotificationManager.IMPORTANCE_LOW // 静默,不响铃val channel = NotificationChannel(CHANNEL_ID,CHANNEL_NAME,importance).apply {description = 用于显示后台服务状态enableLights(false)enableVibration(false)}val manager = context.getSystemService(NotificationManager::class.java)manager.createNotificationChannel(channel)}}/*** 构建并发送通知。* 兼容 Android 11 及更高版本。*/fun startForegroundNotification(context: Context, title: String, content: String) {val intent = Intent(context, MainActivity::class.java).apply {action = Intent.ACTION_MAINcategory = Intent.CATEGORY_LAUNCHER}val pendingIntent = PendingIntent.getActivity(context,0,intent,PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT // Android 12+ 必须指定可变性)val builder = NotificationCompat.Builder(context, CHANNEL_ID).setSmallIcon(android.R.drawable.ic_dialog_info) // 注意:必须用系统图标或自适应图标.setContentTitle(title).setContentText(content).setOngoing(true) // 持续通知,不可滑动取消.setAutoCancel(false).setContentIntent(pendingIntent)// Android 13+ 需要检查权限,Android 11 虽不强制运行时检查,但建议预检if (Build.VERSION.SDK_INT = Build.VERSION_CODES.TIRAMISU) {if (context.checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) != android.content.pm.PackageManager.PERMISSION_GRANTED) {// 提示用户去设置开启return}}val manager = context.getSystemService(NotificationManager::class.java)manager.notify(NOTIFICATION_ID, builder.build())}
}逐行讲解关键点:NotificationChannel 初始化:Android 8.0+ 必须。Android 11 虽然不强制运行时权限,但 Channel 机制是基础。IMPORTANCE_LOW 适合后台服务,不打扰用户。
PendingIntent 的 FLAG_IMMUTABLE:这是 Android 12 的硬性要求,但 Android 11 的实战项目中提前适配,避免版本升级时的 SecurityException。
setOngoing(true):防止用户滑动关闭通知导致服务被杀。这是后台保活的关键细节。3. MainActivity.kt:权限动态申请
Android 11 开始,虽然 POST_NOTIFICATIONS 是 Android 13 才完全强制运行时申请,但为了代码健壮性,我们统一处理。
package com.example.android11demoimport android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompatclass MainActivity : AppCompatActivity() {private val requestNotificationPermission =registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean -if (isGranted) {startBackgroundService()} else {// 处理拒绝:引导用户去设置showPermissionDeniedDialog()}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)NotificationHelper.createNotificationChannel(this)checkAndRequestPermission()}private fun checkAndRequestPermission() {// Android 13+ 才真正需要运行时检查 POST_NOTIFICATIONS// 但 Android 11 也需要检查其他权限,如 WAKE_LOCK 等(通常安装时授予)if (Build.VERSION.SDK_INT = Build.VERSION_CODES.TIRAMISU) {val permissionStatus = ContextCompat.checkSelfPermission(this,Manifest.permission.POST_NOTIFICATIONS)if (permissionStatus != PackageManager.PERMISSION_GRANTED) {requestNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)} else {startBackgroundService()}} else {// Android 11 及以下,通知权限通常默认授予,但需确保渠道已创建startBackgroundService()}}private fun startBackgroundService() {val intent = Intent(this, BackgroundService::class.java)startForegroundService(intent)}private fun showPermissionDeniedDialog() {// 实际项目中应弹窗引导// 这里简化处理,仅打印日志android.util.Log.d(MainActivity, Notification permission denied)}
}避坑点:registerForActivityResult 必须在 onCreate 之前或之中注册,不能在 onResume 中注册,否则在 Android 11+ 某些机型上会崩溃。
对于 Android 11,POST_NOTIFICATIONS 并非强制运行时权限(那是 Android 13 的事),但代码中保留检查逻辑是为了向前兼容。在 Android 11 上,这段代码会直接走 else 分支,直接启动服务。4. BackgroundService.kt:前台服务启动
Android 11 对前台服务有更严格的限制,必须指定类型(虽然 Android 14 才强制类型,但 Android 11 已加强检查)。
package com.example.android11demoimport android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Logclass BackgroundService : Service() {override fun onCreate() {super.onCreate()Log.d(BackgroundService, Service created)// 启动前台服务,必须立即调用 startForegroundstartForeground(1001, NotificationHelper.buildOngoingNotification(this, 服务运行中, 后台任务正在执行))}override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {Log.d(BackgroundService, Service started)// 返回 START_STICKY,服务被杀后系统尝试重建return START_STICKY}override fun onBind(intent: Intent?): IBinder? {return null}override fun onDestroy() {super.onDestroy()Log.d(BackgroundService, Service destroyed)}
}注意:startForeground 必须在服务启动后 5 秒内调用,否则抛出 ForegroundServiceDidNotStartInTimeException。这是 Android 11 常见的崩溃原因之一。
运行与测试
1. 环境准备Android Studio 2022.1+ (Hedgehog)
Min SDK: 21 (Android 5.0)
Target SDK: 31 (Android 12) 或 30 (Android 11)
测试设备:真机 Android 11 (OnePlus 8, Pixel 4a 等)2. 测试步骤安装 APK 到 Android 11 设备。
首次启动,观察是否弹出权限请求(Android 13+ 会弹,Android 11 通常不弹通知权限,但需确认其他权限)。
启动 App,检查状态栏是否出现“服务运行中”通知。
关键测试:锁定屏幕,等待 10 分钟,解锁屏幕,检查服务是否仍在运行(查看 Logcat)。
电池优化测试:进入设置 - 电池 - 后台管理,将 App 设为“无限制”。再次测试保活效果。3. 常见报错与解决SecurityException: Targeting S+ (version 31 and above) apps that include the新媒体...原因:PendingIntent 未指定 FLAG_IMMUTABLE 或 FLAG_MUTABLE。
解决:在 PendingIntent.getActivity 中加上 PendingIntent.FLAG_IMMUTABLE。ForegroundServiceDidNotStartInTimeException原因:startForeground 调用太晚。
解决:在 onCreate 中立即调用,不要延迟。通知不显示原因:NotificationChannel 未创建或 IMPORTANCE 过低。
解决:确保 createNotificationChannel 在 onCreate 中调用,且 IMPORTANCE 至少为 LOW。优化扩展
1. 处理用户拒绝通知权限
在 Android 13+,如果用户拒绝通知权限,App 无法发送通知。在 Android 11 中,虽然不强制运行时申请,但用户可能在设置中手动关闭通知。
解决方案:在 MainActivity 中定期检查通知权限状态。
private fun checkNotificationPermissionStatus() {if (Build.VERSION.SDK_INT = Build.VERSION_CODES.TIRAMISU) {val isGranted = ContextCompat.checkSelfPermission(this,Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTEDif (!isGranted) {// 显示 UI 提示,引导用户去设置showPermissionGuideDialog()}}
}2. 使用 WorkManager 替代 Service
对于非实时性的后台任务,建议使用 WorkManager。它比 Service 更符合 Android 11+ 的后台限制策略,且能自动处理任务重试、网络状态等。
val workRequest = OneTimeWorkRequestBuilderMyWorker().setConstraints(Constraints.Builder().setRequiresNetworkConnected(true).build()).build()
WorkManager.getInstance(context).enqueue(workRequest)3. 多进程兼容
如果 App 使用多进程,确保每个进程都初始化了 NotificationChannel。Android 11 中,不同进程的通知渠道是隔离的。
小结
Android 11 的正式发布标志着隐私权限模型的进一步收紧。在实战项目中,不能只盯着 Android 13 的新特性,而要关注 Android 11 的“隐性变化”:PendingIntent 的不可变性、前台服务的启动时限、通知渠道的强制化。
很多开发者把代码从 Android 10 迁移到 Android 11 时,因为忽略了 FLAG_IMMUTABLE 或 startForeground 的时机,导致线上崩溃。记住:代码能跑通不等于代码健壮。在 Android 11 环境下,必须真机测试,特别是低电量模式和后台限制场景。
你更常用哪种写法?是直接硬编码 PendingIntent 的 Flag,还是封装一个工具类自动判断版本?评论区交流你的适配经验,特别是那些被 Android 11 坑过的案例。