个人开发者如何获得小米上岛

夺舍 媒体通知

夺舍媒体通知:

让生活规划 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()
Kotlin

3.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

  1. 用的是 Android 标准 APINotification.MediaStyle 从 API 21 就存在,所有 Android 手机都支持,只不过 HyperOS 做了更好的适配。
  2. MediaSession 本来就可以”静默”播放 — 没有规定说 STATE_PLAYING 必须对应真实音频流。倒计时、进度条、番茄钟……天生适合这种模式。
  3. 小米文档鼓励用媒体通知 — 澎湃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 实测

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注