日期: 2026年6月27日

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

    夺舍 媒体通知

    夺舍媒体通知:

    让生活规划 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 实测