ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Android消息通知组件buzz:渠道、振动与权限适配实战

Android消息通知组件buzz:渠道、振动与权限适配实战

“buzz”这个名字,起的挺妙。在移动开发圈里聊到buzz,大部分时候不是在说某个社交媒体的热度词,而是指App里那套“消息通知+振动反馈”的联动体验——手机在静音模式下收到一条要紧的消息,屏幕亮起、机身轻轻一震、伴随一声短提示音,用户不用掏出手机也知道“这事儿来了”。这套体验看似简单,真要做好,牵扯到通知渠道、振动波形、厂商兼容、权限适配一大堆问题。我最早开始折腾buzz,是因为产品提了一个需求:不同优先级的消息要有不同的提醒强度,重要消息必须第一时间抓住用户注意力,广告推送则尽量低调不打扰。这篇文章就把我在这套组件上的完整设计与踩坑记录整理出来,给正在做消息提醒、IM推送、日程提醒、闹钟类应用的朋友做个参考。

1. 项目定位与核心设计思路

1.1 “buzz”到底解决什么问题

先聊清楚需求边界。buzz不是要做一个推送SDK,也不是要做一个通知管理后台,它要解决的只有一个核心问题:当消息到达客户端时,如何用最合适的方式提醒用户。

这里的关键词是“最合适”。同样是收到一条消息,凌晨两点的紧急工单和下午三点的优惠券推送,提醒方式绝对不能一样。紧急工单需要强提醒——响铃、振动、亮屏、悬浮窗齐上阵;优惠券推送则适合弱提醒——一条静默通知,用户看见了就点,没看见也不损失什么。

所以buzz的职责可以拆成三块:

  • 通道管理:负责创建和管理不同类型的通知渠道,区分消息优先级。
  • 提醒策略:根据消息类型决定要不要振动、振多久、响不响、响什么音。
  • 权限与兼容适配:处理Android各版本和各家ROM在通知权限、后台限制上的差异。

我最初把它设计成一个独立的组件(Module),而不是写死在业务代码里,就是想让产品同学在调整提醒策略时,只动配置不动逻辑。后面实际用下来,这个决定帮了大忙——产品改需求的速度远比写代码快。

1.2 为什么选择“通知+振动+声音”一体化的方案

你可能想问:系统自带的Notification已经支持设置声音和振动了,为什么还要自己封装一套?

答案是:系统通知的默认能力,满足不了“策略化提醒”的需求。Notification.Builder确实可以设置sound、vibrate、priority这些参数,但这些参数散落在各条通知的构建代码里,属于“点状配置”。一旦有20种消息类型,代码里就会出现20份构建通知的逻辑,每份都要重复设置渠道、重复判权限、重复适配厂商。

buzz的做法是反过来——把提醒策略提到配置层,业务层只传消息类型和内容,组件内部根据消息类型自动匹配提醒方案。业务代码永远只关心“我要发一条‘订单超时提醒’”,不关心它应该振几下、用什么声音。这样有几个实实在在的好处:

  • 提醒策略调整时,不需要改业务代码,改配置表或枚举映射即可。
  • 多端(手机、平板)提醒策略可以保持一致,避免各端行为分叉。
  • 新增消息类型时,只需要注册新类型和对应渠道,不用复制粘贴一大堆通知构建代码。

这个设计思路很像路由的思想:业务方不需要知道目标页面怎么实现,只管用路由名发起跳转。buzz就是提醒领域的路由器。

2. 消息通道与提醒参数设计

2.1 Android 8.0通知渠道机制

从Android 8.0(API 26)开始,Google强制要求所有通知必须绑定一个NotificationChannel,否则通知直接不显示。这个设计本意是好的——把通知的控制权交给用户,用户可以在系统设置里单独关闭某个渠道的通知,而不影响其他渠道。

这意味着buzz的渠道设计直接决定了用户能“精细控制”到什么程度。我见过很多App一个渠道走天下,用户想关推送只能全关,这体验其实很差。buzz的渠道划分建议遵循“消息类型”维度,至少分成以下几类:

