尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Android通知开发全解析:从渠道创建到后台服务通知实战

Android通知开发全解析:从渠道创建到后台服务通知实战 1. 从“烦人”到“核心”为什么通知是Android应用的门面如果你开发过Android应用或者只是作为一个普通用户你一定对通知Notification又爱又恨。爱的是它能及时告诉你外卖到哪了、谁给你发了消息恨的是那些关不掉、看不懂、乱推送的“牛皮癣”通知恨不得把整个手机都砸了。作为一个在移动端摸爬滚打多年的开发者我越来越觉得通知功能的好坏直接决定了一款应用在用户心中的“体感”。它不是一个简单的弹窗而是应用与用户在非活跃状态下沟通的唯一桥梁是用户体验的“最后一公里”。很多开发者尤其是刚入行的朋友对通知的理解还停留在“显示一段文字和图标”的层面。照着官方文档的示例代码抄一遍能弹出来就万事大吉。结果就是要么通知样式简陋、交互单一要么在Android 8.0API 26以上的系统上因为没创建通知渠道Notification Channel而直接“哑火”要么在后台被系统各种限制导致发不出来。更别提那些需要精确控制显示时机、支持复杂操作如回复、进度条、适配不同系统版本从Android 4.4到Android 14的进阶需求了。这篇文章我想彻底拆解Android通知的方方面面。我不会只给你一堆API列表那和看官方文档没区别。我会从一个功能完整的现代应用通知需求出发带你走过从创建、管理、交互到适配和优化的完整路径。你会明白为什么在Android 8.0之后通知渠道成了“命门”如何构建一个既美观又实用的通知布局如何处理前台服务、定时任务等场景下的通知以及如何避开那些我亲自踩过的、文档里不会写的“深坑”。我们的目标是让你看完之后不仅能做出一个“能用”的通知更能做出一个“好用”、甚至让用户觉得“贴心”的通知系统。2. 基石与革命理解通知渠道Notification Channel与兼容性架构在深入代码之前我们必须先理解Android通知体系里最重要的一个概念通知渠道Notification Channel。这是Android 8.0Oreo引入的一项革命性设计彻底改变了应用管理通知的方式。2.1 通知渠道的本质用户赋权与分类管理在Android 8.0之前用户对应用通知的控制权是二元的要么全部允许要么全部禁止。这很不合理比如用户可能希望收到重要的聊天消息但不想被营销推广打扰。通知渠道解决了这个问题。它的核心思想是由应用开发者预先定义好不同类别的通知渠道然后由用户决定每个渠道的开关、响铃、震动等具体行为。你可以把通知渠道想象成电视台的不同频道。你开发者开设了“新闻频道”、“体育频道”、“娱乐频道”。用户可以选择订阅“新闻频道”和“体育频道”并把“新闻频道”设置为重要提醒响铃震动把“体育频道”设置为静音提醒同时彻底关闭“娱乐频道”。这样一来控制粒度从“整个电视台”细化到了“单个频道”用户体验得到了巨大提升。对于开发者而言这意味着责任也更重了。你不能再胡乱发送通知了。你必须仔细思考你的应用有哪些不同类型的通知并为它们创建合适的渠道。如果分类不合理用户可能会因为讨厌某一类通知而关闭整个渠道甚至卸载你的应用。2.2 创建与管理通知渠道的实战代码创建通知渠道的代码并不复杂但时机和逻辑有讲究。渠道一旦创建其大部分属性如名称、描述、重要性在系统设置中将对用户可见且应用无法再修改除了名称可以动态更新。因此通常建议在应用启动时例如Application的onCreate方法中检查并创建所需的渠道。下面是一个创建两个典型渠道的示例import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.os.Build object NotificationChannelManager { const val CHANNEL_ID_HIGH high_priority_channel // 高重要性渠道ID如私信、支付成功 const val CHANNEL_ID_LOW low_priority_channel // 低重要性渠道ID如新闻推送、功能推荐 fun createNotificationChannels(context: Context) { // 仅需在Android 8.0及以上版本创建渠道 if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { val notificationManager context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // 1. 创建高重要性渠道 val highPriorityChannel NotificationChannel( CHANNEL_ID_HIGH, 重要通知, // 用户可见的渠道名称 NotificationManager.IMPORTANCE_HIGH // 重要性级别 ).apply { description 来自好友的消息、交易提醒等重要信息 // 用户可见的渠道描述 enableLights(true) // 开启指示灯如果设备支持 lightColor Color.RED // 指示灯颜色 enableVibration(true) // 开启震动 vibrationPattern longArrayOf(0, 500, 200, 500) // 震动模式等待0ms震动500ms暂停200ms震动500ms // 可以设置声音 setSound(...) } // 2. 创建低重要性渠道 val lowPriorityChannel NotificationChannel( CHANNEL_ID_LOW, 一般通知, NotificationManager.IMPORTANCE_LOW ).apply { description 新闻资讯、活动推荐等 enableLights(false) enableVibration(false) // 低重要性渠道通常不震动、不响铃仅在下拉栏中显示 } // 3. 一次性提交给系统 notificationManager.createNotificationChannels(listOf(highPriorityChannel, lowPriorityChannel)) } } }关键点解析与避坑经验渠道ID (CHANNEL_ID): 是一个字符串常量在应用内必须唯一。它是你后续发送通知时指定目标渠道的凭据。建议定义为常量并集中管理。重要性 (IMPORTANCE): 这是一个核心参数它决定了通知的默认干扰级别。从高到低有IMPORTANCE_HIGH: 紧急通知会弹出横幅Heads-up Notification并可能发出声音和震动。IMPORTANCE_DEFAULT: 默认级别有声音但可能不弹出横幅取决于用户设置。IMPORTANCE_LOW: 低级别无声音无震动仅出现在状态栏和下拉栏。IMPORTANCE_MIN: 最低级别无声音无震动可能不会出现在状态栏只在下拉栏的底部折叠显示。注意IMPORTANCE_HIGH在Android 10及以上版本的行为有调整可能不会在所有情况下都弹出横幅最终行为尊重用户的系统级“勿扰”等设置。渠道属性锁定enableVibration,enableLights,setSound,setVibrationPattern等属性在渠道创建后应用无法再通过代码修改。但用户可以在系统设置里覆盖它们。例如即使用户关闭了某个渠道的震动你的代码中enableVibration(true)的调用依然有效表示你“建议”震动但最终是否震动的决定权在用户手里。这是一个重要的权限让渡。创建时机虽然通常在应用启动时创建但更健壮的做法是“惰性创建检查”。即在每次需要发送通知前检查渠道是否存在若不存在则创建。这能避免因渠道未创建导致通知发送失败。可以使用notificationManager.getNotificationChannel(channelId)来检查。2.3 构建向后兼容的通知创建器有了渠道我们终于可以创建通知本身了。从Android 3.0 (Honeycomb) 引入NotificationCompat.Builder开始到现在的NotificationCompat库Google一直推荐我们使用支持库现在是AndroidX来构建通知以处理复杂的版本兼容问题。绝对不要直接使用Notification.Builder一定要使用androidx.core.app.NotificationCompat.Builder。它会自动帮你处理从旧版本到最新版本的所有差异。下面是一个创建基础通知的兼容性写法import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat fun createBasicNotification(context: Context, channelId: String): Notification { // 1. 构建PendingIntent定义通知点击后的行为 val intent Intent(context, MainActivity::class.java).apply { flags Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } val pendingIntent PendingIntent.getActivity( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE // FLAG_IMMUTABLE在Android 6.0以上推荐使用 ) // 2. 使用NotificationCompat.Builder return NotificationCompat.Builder(context, channelId) // 第二个参数必须传入渠道ID .setSmallIcon(R.drawable.ic_notification_small) // 小图标必须使用alpha通道的纯色图标这是强制要求 .setContentTitle(新的消息) .setContentText(你好这是一条测试通知的内容。) .setContentIntent(pendingIntent) // 设置点击意图 .setPriority(NotificationCompat.PRIORITY_DEFAULT) // 设置优先级兼容旧版本 .setAutoCancel(true) // 点击后自动消失 .build() }这里有几个极易出错的关键细节setSmallIcon是强制的如果不设置通知将无法发出并会抛出异常。这个图标必须是只有alpha通道的纯色图标矢量图或PNG系统会用它来着色。很多开发者用错了彩色图标导致在状态栏显示一个灰色方块。Builder的构造函数在Android 8.0及以上NotificationCompat.Builder(context, channelId)这个构造函数是必须的channelId参数不能省略。对于Android 8.0以下的版本支持库会忽略这个参数但写上也无妨保证了代码的一致性。PendingIntent的FlagsFLAG_IMMUTABLE是从Android 6.0 (API 23) 开始引入并在Android 12 (API 31) 后对大多数PendingIntent变为强制要求除非你明确需要修改其内部Intent。它告诉系统这个PendingIntent创建后其内部Intent是不可变的这是一项重要的安全改进。为了最大兼容性通常使用FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE。setPriorityvs 渠道重要性在Android 8.0之前setPriority是控制通知打扰级别的关键。在Android 8.0之后渠道的重要性Importance取代了优先级Priority成为主要控制因素。setPriority方法仍然存在主要用于在Android 7.1及以下设备上影响通知的排序或者影响Android 8.0设备上通知在渠道内的排序如果渠道重要性允许。作为最佳实践两者都应该合理设置。3. 超越文本打造丰富交互的通知样式与内容一个只有标题和文本的通知是乏味的。现代应用的通知需要承载更多信息和交互。NotificationCompat提供了多种样式Style和组件来丰富你的通知。3.1 使用大图、收件箱与消息样式1. 大图样式 (BigPictureStyle):非常适合展示一张预览图比如社交媒体的图片分享、新闻应用的头条配图。fun createBigPictureNotification(context: Context, channelId: String): Notification { val bitmap BitmapFactory.decodeResource(context.resources, R.drawable.preview_large_image) // 从资源加载大图 val bigPictureStyle NotificationCompat.BigPictureStyle() .bigPicture(bitmap) // 设置大图 .bigLargeIcon(null) // 设置大图展开时右侧的大图标传null则使用setLargeIcon的图标 .setSummaryText(查看完整图片) // 摘要文本 return NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_notification_small) .setContentTitle(图片分享) .setContentText(我分享了一张图片) .setLargeIcon(bitmap) // 设置通知折叠时右侧的图标可选通常用图片的缩略图 .setStyle(bigPictureStyle) // 应用样式 .build() }2. 收件箱样式 (InboxStyle):用于汇总多条相似信息比如“您有3条未读消息”展开后可以显示每条消息的摘要。这是处理批量通知的优雅方式避免刷屏。fun createInboxStyleNotification(context: Context, channelId: String): Notification { val inboxStyle NotificationCompat.InboxStyle() .setBigContentTitle(3条新消息) // 展开后的大标题 .setSummaryText(exampleemail.com) // 底部的摘要 .addLine(张三项目会议改到下午3点) // 添加一行摘要 .addLine(李四需求文档已发给你) .addLine(系统你的账号在异地登录) return NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_notification_small) .setContentTitle(3条新消息) .setContentText(来自张三、李四等) .setNumber(3) // 设置角标数字如果Launcher支持 .setStyle(inboxStyle) .build() }3. 消息样式 (MessagingStyle):这是为聊天类应用量身定做的样式。它可以清晰地展示对话线程、联系人头像和每条消息支持显示“对方正在输入...”等状态交互体验最佳。fun createMessagingStyleNotification(context: Context, channelId: String): Notification { // 代表对话中的“你” val me Person.Builder() .setName(我自己) .setIcon(IconCompat.createWithResource(context, R.drawable.avatar_me)) // 设置头像 .build() // 代表对话中的对方 val friend Person.Builder() .setName(小王) .setIcon(IconCompat.createWithResource(context, R.drawable.avatar_friend)) .build() val messagingStyle NotificationCompat.MessagingStyle(me) // 以“我”为视角 .setConversationTitle(项目群聊) // 设置群聊标题如果是群聊 .addMessage(大家下午好原型图我发群里了。, System.currentTimeMillis() - 3600000, friend) // 消息内容时间戳发送者 .addMessage(收到UI部分我今晚跟进。, System.currentTimeMillis() - 1800000, me) .addMessage(后端接口预计明天提供。, System.currentTimeMillis(), friend) return NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_notification_small) .setStyle(messagingStyle) // MessagingStyle会自己处理标题和内容所以通常不需要再setContentTitle/Text .build() }3.2 添加操作按钮与直接回复静态的通知只是信息的展示而操作按钮Action让用户可以不打开应用就完成快速操作如“归档邮件”、“暂停播放”、“快捷回复”。添加操作按钮fun createNotificationWithActions(context: Context, channelId: String): Notification { // 意图1点赞操作 val likeIntent Intent(context, NotificationReceiver::class.java).apply { action ACTION_LIKE putExtra(post_id, 12345) } val likePendingIntent PendingIntent.getBroadcast( context, 0, likeIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) // 意图2评论操作跳转到Activity val commentIntent Intent(context, CommentActivity::class.java).apply { putExtra(post_id, 12345) } val commentPendingIntent PendingIntent.getActivity( context, 1, commentIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) return NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_notification_small) .setContentTitle(新的动态) .setContentText(你的好友发布了一条新状态) .addAction( R.drawable.ic_like, // 操作图标 点赞, // 操作文字 likePendingIntent ) .addAction( R.drawable.ic_comment, 评论, commentPendingIntent ) .build() }实现直接回复Direct Reply这是消息类应用的杀手锏功能。用户可以在通知栏里直接输入文字并发送无需跳转应用。实现直接回复需要几个步骤创建一个RemoteInput对象定义输入框的键和提示文字。创建一个特殊的PendingIntent通常是发送给Service或BroadcastReceiver用于接收回复内容。使用addRemoteInput方法将一个Action标记为回复动作。// 1. 定义RemoteInput的键用于后续提取输入内容 const val KEY_TEXT_REPLY key_text_reply fun createNotificationWithReply(context: Context, channelId: String): Notification { // 2. 创建RemoteInput val remoteInput: RemoteInput RemoteInput.Builder(KEY_TEXT_REPLY) .setLabel(回复消息) // 输入框的提示文字 .build() // 3. 创建用于处理回复的Intent和PendingIntent // 通常由一个Service处理确保应用在后台也能工作 val replyIntent Intent(context, ReplyService::class.java) val replyPendingIntent PendingIntent.getService( context, 0, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE // 注意需要可变PendingIntent以附加RemoteInput结果 ) // 4. 构建回复Action val replyAction NotificationCompat.Action.Builder( R.drawable.ic_reply, 回复, replyPendingIntent ).addRemoteInput(remoteInput) // 关键添加RemoteInput .build() return NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_notification_small) .setContentTitle(新消息) .setContentText(小王晚上一起吃饭) .setStyle(NotificationCompat.MessagingStyle(Person.Builder().setName(我).build()) .addMessage(晚上一起吃饭, System.currentTimeMillis(), Person.Builder().setName(小王).build())) .addAction(replyAction) // 添加回复动作 .build() } // 在ReplyService中获取回复内容 class ReplyService : IntentService(ReplyService) { override fun onHandleIntent(intent: Intent?) { val remoteInput RemoteInput.getResultsFromIntent(intent) remoteInput?.getCharSequence(KEY_TEXT_REPLY)?.let { replyText - // 在这里处理回复文本例如发送到你的服务器 Log.d(ReplyService, 用户回复: $replyText) // 更新通知显示“已回复”或更新消息列表 updateNotificationAfterReply(replyText) } } // ... updateNotificationAfterReply 方法 }重要提示处理直接回复的PendingIntent需要使用FLAG_MUTABLE标志Android 12要求更严格因为系统需要将RemoteInput的结果填充到这个Intent中。这是少数需要使用可变PendingIntent的场景之一务必注意其安全性确保Intent的接收端如你的Service是可信的。4. 在后台可靠运行前台服务、定时任务与通知的绑定通知常常需要与后台任务配合。最典型的两个场景是前台服务Foreground Service和定时/延迟通知。4.1 前台服务通知维系长期后台任务的“通行证”从Android 8.0开始后台执行限制越来越严格。如果你想执行一个用户可感知的、长时间运行的任务如播放音乐、下载文件、记录GPS轨迹你必须启动一个前台服务并必须关联一个持续显示的通知。这个通知就是你的服务能在后台存活的“通行证”。创建前台服务通知的关键步骤class MyForegroundService : Service() { private val NOTIFICATION_ID 1001 private val CHANNEL_ID foreground_service_channel override fun onCreate() { super.onCreate() createNotificationChannel() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // 1. 构建一个前台服务专用的通知 val notification buildForegroundNotification() // 2. 调用 startForeground传入通知ID和通知对象 // 这一步必须在Service启动后5秒内完成否则会引发ANR应用无响应 startForeground(NOTIFICATION_ID, notification) // 3. 开始你的实际后台工作如下载 startDownloadWork() return START_STICKY // 根据你的服务需求选择合适的返回值 } private fun buildForegroundNotification(): Notification { // 这个通知通常包含一个停止服务的操作按钮 val stopIntent Intent(this, MyForegroundService::class.java).apply { action ACTION_STOP } val stopPendingIntent PendingIntent.getService(this, 0, stopIntent, PendingIntent.FLAG_IMMUTABLE) return NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_service_small) .setContentTitle(文件下载中) .setContentText(正在下载 update.zip...) .setContentIntent(getMainActivityPendingIntent()) // 点击通知通常回到主界面 .addAction(R.drawable.ic_stop, 停止, stopPendingIntent) .setOngoing(true) // 设置为持续进行中的通知用户通常无法直接滑动清除 .build() } private fun startDownloadWork() { // 模拟下载工作 thread { // ... 下载逻辑 // 下载过程中可以更新通知进度见下文 updateNotificationProgress(50) // ... 更多逻辑 // 下载完成 updateNotificationComplete() // 任务完成后记得停止前台服务 stopForeground(true) // 参数true表示同时移除通知 stopSelf() } } private fun updateNotificationProgress(progress: Int) { val notification NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.draw.ic_service_small) .setContentTitle(文件下载中) .setContentText(下载进度: $progress%) .setProgress(100, progress, false) // 设置进度条 (最大值当前值是否不确定模式) .setOngoing(true) .build() // 更新通知 val notificationManager getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(NOTIFICATION_ID, notification) // 注意对于前台服务也可以直接更新 startForeground 时传入的通知 // 但更常见的做法是像上面这样用 notificationManager.notify 更新 // 因为 startForeground 会固定一个通知而 notify 可以更新它 } }踩坑实录与核心要点5秒ANR规则在onStartCommand()中调用startForeground()必须在服务启动后5秒内完成。否则系统会认为你的服务启动超时导致ANR应用可能被强制停止。务必确保通知构建逻辑简单高效。通知ID前台服务的通知ID必须非零且在整个应用内应唯一标识该服务。停止前台服务后对应的通知会被移除。setOngoing(true)这使通知变为“进行中”状态在Android 11以下版本用户无法通过滑动直接清除此类通知但可以在设置中强制停止。这确保了服务不会被意外中断。但从用户体验角度务必提供明确的停止操作如Action按钮。任务完成后的清理后台任务如下载完成结束后必须调用stopForeground()并传入true来停止前台状态并移除通知然后调用stopSelf()停止服务。否则通知会一直挂着服务也会一直消耗资源。Android 9 (Pie) 及以上的权限使用前台服务需要在AndroidManifest.xml中声明FOREGROUND_SERVICE权限android.permission.FOREGROUND_SERVICE。这是一个普通权限只需声明即可。Android 12 (API 31) 的前台服务启动限制在Android 12上除非有特殊情况如用户操作触发、高优先级FCM消息、系统事件等否则后台应用无法启动前台服务。这要求你的应用设计需要更谨慎通常需要引导用户执行一个操作如点击按钮来启动需要前台服务的任务。4.2 进度通知与定时/延迟通知进度通知如上例所示使用setProgress(max, progress, indeterminate)方法。第三个参数为true时是无限循环的模糊进度条适用于无法确定进度的情况为false时是精确进度条。重要当任务完成时一定要更新通知移除进度条setProgress(0,0,false)或直接重建一个完成状态的通知否则进度条会一直显示。定时/延迟通知Android本身不提供直接发送延迟通知的API。你需要使用**AlarmManager或更推荐的WorkManager**来在指定时间触发一个任务由该任务来发送通知。使用WorkManager实现下午2点的每日提醒// 1. 定义一个Worker class DailyReminderWorker(context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { // 在这里发送通知 sendReminderNotification(applicationContext) return Result.success() } } // 2. 在应用代码中如Activity或ViewModel安排任务 fun scheduleDailyReminder() { val constraints Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) // 可选约束需要网络 .build() // 创建每天下午2点触发的周期性任务 val dailyReminderRequest PeriodicWorkRequestBuilderDailyReminderWorker( 24, // 重复间隔 TimeUnit.HOURS, 15, // 灵活间隔允许系统在15分钟窗口内优化执行时机 TimeUnit.MINUTES ) .setInitialDelay(calculateDelayTo2PM(), TimeUnit.MILLISECONDS) // 设置首次延迟到下午2点 .setConstraints(constraints) .build() WorkManager.getInstance(context).enqueueUniquePeriodicWork( daily_reminder, // 唯一工作名避免重复安排 ExistingPeriodicWorkPolicy.KEEP, // 如果已存在同名任务保留旧的 dailyReminderRequest ) } private fun calculateDelayTo2PM(): Long { val calendar Calendar.getInstance().apply { timeInMillis System.currentTimeMillis() set(Calendar.HOUR_OF_DAY, 14) set(Calendar.MINUTE, 0) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) } var triggerTime calendar.timeInMillis if (System.currentTimeMillis() triggerTime) { // 如果现在已过今天下午2点则设定为明天下午2点 triggerTime 24 * 60 * 60 * 1000 } return triggerTime - System.currentTimeMillis() }为什么推荐WorkManagerAlarmManager需要自己处理设备重启、低电耗模式等复杂情况而WorkManager是Jetpack组件它整合了JobScheduler,AlarmManager和GcmNetworkManager的优点提供了更简单、更省电、更可靠的后台任务调度并且能很好地与Android的后台限制策略协同工作。5. 调试、适配与性能优化从能用到好用的最后一步功能实现了但在成千上万的设备和复杂的系统版本面前通知可能表现不一。以下是确保稳定性和体验的关键环节。5.1 通知的调试与问题排查通知发不出来或者样式不对按以下步骤排查检查渠道对于Android 8.0确认通知使用了已创建的渠道ID。去系统设置里找到你的应用查看通知渠道是否存在且未被用户关闭。检查小图标setSmallIcon是否设置图标是否是带有alpha通道的纯色图标用图片查看工具检查图标的颜色模式。检查权限在Android 13 (API 33) 及以上发送通知需要申请新的运行时权限POST_NOTIFICATIONS。如果目标API级别是33必须在发送通知前请求该权限。if (Build.VERSION.SDK_INT Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ! PackageManager.PERMISSION_GRANTED) { // 请求权限 ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), REQUEST_CODE) } }检查通知ID更新通知时需要使用相同的ID。如果要显示多条独立通知请使用不同的ID。使用NotificationManager确保你通过Context.NOTIFICATION_SERVICE正确获取了NotificationManager实例并调用了notify()方法。查看LogcatAndroid系统在通知发送失败时有时会在Logcat中输出错误信息例如“Invalid notification (no valid small icon)”等这是非常重要的调试线索。5.2 不同Android版本的适配要点Android 5.0 (Lollipop, API 21)引入了锁屏通知和通知的“可见性”(setVisibility)。如果你需要控制锁屏状态下通知的显示内容需要适配。Android 7.0 (Nougat, API 24)引入了通知的“快速回复”Direct Reply和消息样式(MessagingStyle)的增强。如果你做即时通讯应用这是必须适配的版本。Android 8.0 (Oreo, API 26)通知渠道。这是最大的分水岭必须适配。Android 9.0 (Pie, API 28)增强了“勿扰模式”的规则并引入了“通知智能回复”的建议。你的应用可以提供回复建议。Android 10 (Q, API 29)对IMPORTANCE_HIGH渠道的弹出行为做了更严格的限制更尊重用户的全局“勿扰”设置。Android 11 (API 30)对话Conversation被提升为一种特殊的通知类别拥有更高的优先级和专属区域。使用MessagingStyle并正确设置Person对象的通知会被自动识别为对话。Android 12 (API 31)前台服务启动限制前文已提。通知UI设计变更系统会自动对通知应用圆角和新的配色。要求为PendingIntent显式声明可变性(FLAG_IMMUTABLE/FLAG_MUTABLE)。Android 13 (API 33)新增运行时通知权限(POST_NOTIFICATIONS)。这是目前最需要立即关注的适配点否则在Android 13设备上用户不授权你的通知就完全发不出去。5.3 性能与体验优化建议通知分组如果你的应用会在短时间内产生多条同类型通知如聊天消息应该将它们分组。使用setGroup(key)将多条通知归入同一组并可以创建一个“摘要通知”setGroupSummary(true)来汇总显示。这能避免通知栏被你的应用刷屏提升用户体验。大图优化BigPictureStyle中使用的大图务必进行压缩和采样避免使用巨幅原图导致内存溢出和加载缓慢。可以使用BitmapFactory.Options进行inSampleSize采样。更新而非新建对于进度通知、实时更新的信息如音乐播放应使用相同的通知ID来更新通知内容而不是每次都创建新通知。这更高效且对用户更友好不会一直产生新通知提示音。及时取消对于一次性、过时的通知如“下载完成”在用户点击或操作后应调用NotificationManager.cancel(id)及时将其从通知栏清除保持通知栏的整洁。尊重用户提供清晰、合理的通知渠道分类和描述。允许用户轻松地关闭非关键通知渠道。这是减少用户卸载应用可能性的重要手段。一个“牛皮癣”应用是活不长的。写到这里关于Android通知的核心脉络和实战细节已经基本覆盖。从最基础的渠道创建到丰富的样式交互再到与后台服务的深度绑定最后是确保稳定可用的调试适配策略每一个环节都充满了细节和“坑”。我个人的体会是通知开发是一个“细节决定成败”的领域。它要求开发者不仅懂API更要懂用户体验懂系统规则甚至要懂一点设计。把通知做好你的应用就在与用户建立长期、友好、不打扰的关系上迈出了坚实的一步。下次当你设计一个通知时不妨多问自己一句这个通知用户真的需要吗它是否足够清晰、有用且克制
返回列表