Skip to content

qwfy5287/LiveTalk

Repository files navigation

LiveTalk

一个集成 IM 即时通讯直播观看 的 Android 原生 App,聚焦高级岗位关注的工程与架构深度:消息可靠性、长连接生命周期管理、高帧率弹幕渲染、Compose + 多模块 Clean Architecture、Jetpack 全家桶。

项目定位:本仓库的目的是在代码层面向面试官完整展示我的能力——而不是做一款能跑的产品。代码中所有关键抽象都配有注释,解释"为什么这样写"而不只是"做了什么"。


一、技术栈

维度 选型
语言 Kotlin 2.0.20(Compose 编译器 Gradle 插件)
UI 100% Jetpack Compose + Material 3
架构 Clean Architecture(data / domain / ui 分层)+ MVI + StateFlow
异步 Coroutines + Flow
DI Hilt(Convention Plugin 自动接线)
网络 OkHttp + Retrofit + kotlinx.serialization + WebSocket
持久化 Room + DataStore
分页 Paging 3 + Room Paging
播放 Media3 / ExoPlayer(HLS 拉流)
动画 Lottie + Animatable / withFrameMillis 自绘
构建 Gradle 8.9 + AGP 8.5 + Version Catalogs + Convention Plugins
质量 Detekt + ktlint + Kotest + MockK + Turbine
性能 Baseline Profile + Macrobenchmark + LeakCanary
CI GitHub Actions(assembleDebug + unit tests + detekt)

二、模块划分

LiveTalk/
├── build-logic/              Convention Plugins(消除 15 个模块的 build.gradle 重复)
├── app/                      壳工程 + Navigation + HiltApplication
├── core/
│   ├── common/               Dispatcher qualifier、Clock、AppResult、Logger
│   ├── designsystem/         Material3 主题、品牌色板、通用组件(Avatar 等)
│   ├── model/                纯 Kotlin 数据模型(跨层契约,JVM 模块)
│   ├── network/              OkHttp/Retrofit 封装、AuthInterceptor、TokenAuthenticator、WebSocketFactory
│   ├── database/             Room Database、DAO、Converter、DataStore
│   └── ui/                   通用 UI 能力(弹幕引擎、礼物 Combo 横幅)
├── data/
│   ├── im/                   IM 长连接、消息协议、Outbox/Ack、Inbound 路由、前台服务、Repository
│   ├── live/                 直播 API、Repository、Paging 3 PagingSource
│   └── user/                 用户登录、Session、TokenProvider 实现(OkHttp 刷新)
└── feature/
    ├── auth/                 登录页(MVI + Channel 单次事件)
    ├── home/                 直播列表(Paging + 网格卡片)
    ├── conversation/         会话列表(实时未读数)
    ├── chat/                 聊天页(Paging 分页 + 气泡 + 发送状态)
    └── liveroom/             直播间(ExoPlayer + 弹幕 + 礼物)

依赖方向app → feature → data → core,单向无环;core:model 是纯 JVM 模块,禁止任何 Android 依赖,保证领域层可在纯 JVM 测试中运行。


三、核心亮点(面试讨论点)

