
夺舍 媒体通知
夺舍媒体通知:
让生活规划 App 白嫖 HyperOS 锁屏通知 + 灵动岛 + 大视图
场景:你在开发一个时间块规划 App,前台服务跑着进度通知。HyperOS(小米澎湃OS)上,通知要么被折叠成”更多通知”(用户看不到),要么缩成一行挤在状态栏(丑且没信息量),锁屏上更是干干净净什么也没有。开发者账户没有,灵动岛 API 不公开。怎么办?
答案是:夺舍媒体通知。
1. 问题全景
- 锁屏通知不显示 — HyperOS 限制了普通通知出现在锁屏上的类型,除非用户在系统设置里手动开。
- “更多通知”折叠 — 前台服务的进度通知被 HyperOS 自动归到 Aggregate Section,收成一个”更多通知”折叠项,用户必须点两下才能看到进度条。
- 通知高度不够 — 标准通知只能显示标题 + 一行文字 + 小进度条,事件名称、已用时间、剩余时间、下一任务全挤不下。
- 灵动岛需要开发者账户 — 小米的灵动岛(Capsule)API 需要开发者资质,没账户连文档都看不了。
这四个问题,最终用一个 Android 标准 API 全解决了——Notification.MediaStyle。
2. 为什么是 MediaStyle
翻小米澎湃OS开发者文档时看到 MIUI10 媒体通知适配说明,里面有几句话亮了:
正在播放的媒体通知默认置顶 + 默认展开为大视图。
正在播放的媒体通知默认会出现在锁屏上(置顶+大视图)。
调用系统的媒体通知,系统就能做针对性的优化……
也就是说,只要 HyperOS 认为你的通知是一个”媒体通知”,它就自动给你:
- ✅ 锁屏上置顶展开显示
- ✅ 通知栏里大视图(够高、能放更多信息)
- ✅ 灵动岛胶囊(HyperOS 对媒体通知自动展示)
- ✅ 不会被折叠进”更多通知”
关键是:不需要开发者账户,不需要小米私有 API。这是 Android 5.0 就有的标准 API —— Notification.MediaStyle。
3. “夺舍”方案
我们并不播放媒体。但我们可以创建一个假的 MediaSession,设为”播放中”状态,然后把事件进度伪装成播放进度。 HyperOS 看到播放中的媒体通知,就会自动展开、置顶、锁屏显示、弹出灵动岛。
3.1 创建假 MediaSession
object PlannerMediaSession {
private var session: MediaSession? = null
val token: MediaSession.Token? get() = session?.sessionToken
fun start(ctx: Context) {
val s = MediaSession(ctx, "ld_planner")
s.setPlaybackState(
PlaybackState.Builder()
.setState(PlaybackState.STATE_PLAYING, 0, 1.0f)
.build()
)
s.isActive = true
session = s
}
// 核心:用播放进度模拟事件进度,seekbar 自然变成进度条
fun updateProgress(currentSec: Int, totalSec: Int) {
val s = session ?: return
val curMs = (currentSec.coerceAtLeast(0) * 1000).toLong()
val durMs = (totalSec.coerceAtLeast(1) * 1000).toLong()
s.setPlaybackState(
PlaybackState.Builder()
.setState(PlaybackState.STATE_PLAYING, curMs, 1.0f)
.setBufferedPosition(durMs) // 缓冲条 → 总时长
.build()
)
s.setMetadata(
MediaMetadata.Builder()
.putLong(MediaMetadata.METADATA_KEY_DURATION, durMs)
.build()
)
}
}Kotlin三个关键点:
STATE_PLAYING— 告诉 HyperOS”正在播放”,触发锁屏展开 + 灵动岛setPosition(curMs)— 当前进度(毫秒),驱动 seekbar 位置METADATA_KEY_DURATION— 总时长(毫秒),驱动 seekbar 最大值setBufferedPosition(durMs)— 把”缓冲位置”设为总时长,seekbar 上的灰色缓冲区变成进度条的背景
3.2 构建 MediaStyle 通知
注意要用原生 Notification.Builder 而非 NotificationCompat.Builder——后者不接受 Notification.MediaStyle。
val mediaStyle = Notification.MediaStyle()
.setShowActionsInCompactView(0, 1, 2) // 折叠时显示哪几个按钮
.setMediaSession(PlannerMediaSession.token) // 绑定假媒体会话
val notif = Notification.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_notification_progress)
.setLargeIcon(largeIcon) // 媒体通知左侧大图
.setContentTitle("测试事件")
.setContentText("1h 23m / 2h · 52% · Ends 10:00")
.setStyle(mediaStyle) // 关键:MediaStyle
.setColor(accent) // 没有开发者账户,setColor 在 MIUI 上也能生效
.addAction(/* Open */)
.addAction(/* Next task name */)
.addAction(/* Done */)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setOngoing(true)
.build()Kotlin3.3 在前台服务里更新进度
// PlannerForegroundService 的 progress loop 里
while (isActive) {
delay(5_000) // 5 秒刷新一次
val content = activeBlock.toProgressContent()
val notif = builder.build(content, ProgressSilentUpdate)
NotificationManagerCompat.from(this).notify(PROGRESS_ID, notif)
// 同步更新媒体通知 seekbar
PlannerMediaSession.updateProgress(content.elapsedSeconds, content.progressMax)
}Kotlin这样每次通知刷新时,MediaSession 的播放位置也跟着更新。HyperOS 看到播放位置在变,就持续保持锁屏展开 + 灵动岛显示。
4. 实际效果
| 场景 | 效果 |
|---|---|
| 锁屏 | 通知自动出现在锁屏最上方,展开显示事件名称、已用/总时长、进度条、下一任务 |
| 通知栏 | 大视图(MediaStyle),不会被折叠成”更多通知”,底部有 3 个操作按钮 |
| 灵动岛 | HyperOS 自动在状态栏/屏幕顶部弹出胶囊,显示事件名称和进度 |
| 进度条 | 媒体通知原生 seekbar 实时走事件进度,灰色缓冲条代表总时长 |
5. 为什么这不算 hack
- 用的是 Android 标准 API —
Notification.MediaStyle从 API 21 就存在,所有 Android 手机都支持,只不过 HyperOS 做了更好的适配。 - MediaSession 本来就可以”静默”播放 — 没有规定说
STATE_PLAYING必须对应真实音频流。倒计时、进度条、番茄钟……天生适合这种模式。 - 小米文档鼓励用媒体通知 — 澎湃OS开发文档专门有一章讲媒体通知适配,核心观点就是”用系统媒体通知,系统帮你优化”。
6. 注意事项
- 必须用原生
Notification.Builder而不是NotificationCompat.Builder,因为后者不接收Notification.MediaStyle。 - 目标设备 minSdk 至少 26(Android 8),因为需要通知渠道。API 21-25 需额外处理。
- 锁屏通知需要渠道 importance 设为
IMPORTANCE_HIGH,但不设 sound(setSound(null))就不会打扰用户。 - 如果没有真实媒体回话,用完记得
session.release()避免泄漏。 - 不要用自定义 RemoteViews 布局—— HyperOS 对自定义布局的通知可能折叠。
7. 总结
一行 .setStyle(Notification.MediaStyle()),加上一个假的播放中 MediaSession,换来了:锁屏通知、大视图、灵动岛胶囊、内嵌进度条——四个特性零 API 开销。
这招的适用范围远不止生活规划类 App:
- 🍅 番茄钟 — 进度 = 剩余时间,动作 = 暂停/跳过
- 🏃 运动计时 — 进度 = 已跑距离/时间
- 📦 文件下载 — 进度 = 已下载百分比
- 🚗 导航类 — 进度 = 已行驶/剩余路程
- ⏰ 倒计时器 — 进度 = 已过时间
本质上,任何需要一个”持续可见的进度通知”的场景,都可以套这个模板。
需求是锁屏通知 → 想到媒体通知 → 发现不需要开发者账户 → 创建假 MediaSession → 绑上进度。
这叫”夺舍”。
// 全文核心就三行
val mediaStyle = Notification.MediaStyle().setMediaSession(fakeToken)
val notif = Notification.Builder(ctx, channel).setStyle(mediaStyle)...
PlannerMediaSession.updateProgress(elapsed, total)Kotlin缺点:
会被其他音乐播放器顶掉…
2026-06-27 · LifeDrive 开发笔记 · 基于 HyperOS 2.0 / REDMI K70 实测


发表回复