22、通知的富媒体:图片通知、视频通知、GIF通知、音频播放控制通知
通知发展到今天,早就不只是弹个文字提醒了。用户期待的是更丰富的交互体验——直接看到图片、预览视频、甚至控制音乐播放。这一章,我们就来聊聊这些「富媒体通知」的实现细节。
说实话,我刚接触富媒体通知时也踩过不少坑。尤其是不同厂商的ROM对通知的处理方式差异很大,稍不注意就会在某个机型上翻车。嗯,咱们一步步来。
22.1 图片通知:不只是加个缩略图
图片通知,说白了就是在通知里展示一张图片。最常见的场景是聊天App的消息预览——比如微信里收到一张照片,通知栏直接显示缩略图。
实现方式其实很简单,核心是 NotificationCompat.BigPictureStyle。
// 加载图片(建议用BitmapFactory.decodeStream避免OOM)
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.notification_big);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("新图片消息")
.setContentText("点击查看大图")
.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
.bigLargeIcon(null)) // 隐藏大图标,只显示图片
.setPriority(NotificationCompat.PRIORITY_HIGH);
NotificationManagerCompat.from(this).notify(1001, builder.build());
bigLargeIcon(null) 这行很多人会漏掉,结果通知里同时显示了大图标和图片,非常丑。
22.2 视频通知:预览与跳转
视频通知并不是真的在通知栏播放视频——那太耗电了。通常的做法是:显示视频的第一帧作为预览图,点击后跳转到播放页面。
这里有个关键点:如何获取视频的缩略图?
// 获取视频缩略图(API 10+)
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(videoPath);
Bitmap thumbnail = retriever.getFrameAtTime(0, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
// 然后使用BigPictureStyle展示
} catch (Exception e) {
e.printStackTrace();
} finally {
retriever.release();
}
为什么用 OPTION_CLOSEST_SYNC?因为视频的关键帧(I帧)解码最快,能避免耗时。我刚开始用 OPTION_CLOSEST 时,在某些低端机上会卡几百毫秒,用户体验很差。
MediaMetadataRetriever 是重量级对象,记得在子线程调用。我曾经在UI线程直接调用,结果被系统弹了ANR对话框……嗯,血的教训。
22.3 GIF通知:动起来的通知
GIF通知的实现,本质上还是图片通知。因为Android原生通知不支持GIF动画,我们需要把GIF转成Bitmap序列,或者直接展示第一帧。
我个人习惯的做法是:
- 如果GIF不大(小于1MB),用
Glide加载并转成Bitmap - 如果GIF较大,只展示第一帧,点击后跳转
// 使用Glide获取GIF的第一帧
FutureTarget<Bitmap> futureTarget = Glide.with(this)
.asBitmap()
.load(gifUrl)
.submit(TARGET_SIZE, TARGET_SIZE);
Bitmap bitmap = futureTarget.get(); // 注意在子线程调用
// 然后同样用BigPictureStyle展示
你想想看,为什么不用 Movie 类?因为 Movie 只能在自定义View里播放,通知栏不支持。所以老老实实用静态图吧。
22.4 音频播放控制通知:最复杂的富媒体
音频控制通知,说白了就是音乐播放器那种——有播放/暂停、上一首、下一首按钮。这是富媒体通知里最复杂的,因为它涉及前台服务、媒体会话、以及系统UI的交互。
核心组件有三个:
| 组件 | 作用 |
|---|---|
MediaSession |
管理播放状态,与系统媒体中心通信 |
MediaStyle |
通知的样式,支持添加媒体按钮 |
Foreground Service |
保证后台播放不被系统杀死 |
来看一个完整的实现片段:
// 1. 创建MediaSession
MediaSessionCompat mediaSession = new MediaSessionCompat(this, "MusicSession");
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
// 2. 构建通知
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_music)
.setContentTitle("正在播放")
.setContentText("歌曲名称 - 歌手")
.setLargeIcon(albumArtBitmap)
.setStyle(new androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.getSessionToken())
.setShowActionsInCompactView(0, 1, 2)) // 在紧凑模式下显示哪些按钮
.addAction(R.drawable.ic_prev, "上一首", pendingIntentPrev)
.addAction(R.drawable.ic_play, "播放/暂停", pendingIntentPlay)
.addAction(R.drawable.ic_next, "下一首", pendingIntentNext)
.setOngoing(true) // 不可滑动清除
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
// 3. 启动前台服务
startForeground(NOTIFICATION_ID, builder.build());
setShowActionsInCompactView(0, 1, 2) 决定了在通知栏折叠状态下显示哪几个按钮。我一般只放播放/暂停和下一首,因为空间有限。展开后才会显示全部按钮。
这里有个坑:不同Android版本对媒体通知的展示方式不同。Android 12+ 会强制使用系统媒体中心样式,你的自定义按钮可能被重新排列。我建议用 MediaStyle 的标准方式,不要自己魔改布局。
22.5 知识体系总览
下面这张图总结了本章的核心逻辑:
22.6 避坑指南
最后,分享几个我实际项目中踩过的坑:
- 通知渠道优先级: 富媒体通知一定要设置高优先级,否则在某些手机上会被折叠。我见过一个音乐App的通知被系统归类到「低优先」渠道,用户完全看不到控制按钮。
- 前台服务限制: Android 14 对前台服务有更严格的限制。音频通知必须声明
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,否则会崩溃。 - 大图内存: 一张1920x1080的Bitmap在内存里占大约8MB。如果你同时展示多张图片,很容易OOM。建议用
BitmapFactory.Options做采样压缩。 - GIF循环问题: 有些GIF只有一帧,展示出来就是静态图。用户会以为通知坏了。我一般会在UI上提示「点击查看动图」。
好了,富媒体通知的内容就这些。说白了,核心就是 BigPictureStyle 和 MediaStyle 两个类,加上对性能和兼容性的敬畏之心。动手试试吧,你会发现其实没那么复杂。