渠道类型重要性级别典型场景提醒方式
紧急消息IMPORTANCE_HIGH订单异常、账号安全响铃+振动+悬浮窗
即时消息IMPORTANCE_DEFAULTIM聊天、会议提醒响铃+振动(可关闭)
服务通知IMPORTANCE_LOW物流进度、订单状态仅提示音,不振动
营销通知IMPORTANCE_MIN活动推送、内容推荐静默通知,无声音无振动

渠道创建代码(Kotlin):

fun createChannels(context: Context) { val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // 紧急消息渠道 val emergencyChannel = NotificationChannel( ChannelId.EMERGENCY, "紧急消息", NotificationManager.IMPORTANCE_HIGH ).apply { description = "涉及账户安全、订单异常的即时提醒" enableVibration(true) vibrationPattern = longArrayOf(0, 500, 300, 500) lockscreenVisibility = Notification.VISIBILITY_PUBLIC setSound(Settings.System.DEFAULT_NOTIFICATION_URI, AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ALARM) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build()) } manager.createNotificationChannel(emergencyChannel) // 营销消息渠道 - 低打扰 val marketingChannel = NotificationChannel( ChannelId.MARKETING, "营销通知", NotificationManager.IMPORTANCE_MIN ).apply { description = "活动推送、内容推荐等低优先级通知" enableVibration(false) setSound(null, null) } manager.createNotificationChannel(marketingChannel) }

这里有几个容易被忽略的细节:

  • 渠道一旦创建就不能改重要性级别,用户手动调整后代码也无法覆盖。所以渠道的初始重要性要谨慎设置,宁高勿低。
  • 渠道数量不是越多越好,用户看到几十个渠道会更烦。建议控制在5个以内,且名称要通俗易懂,让用户一看就知道是什么。
  • 渠道ID一旦确定就不要改,否则会出现“旧渠道失效、新渠道空跑”的问题。

2.2 振动反馈的参数设计

振动反馈是buzz的核心竞争力。Android原生提供了Vibrator类,但从Android 8.0开始,推荐使用VibrationEffect来定义振动波形,而不是直接传一个long数组。

基础振动调用:

// Android 8.0+ fun vibrate(context: Context, waveform: LongArray, repeatIndex: Int = -1) { val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { vibrator.vibrate(VibrationEffect.createWaveform(waveform, repeatIndex)) } else { @Suppress("DEPRECATION") vibrator.vibrate(waveform, repeatIndex) } }

常见振动波形参考:

场景波形(ms)效果体验
默认消息[0, 100, 100, 100]短促两下,清爽不刺耳
紧急提醒[0, 500, 300, 500, 300, 700]长振三下,明显感知
静音弱提醒不振动彻底安静,不打扰
连续强提醒[0, 1000, 200, 500]一长一短,适合闹钟类

设计振动波形时要注意几个原则:

  • 总时长控制在3秒以内,超过3秒的长振动会让人烦躁,而且费电。
  • 振幅不等于时长,长振动并不比多次短振动更“紧急”,多次短振动反而更有节奏感。
  • 自定义来电振动要谨慎,别用AI生成那种随机波形,测试时你自己都分辨不出区别。

2.3 声音与视觉提醒的配合

buzz里声音和视觉要搭配着来,不能只用声音。通知提醒不是一个单维度的事,用户感知顺序是先看到(亮屏/状态栏),后听到(声音),再感觉到(振动)。三者需要形成统一节奏。

声音这块有几个坑要提醒:

  • 不要给所有渠道都设同一个自定义铃声,要让用户能用系统设置区分。紧急消息可以提示用户去设置里替换成特殊铃声。
  • 营销渠道不要设声音,这条是我的血泪教训——用户半夜被一条优惠券推送吵醒,后果不是关掉这个渠道,而是卸载App。
  • Android 8.0之后,setSound(null) 代表无声音,但要注意部分ROM对这个实现有兼容问题,建议额外在Notification.Builder上调用setOnlyAlertOnce(true),避免重复提醒时反复响铃。

视觉提醒方面,锁定屏幕可见性是关键参数:

// 锁屏可见性 notificationBuilder.setVisibility(Notification.VISIBILITY_PUBLIC) // 所有内容可见 notificationBuilder.setVisibility(Notification.VISIBILITY_PRIVATE) // 隐藏敏感内容 notificationBuilder.setVisibility(Notification.VISIBILITY_SECRET) // 完全不显示

聊天类消息建议用VISIBILITY_PRIVATE,正文会被替换成“1条新消息”,既保护隐私,又不打断用户查看锁屏的习惯。交易类通知建议VISIBILITY_PUBLIC,金额变动一目了然,减少用户解锁查看的路径。

3. 权限适配与分机型兼容

3.1 通知权限与悬浮窗权限

从Android 13(API 33)开始,Google引入了POST_NOTIFICATIONS运行时权限。这意味着App弹通知前必须先请求通知权限,用户拒绝后收不到任何通知(包括紧急消息)。buzz组件需要把权限申请纳入自身管理,而不是让业务方各自处理。

通知权限申请:

class BuzzPermissionManager { companion object { const val REQUEST_CODE_NOTIFICATION = 1001 } fun requestNotificationPermission(activity: Activity, callback: (Boolean) -> Unit) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { callback(true) // 13以下无需动态申请 return } val permission = Manifest.permission.POST_NOTIFICATIONS if (ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED) { callback(true) return } ActivityCompat.requestPermissions( activity, arrayOf(permission), REQUEST_CODE_NOTIFICATION ) } }

这里有一个产品层面的小心机:不要把通知权限申请放在启动页第一步。用户第一次打开App,还没感受到产品价值,就被要求“允许发送通知”,拒绝率会非常高。比较稳妥的做法是:在用户完成核心操作后(比如下单成功、注册完成)再弹出申请,用户正处于需要反馈的节点,同意率会明显提升。

悬浮窗权限又是另一个故事。紧急消息需要在锁屏界面显示悬浮窗(heads-up通知),这需要SYSTEM_ALERT_WINDOW权限。Android原生系统对悬浮窗权限是“跳转设置页让用户手动打开”,国产ROM也基本沿用这个逻辑。

fun ensureOverlayPermission(context: Context, activity: Activity) { if (!Settings.canDrawOverlays(context)) { val intent = Intent( Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:${context.packageName}") ) activity.startActivity(intent) } }

建议悬浮窗权限不要强制,只针对“紧急消息”这一场景引导开启,否则用户会觉得你在耍流氓。

3.2 国产ROM后台限制问题

这可能是buzz组件在实际落地中最头疼的部分。小米、华为、OPPO、vivo四大厂商对后台活动有严格限制,通知权限申请了也未必能正常送达,尤其是在用户手动关闭“自启动”“后台弹出界面”之后。

实测结论是:通知能否送达,核心不在buzz组件的代码,而在App是否在白名单里。buzz能做的是提供一个“检查与引导”的工具函数:

fun isIgnoringBatteryOptimizations(context: Context): Boolean { val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager return pm.isIgnoringBatteryOptimizations(context.packageName) } fun requestIgnoreBatteryOptimizations(context: Context) { val intent = Intent( Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse("package:${context.packageName}") ) // 注意:在部分ROM上此Activity不存在,需fallback到应用详情页 }

这里有一个必须强调的点:对于IM类、工作协同类App,建议引导用户开启“自启动”。因为消息通知的及时性依赖App进程存活。而对工具类App(比如计算器、文件管理器),没必要做这个引导,强行引导会让用户觉得你很鸡贼。

buzz组件内部要做一个状态检测面板,可以手动检查当前设备是否满足通知送达条件,包括:

  • 通知权限是否开启
  • 电池优化是否排除
  • 自启动是否开启
  • 后台运行是否限制

把这些信息汇总成一个检查结果,帮助客服定位用户收不到通知的原因。这个面板在开发阶段也好使,可以快速确认是不是ROM限制导致的问题。

3.3 低版本兼容处理

虽然Android 8.0普及率已经很高,但海外市场和一些特殊设备(如低端教育平板)仍然存在老系统。buzz的兼容处理要分层:

  • API 26以下:没有渠道概念,所有通知走默认通道,需要判断是否有渠道再创建,避免崩溃。
  • API 33以下:不需要动态申请通知权限,但要兼容用户已在系统设置中手动关闭通知的情况。
  • API 29及以下:VibrationEffect不可用,需要走老式vibrate(waveform, repeat)路径。

封装一层兼容工具:

object BuzzCompat { fun createNotificationChannel( manager: NotificationManager, channelId: String, channelName: String, importance: Int, description: String ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel(channelId, channelName, importance).apply { this.description = description } manager.createNotificationChannel(channel) } } fun vibrate(context: Context, waveform: LongArray, repeat: Int) { val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { vibrator.vibrate(VibrationEffect.createWaveform(waveform, repeat)) } else { vibrator.vibrate(waveform, repeat) } } }

低版本兼容最容易踩的坑是:用高版本API写的代码在高版本没问题,但低版本直接崩。建议在buzz组件内部所有涉及系统能力的调用都走一层兼容封装,同时在使用MinSdkVersion 23的前提下,所有API 26+的方法都要加SDK_INT判断。

4. 实操:从零实现一个Buzz通知组件

4.1 项目依赖与基础配置

新建一个Android Module,我命名为buzz(也可以叫notification-core),只依赖AppCompat和核心库,不依赖任何业务模块,保证它可独立测试、独立复用。

// build.gradle (Module) plugins { id 'com.android.library' id 'org.jetbrains.kotlin.android' } android { namespace 'com.example.buzz' compileSdk 34 defaultConfig { minSdk 23 targetSdk 34 } } dependencies { implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.appcompat:appcompat:1.6.1' }

buzz组件对外只暴露一个极简入口:

object Buzz { fun notify( context: Context, request: BuzzRequest ) fun cancel(context: Context, notificationId: Int) }

BuzzRequest是业务层传入的唯一数据结构:

data class BuzzRequest( val notificationId: Int, val channelId: String, val title: String, val content: String, val priority: BuzzPriority = BuzzPriority.DEFAULT, val payload: Map<String, String> = emptyMap(), val deleteIntent: PendingIntent? = null, val contentIntent: PendingIntent? = null ) enum class BuzzPriority { EMERGENCY, HIGH, DEFAULT, LOW, SILENT }

这里的设计思路是:让业务层永远不直接操作NotificationManager,所有细节都收敛在Buzz内部。业务层只说“我要发什么”,buzz决定“怎么发”。

4.2 创建通知渠道与发送通知

在Application初始化时创建所有渠道:

class BuzzInitializer { fun init(context: Context) { val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // 批量创建渠道 BuzzChannels.allChannels.forEach { config -> BuzzCompat.createNotificationChannel( manager, config.id, config.name, config.importance, config.description ) } } }

发送通知的核心方法:

fun notify(context: Context, request: BuzzRequest) { val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // 1. 检查通知权限 if (!hasNotificationPermission(context)) return // 2. 根据优先级获取提醒配置 val strategy = BuzzStrategy.resolve(request.priority) // 3. 构建通知 val builder = NotificationCompat.Builder(context, request.channelId) .setSmallIcon(R.drawable.ic_notification) .setContentTitle(request.title) .setContentText(request.content) .setPriority(strategy.notificationPriority) .setAutoCancel(true) .setContentIntent(request.contentIntent) .setDeleteIntent(request.deleteIntent) // 4. 按策略设置声音、振动、锁屏可见性 if (strategy.enableVibration) { builder.setVibrate(strategy.vibrationWaveform) } if (strategy.soundUri != null) { builder.setSound(strategy.soundUri) } else { builder.setSound(null) } builder.setVisibility(strategy.lockscreenVisibility) // 5. 发送 manager.notify(request.notificationId, builder.build()) }

构建通知时有一个容易被忽略的点:重复使用同一个notificationId发通知时,如果通知内容变化,一定要调用setOnlyAlertOnce(true)。否则用户会反复收到同一内容的提醒,体验非常差。我习惯在策略层默认开启这个选项,只有EMERGENCY级别才关闭,确保重复提醒时用户真的能感知到。

4.3 自定义振动与提醒模式

这个功能是buzz组件的高阶能力。默认的通知振动只有“有或没有”,想做出“轻拍三下”这种细腻的反馈,需要自己组合波形。

常用提醒模式枚举:

enum class BuzzPattern { SINGLE_SHORT, // [0, 100] 单次短振 DOUBLE_SHORT, // [0, 120, 80, 120] 两次短振 TRIPLE_LONG, // [0, 400, 200, 300, 200, 500] 三次长振 PRESSURE_WAVE, // 不规则的那种强提醒波形 CUSTOM // 业务自定义 }

实现自定义模式时,波形数组的写法要遵循VibrationEffect的规则:数组下标为偶数的元素代表“等待”时间,奇数的代表“振动”时间,单位都是毫秒。比如[0, 200]表示立刻振动200毫秒,[0, 200, 100, 100]表示振动200ms后等待100ms再振动100ms。

fun vibrateWithPattern(context: Context, pattern: BuzzPattern) { val waveform = when (pattern) { BuzzPattern.SINGLE_SHORT -> longArrayOf(0, 150) BuzzPattern.DOUBLE_SHORT -> longArrayOf(0, 120, 80, 120) BuzzPattern.TRIPLE_LONG -> longArrayOf(0, 400, 200, 300, 200, 500) BuzzPattern.PRESSURE_WAVE -> longArrayOf(0, 300, 100, 200, 300, 400, 200) BuzzPattern.CUSTOM -> return } BuzzCompat.vibrate(context, waveform, -1) }

注意:repeatIndex传-1表示不循环,传0表示从头循环。通知场景不建议循环振动——如果用户5分钟没看手机,手机就振5分钟,那跟闹钟没区别,而且极其耗电。

4.4 通知样式与快捷操作

当通知内容多起来时,默认的文本样式会显得很粗糙。buzz组件需要支持几种系统提供的扩展样式,让通知看起来更专业:

// 大文本样式 val bigTextStyle = NotificationCompat.BigTextStyle() .bigText(request.longContent) .setBigContentTitle(request.title) .setSummaryText("来自 ${request.appName}") // 大图样式(适合商品、物流图片通知) val bigPictureStyle = NotificationCompat.BigPictureStyle() .bigPicture(request.imageBitmap) .setBigContentTitle(request.title)

快捷操作是用户高频使用的入口。最常见的是“快捷回复”和“暂停通知”。实现快捷回复需要RemoteInput:

// 快捷回复 val replyText = RemoteInput.Builder(EXTRA_REPLY_TEXT) .setLabel("快速回复") .build() val replyAction = NotificationCompat.Action.Builder( R.drawable.ic_reply, "回复", replyPendingIntent ) .addRemoteInput(replyText) .build() builder.addAction(replyAction)

我这里建议:通知上至少要有“打开”和“删除”两个基础操作。很多App只做了“打开”操作,用户左滑删除时却忘了清理广播和统计埋点,导致下次点击一个已删除的通知还能进入页面,非常怪。

5. 常见问题与排坑实录

5.1 通知发不出去或延迟

出现频率最高的一个问题:权限和渠道都配置了,但通知就是不显示,或者延迟几分钟才出现。排查顺序如下:

  1. 先检查AndroidManifest是否声明了POST_NOTIFICATIONS权限(API 33+)。
  2. 再检查是否创建了渠道,且渠道重要性不是IMPORTANCE_NONE。
  3. 最后检查ROM的后台限制——非白名单App在后台发通知有延迟或丢弃,尤其是小米和华为。

我自己踩过一个特别隐蔽的坑:targetSdkVersion升级到33后,没有动态申请通知权限,导致通知权限默认拒绝。Google的策略是targetSdk 31+未申请则默认拒绝。升级targetSdk一定要回归通知功能。

5.2 振动毫无反应

振动不生效,90%的情况是权限问题。Android 6.0以上的VIBRATE权限是normal级别(安装即授予),但部分定制ROM对这个权限有额外控制,尤其是华为、荣耀机型。

另外要注意认识一个常见误区:NotificationCompat.Builder.setVibrate()并不总是生效。如果渠道的enableVibration为false,代码里setVibrate会被忽略。所以排查振动问题时,一定要同时检查:

  1. 渠道里是否enableVibration(true)。
  2. 通知Builder里是否setVibrate()。
  3. 设备是否开启“触摸振动”或“通知振动”总开关。
  4. 自定义ROM是否有额外的“通知振动”独立开关。

这里面最容易忽略的是第3点。很多手机在“声音与振动”设置里有一个“通知振动”总开关,用户关掉后,所有App内代码振动都会失效。这不是你代码的问题,但你要能在排查时识别出来。

5.3 通知渠道创建报错或失效

最常见的原因是:渠道ID在代码中改了,但老用户在设备上还保留着旧渠道。系统不支持修改渠道ID对应的名称和描述,改ID等于创建一个新渠道,用户之前对这个渠道做的所有设置(包括关闭通知)都失效。

解决办法:渠道ID命名要做到“一次确定,不再改变”。如果你已经发布过版本,就保持渠道ID不变,只修改渠道名称。实在要改,就迁移用户到新渠道,并标记旧渠道废弃。这个“废弃”不是删除,因为程序删不了渠道,只能帮用户设置禁用:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { manager.getNotificationChannel(oldChannelId).importance = NotificationManager.IMPORTANCE_NONE }

5.4 保活与省电冲突

buzz组件最重要的能力之一是及时的提醒能力,而手机厂商的省电策略恰恰是限制后台提醒。这里有一个无奈的结论:任何纯客户端代码都无法100%保证后台通知及时性,除非你能让App进入厂商白名单(系统应用或用户手动授权)。

最稳妥的做法是做“双通道”:

  • 应用在线时:走buzz自建的长连接或推送SDK,消息一到立即提醒。
  • 应用被杀时:走厂商推送通道(小米、华为、OPPO、vivo都有自己的推送服务),确保消息能到达,但提醒效果取决于厂商策略。

buzz组件需要兼容这两种来源:收到厂商推送时,如果App进程已死,系统会通过Intent拉起一个广播接收器,buzz在接收器里重新构建通知并展示。这要求buzz的通知构建逻辑必须能独立运行,不依赖Activity或进程内单例。

注意:不要在广播接收器里做耗时操作。厂商对第三方广播接收器的执行时长有严格限制(通常只有10秒左右),通知构建务必轻量。

6. 优化与扩展方向

buzz组件迭代到后期,我发现它不仅在“消息提醒”场景有用,很多场景都能复用:

  • 闹钟和日程提醒:结合闹钟权限、震动波形自定义,效果比直接用系统闹钟好。
  • 订单状态变更:结合大图样式和快捷操作,展示物流动态比纯文字通知清楚得多。
  • 音视频通话来电:利用全屏Intent实现来电界面,比普通通知更容易吸引注意。

有一个锦上添花的扩展方向是通知埋点统计。在buzz内部对通知的发送、展示、点击、删除做埋点,数据可以帮助产品判断:

  • 哪些渠道的打开率高,哪些渠道形同虚设。
  • 哪些时段发通知效果最好。
  • 用户对通知的授权趋势和拒绝原因。

这些数据反过来会指导渠道设计——如果发现“营销通知”渠道打开率趋近于0,可以直接砍掉,减少对用户的打扰。

还有一个小技巧值得分享:给通知加上延时提醒能力。有些消息用户当下不方便处理,可以长按通知选择“1小时后提醒”,buzz内部通过AlarmManager+广播实现,相当于一个轻量级的待办提醒器。这个功能实现起来不难,但用户好感度提升非常明显,算是一个投入产出比很高的扩展点。

最后说一点我个人的体会:做buzz这样的基础组件,最重要的不是功能多炫,而是该响的时候一定响,不该响的时候坚决不打扰。提醒策略克制一点,用户对App的信任感反而会更强。我见过不少产品把每个渠道都设成IMPORTANCE_HIGH,结果用户烦了直接把App通知全关——这等于彻底丧失了触达用户的机会。buzz的价值就在于把“提醒”这个行为做得有分寸感,让用户愿意保留通知权限,让消息真正成为有用的信息流,而不是骚扰源。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进