如何实现通知的折叠展开(Expandable Notification)?
解读
国内面试官问“折叠展开通知”时,通常不是让你背出 BigPictureStyle 几个单词,而是想确认三件事:
- 是否真正理解 RemoteViews 的“折叠-展开”两套布局机制;
- 能否在 7.0 及以上版本正确适配通知组(Notification Group)与 MessagingStyle 的折叠行为;
- 是否知道国内 ROM(小米、华为、OPPO、vivo)对 RemoteViews 高度、动画、图标缓存的“魔改”限制,以及后台弹出权限对展开视图能否立即展示的影响。
回答时要把“官方 API + 国内坑点 + 性能/权限”串成一条线,才能体现资深经验。
知识点
- RemoteViews 双布局:
setContent()给折叠视图(≤ 48 dp),setCustomBigContentView()给展开视图(≤ 256 dp)。 - 样式类:NotificationCompat.BigPictureStyle、BigTextStyle、InboxStyle、MessagingStyle;自定义布局用
NotificationCompat.DecoratedCustomViewStyle保证系统装饰一致。 - 通知组:7.0 之后必须设置
setGroup()+setGroupSummary(true),否则多条通知不会自动折叠成“通知束”。 - 国内 ROM 限制:
- 小米/华为默认关闭“悬浮通知”权限,导致
PRIORITY_HIGH的展开视图只能静默显示,用户需手动下拉。 - 部分 ROM 把 RemoteViews 最大高度裁剪到 200 dp,超出的布局被强制滑动或截断。
- 图标缓存策略差异:若
setLargeIcon()与setSmallIcon()使用相同 Bitmap,部分 ROM 会复用缩略图导致模糊。
- 小米/华为默认关闭“悬浮通知”权限,导致
- 性能:展开布局禁止嵌套 ConstraintLayout,推荐扁平 LinearLayout;图片需预缩放到屏幕密度,避免每次下拉触发重新解码。
- 安全:自定义 RemoteViews 不允许使用自定义字体、不可执行 Java 代码,必须通过
setOnClickPendingIntent()跳转,防止国内商店审核被驳回“远程代码执行”风险。
答案
“实现折叠展开通知分三步:
第一步,根据内容类型选官方样式或自定义 RemoteViews。文本类用 BigTextStyle,图片类用 BigPictureStyle,聊天类用 MessagingStyle;若 UI 需要品牌定制,则自定义 layout,然后用 NotificationCompat.DecoratedCustomViewStyle 包一层,保证系统头部、时间、折叠按钮样式一致。
第二步,构建 NotificationChannel 时把重要级别提到 IMPORTANCE_HIGH,并申请国内 ROM 的“后台弹出界面”权限(小米为 android.permission.SYSTEM_ALERT_WINDOW,华为为 android.permission.USE_FULL_SCREEN_INTENT),否则展开视图只能静默显示。
第三步,7.0 以上如果业务有多条通知,必须设置同一 groupKey 并指定一条 summary 通知,系统才会自动折叠成通知束;summary 通知用 InboxStyle 即可。
最后,上线前用 adb shell cmd notification post 模拟 50 条通知,检查国内四大 ROM 是否出现高度裁剪、图标模糊、展开动画失效;若发现小米裁剪到 200 dp,就在远端配置中心下发 200 dp 的专用布局,保证体验一致。”
拓展思考
- 折叠展开通知与“通知优化”冲突:Android 13 开始系统会根据用户“划掉频率”自动降级渠道级别,导致高优先级展开视图被强制折叠。解决思路是在用户划掉后 24 h 内不再推送同渠道通知,或动态切换到低打扰渠道。
- 折叠屏场景:展开态屏幕宽度变大,RemoteViews 中的图片可能被系统拉伸。可利用
res/layout-w600dp-notification.xml限定尺寸,或在大屏设备改用 MessagingStyle 的setConversationTitle展示更多上下文。 - 车载与 Wear OS:车载通知要求展开视图在 2 s 内可读完,RemoteViews 高度限制 128 dp;Wear 上折叠展开由系统统一转卡片,不允许自定义 BigContentView。同一套推送需做设备形态分流,通过
NotificationManager.areNotificationsEnabled()与getActiveNotifications()动态决策。