本文主要介绍 Android 媒体库客户端的两层结构决策:工程上为何坚持单一 App 产物 + 双 Interaction Shell;TV 界面上为何在行业仍以 Leanback 为主时,继续选择 Compose for TV 并拒绝把 Leanback 接回主线。

以 Octans Android 为例。播放内核、认证、API 与数据层希望全端共享;交互上 TV 又绝不是「平板横屏」。若过早拆 app-mobile / app-tv,或跟风把 TV 重写成 Leanback,会把成本花在错误的边界上。

决策一:单 App + 双 Interaction Shell

目标形态

single Android app artifact
  + shared core / data / domain / playback
  + touch interaction shell
  + TV interaction shell
  + 统一播放引擎与 runtime 治理
  • 只保留一个 Gradle App module(例如 :app)。
  • 手机、平板、Android TV / Google TV 安装同一应用产物(可按渠道 flavor 区分包名,但不等于两套业务工程)。
  • 隔离的是 交互模型、焦点、导航图、布局,不是登录态、播放 session、LGPL runtime 与许可证审计。

交互模式

InteractionMode = Auto | Touch | Tv
模式含义
Auto按设备能力进入对应 shell(判定实现以产品实现文档为准)
Touch触控优先,适配窗口尺寸与横竖屏
Tv遥控器 / 10-foot,独立返回栈与播放控制

交互模式不应分叉播放 engine、认证、repository 与 runtime 制品。

为什么拒绝双 App 入口

app-mobile + app-tv 在概念上清晰,但在个人主线 / 中小团队阶段会让:

  • Gradle、manifest、依赖、runtime artifact
  • 登录态、播放 engine、版本号、CI、装机验证

全部过早分裂。播放主线若已固定为单一 native 内核(如 LGPL libmpv),更应共享工程入口,只在 UI shell 处分叉。

为什么拒绝「一个响应式 UI 通吃 TV」

手机与平板可以共用触控 shell 做自适应;TV 不是平板横屏

  • 焦点与 D-pad / 返回键语义
  • 10-foot 信息密度与 overscan
  • 播放控制与空状态

必须允许独立设计。单 App 解决的是工程与契约统一,不是 UI 偷懒。

演进约束

  • 首版可先打通触控 compact shell + 播放 MVP,TV shell 后置,但目录与决策上预留,不反向拆双 App。
  • 只有代码体量与依赖边界真实变大时,再抽 Gradle module;不要为历史占位空模块复辟。

决策二:TV UI 框架继续 Compose for TV

背景张力

真机上 TV 首页焦点滚动可能「不跟手、中途停顿」。对照多款主流 Android TV 媒体客户端,静态分析常见路径是:

Leanback / View Presenter + GridView 回收
  → 选中一次滚动到位 的手感模型

问题于是变成:是否为了滚动手感,把 TV 主线切回 Leanback?

决策

TV interaction shell = Compose for TV / tv-material
  + foundation 列表与焦点 / bringIntoView 工程化
  + 不引入 Leanback Presenter / Browse 模板作为默认架构

与 touch 共享:认证、domain、data、播放 engine、设计语义。若局部需要 View 互操作,只能是可删除实验,不能反客为主。

行业对照(观察级,非竞品永久承诺)

类型TV UI 主架构(样本期)含义
多款存量媒体 TV 客户端Leanback 族(Presenter + Grid)历史定型 + 迁移成本
官方方向Leanback deprecated;新项目推荐 Compose for TV时间线:手机 Compose 成熟早于 TV Material
本项目Compose for TV与 touch 同栈,滚动问题当工程债还

Compose for TV 相对新tv-material 稳定晚于手机 Compose),存量客户端继续 Leanback 不等于「Compose 做不了 TV」,更像路径依赖。

拒绝的备选

方案结论原因
Leanback 作为 TV 主 UI拒绝等于第二套 TV 前端;与 touch Compose 分裂;官方已 deprecate
首页 Leanback、其余 Compose 混合拒绝作默认焦点、导航、生命周期与主题双轨,债务高于单栈攻坚
为滚动问题推倒重写拒绝收益主要是抄滚动模型,不是产品能力增量

与「实验更卡」记录的关系

若未提交的「性能 / 焦点」工作区实验体感更差,应整包回退到已知好提交,用单变量对照再优化;不要据此写成「Compose 路线失败」或「必须 Leanback」。观察级对照 ≠ 根因定论。

两层决策如何叠在一起

工程:single app + shared playback/auth/data
        ├─ touch shell(Compose / Material)
        └─ TV shell(Compose for TV)
                └─ 滚动 / 焦点持续工程化
                   (不切换 Leanback 主线)

播放内核演进(libmpv / Media3 实验)发生在 playback 层,不改变 shell 选型;UI 排障(Paging、滚动状态、会话恢复)发生在 touch shell 实现,也不改变「单 App」合同。

对同类产品的启示

  1. 先统一 artifact 与播放契约,再分叉交互;双 APK 是规模化选项,不是起步默认。
  2. TV 独立 shell 是产品要求,双 App 是工程选项——两者不要绑死。
  3. 行业手感来自长期 View 工程,不是 Leanback 商标魔法;换栈前算清重写面。
  4. 官方 deprecate 方向应进入长期决策,避免新主线往死路上堆。
  5. 性能实验要可回退、可二分;不要用「感觉卡」直接否定框架选型。

注意事项

  • 设备身份 / Auto 判定细节以实现文档为准,本文只固定「应有 shell 分叉」。
  • 竞品 APK 分析会随版本变化,表中结论仅作选型背景。
  • 本文不承诺 TV 滚动已达竞品手感;只固定框架主线与工程策略。

相关阅读

来源

本文改写自 Octans 项目「Android 单 App 与双 Interaction Shell」决策,以及「Android TV UI 框架:Compose for TV」决策。行业对照为样本期静态分析摘要;实现路径已抽象。