3.1 IM 长连接与消息可靠性(data/im 模块

这是项目最重点的部分,对齐线上 IM 的生产实践。

① WebSocket 状态机 + 心跳 + 退避重连ImConnectionManager.kt

  • 状态流:Idle → Connecting → Authenticating → Connected → Backoff → ...
  • 双心跳:OkHttp 内置 pingInterval(25s) + 应用层 Ping/Pong。应用层心跳带 nonce 时间戳,并跟踪 lastPongAtMs;超过 pongTimeoutMs (45s) 没收到 Pong 就主动断连——这是捕获 NAT 半开连接的关键(OkHttp 的 ping 在某些运营商 NAT 下会"看起来成功"但对端其实已经掉线)。
  • 退避算法:Decorrelated Jitter(AWS 官方推荐),避免大量客户端在服务端恢复时同时重连产生雪崩(BackoffPolicy.kt)。
  • 单写线程:使用 Dispatchers.IO.limitedParallelism(1) 作为 IM 网络分发器,保证出站帧的绝对顺序,并省去对 OkHttp WebSocket writer 的显式锁。

② 消息可靠性三板斧OutboundMessageQueue.kt + AckReconciler.kt

需求 机制
不丢(at-least-once) 消息持久化到 outbox 表,进程被杀也能恢复;ACK 到才从 outbox 删除
不重(去重) 客户端生成 clientMessageId 作为幂等键,服务端以此返回稳定的 serverMessageId
有序(同会话顺序) 单写线程 + 工作循环按 nextAttemptAtMillis 升序拉取;MessageDaoserverSequence 排序
重试(网络抖动) 指数退避 baseRetryDelayMs * 2^(attempts-1),最高 60s;超过 6 次标记 FAILED,UI 出现重试按钮

用户体验enqueue() 在一个 DB 事务里同时写 messages(PENDING)outbox,UI 观察 Room Flow,在真正发送之前就渲染出消息气泡——这是消息类应用"发送即刻显示"观感的实现方式。

③ 入站消息与离线补拉InboundMessageRouter.kt

  • 每个会话维护 ackedSequence,收到 Push 时比对 serverSequence > ackedSequence + 1 即检测到序列断层,立即发 PullSince 请求服务端把缺口补回来。
  • Backfill 支持分页(hasMore 标志),大块离线消息不会一次性占爆内存。
  • Push 写入使用 INSERT OR IGNORE,防止"已经在 outbox 里的消息"和"服务端 Push"双路并发时产生重复行。

④ 后台常驻ImForegroundService.kt

  • Android 14+ 要求 foregroundServiceType="dataSync" 才能合法常驻。
  • Service 只负责"让连接活着",业务逻辑全部通过依赖注入的单例执行;符合 Android 官方后台限制演进。

3.2 直播播放与弹幕(feature/liveroom + core/ui

① ExoPlayer 拉流LivePlayer.kt

  • 采用 Media3 最新 HlsMediaSourcesetAllowChunklessPreparation(true) 降低首帧延迟。
  • 生命周期感知:ON_STOP 暂停播放,ON_START 恢复,避免用户切后台时持续消耗带宽。
  • 连接/读取超时做得很小(5s/8s),直播场景宁愿快速失败切换协议也不要长时间卡白屏。

② 高性能弹幕DanmakuEngine.kt + DanmakuHost.kt

这是我想重点讲的 Compose 性能优化案例:

  • Track-based 布局:所有弹幕共享同一个 withFrameMillis 时间戳,X 坐标由 (currentMs - startMs) * speedPxPerMsdrawWithContent 里算出——零每帧分配
  • 对象跟踪而非复用mutableStateListOf<LivePlacement>,到期直接移除,不搞传统 RecyclerView 式的对象池(Compose 下复用带来的好处小于心智成本)。
  • 权重分级:普通弹幕在所有赛道抢位,礼物弹幕优先占用上半部赛道,避免重要信息被淹没。
  • 背压DanmakuDispatcher 使用 SharedFlow(extraBufferCapacity=256, DROP_OLDEST)——礼物风暴时扔掉旧的而不是积压。
  • 为什么不用 AnimatedVisibility 堆叠:100+ 条弹幕会产生 100+ 个独立动画 clock,每帧都触发大量重组;我们用单一 clock + drawWithContent 保证 draw 成本 O(active)、重组成本几乎为 0。

③ 礼物连击GiftComboBanner.kt

  • Animatable keyed on comboSeq —— 每次 combo 递增都触发一次 spring 弹跳,banner 本体不卸载不重建。
  • 数字使用 Modifier.scale(animated) 在 GPU 层放大,不占 CPU 重组。

3.3 架构与工程化

① Convention Pluginsbuild-logic/

7 个自定义插件(livetalk.android.librarylivetalk.android.feature 等),每个 feature 模块的 build.gradle.kts 只有 10-15 行。升级 Kotlin/AGP/SDK 只改一处。

② Version Cataloggradle/libs.versions.toml

所有版本号、依赖、插件 ID 集中声明。模块里写 libs.androidx.compose.ui 而不是字符串坐标,Android Studio 可补全、重构。

③ 依赖反转

core:network 定义 TokenProvider 接口;data:userSessionTokenProvider 实现。避免 network 模块依赖 user 模块(否则形成环),让认证细节(刷新、Keystore 等)可以独立演进。

④ 分层可测性

  • Clock 接口 + FakeClock:IM 层所有时间判断可在单元测试里确定性推进。
  • Dispatcher qualifier:测试中替换为 StandardTestDispatcher,协程调度可控。
  • WebSocketFactory seam:测试中注入假 factory,模拟 onMessage/onFailure。

⑤ Token 刷新并发控制SessionTokenProvider.kt

401 风暴场景下(很多请求同时失败),用 Mutex 串行刷新并在锁内做 double-check——第一个 caller 刷新、后续 caller 直接复用新 token。避免刷新 token 被并发请求连续消费导致失效。

⑥ 消息存储索引MessageEntity.kt

  • (conversationId, serverSequence) 联合索引:聊天页分页查询的主路径。
  • serverMessageId 唯一索引:服务端推送去重。
  • nextAttemptAtMillis 单列索引:Outbox 重试调度器扫描热点。

四、构建与运行

4.1 环境

  • JDK 17+(JDK 21 也可用,已验证)
  • Android SDK 34(platform-34、build-tools 34.0.0)
  • macOS / Linux / Windows 均可

首次拉仓库后,根据你的 Android SDK 路径改 local.properties

sdk.dir=/your/path/to/android-sdk

4.2 本机编译 + 装机演示(Demo 模式默认开启)

# 编译 debug APK(产物:app/build/outputs/apk/debug/app-debug.apk)
./gradlew :app:assembleDebug

# 连上手机/模拟器后直接装
./gradlew :app:installDebug

# 或者手动推 apk:
adb install -r app/build/outputs/apk/debug/app-debug.apk

首次构建会下载 Gradle 8.9 distribution(~120MB)和 AGP/Compose 等依赖(~500MB),需要 3-5 分钟;之后增量构建 < 30s。

4.3 Demo 模式下的完整流程

Demo 模式是默认开启的(core/common/AppConfig.demoMode = true),没有后端也能跑完整流程:

  1. 登录页:随便输一个手机号,密码可空,点"登录"直接进主页。
  2. 直播 tab:看到 6 张假直播封面,点击任意一张进入直播间。
  3. 直播间
    • 视频源是 Apple 公开的 HLS 测试流(稳定不停),能看到真实的视频解码与播放
    • 顶部是主播信息条,中间是自动滚动的弹幕(每 220ms 一条,高优先级会走上半部赛道)
    • 底部"发弹幕"按钮点一下自己也能插入一条
  4. 消息 tab:5 个预置会话,其中 2 条带未读红点。
  5. 聊天页:点任一会话进入,底部输入任意文字发送——消息立刻显示为 SENT 状态;每 6-8 秒会有假 Push 从后端(DB 层注入)进来。
  6. 底部未读角标:随 Push 增长;点进会话后清零。

4.4 其它命令

# 全部单元测试(BackoffPolicy / FrameCodec / DanmakuEngine)
./gradlew testDebugUnitTest

# 静态检查
./gradlew detekt

# 清缓存重来
./gradlew clean

# 查看完整任务列表
./gradlew tasks

4.5 常见问题

症状 解决
Directory does not exist 指向 sdk.dir local.properties 里的 sdk.dir= 为你本机路径
Unsupported Java version 升级 JDK 到 17+;项目 CI 用的是 JDK 17
首次 sync 超慢 用代理或镜像(Gradle 和 Maven Central 在国内不稳)
装机后看不到视频 Demo HLS 在 devstreaming-cdn.apple.com,有时国内网络会超时;换 VPN 即可
APK 24MB 偏大 Debug 带 LeakCanary + Compose tooling;:app:assembleRelease 会用 minify + shrinkResources 瘦到 < 10MB

4.6 切换到生产模式

core/common/src/main/kotlin/com/qwfy/livetalk/core/common/AppConfig.kt 里的 demoMode 改成 false。那时:

  • 登录会真打 HTTP 请求(当前后端域名是占位,会 404)
  • IM 会尝试连 WebSocket(同上)
  • 直播列表会是空(API 返回 404)

这是预期行为 —— 这个仓库的目的是展示代码,不是跑真实服务。

4.7 最低 SDK

支持 Android 7.0 / API 24 起步。



五、有意识的取舍与待办

以下项被故意搁置,换取在关键路径上投入更多——每一项都会在面试时展开解释:

取舍 原因 生产做法
协议用 JSON 不是 Protobuf 不想拉进 protoc 工具链,让仓库开箱即用 升级为 Protobuf(envelope 形状一致,已按此预留)
Token 存 DataStore 明文 展示 DataStore 用法 Jetpack Security / Keystore 包一层
直播推流仅保留接口 推流涉及 CameraX + MediaCodec + OpenGL ES + RTMP 编码,单独一篇文章 独立仓库演示
服务端 Mock 面试是聚焦客户端代码,真实后端不在本项目范围 MockWebServer + JSON Fixture 已接入可自启
UI 测试覆盖率低 Compose 测试成本高,优先把单元测试写在 IM 核心逻辑上 补充 robolectric + compose test
WebRTC 连麦 和直播推流同一量级的系统工程 单独仓库

六、代码导览(推荐阅读顺序)

  1. 构建骨架settings.gradle.ktsgradle/libs.versions.tomlbuild-logic/convention/**
  2. IM 核心(重点):
    • data/im/connection/ImConnectionManager.kt(状态机)
    • data/im/connection/BackoffPolicy.kt(退避)
    • data/im/outbound/OutboundMessageQueue.kt(发送+重试)
    • data/im/outbound/AckReconciler.kt(ACK 对账)
    • data/im/inbound/InboundMessageRouter.kt(入站+补拉)
  3. 网络层core/network/auth/*(AuthInterceptor、TokenAuthenticator、SessionTokenProvider)
  4. 弹幕core/ui/danmaku/DanmakuEngine.kt + DanmakuHost.kt
  5. 直播间feature/liveroom/player/LivePlayer.kt + LiveRoomScreen.kt

七、我在这个项目里想传达的能力

  • Android 工程化:多模块划分、Convention Plugins、Version Catalog、依赖倒置
  • Kotlin 原生:Coroutines/Flow 高级模式、状态机、Channel vs SharedFlow 选型
  • Compose 深度:自定义 Layout、withFrameMillis 驱动动画、性能意识(Stable/Immutable、derivedStateOf、对象分配)
  • IM 系统设计:长连接、心跳、退避、ACK、幂等、离线补拉、持久化 outbox
  • 音视频:ExoPlayer 配置、HLS 参数、生命周期与带宽管理
  • 工程质量:可测性设计(Clock / Dispatcher / Factory 抽象)、单元测试、Detekt、CI

欢迎一起看代码、提问。

About

No description, website, or topics provided.

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages