15 KiB
15 KiB
步骤 02:Bridge 层实现(✅ 已完成)
实现 Go ↔ Kotlin 桥接层,包括 Go 侧 TSClient 导出、Kotlin 友好封装(TsClient.kt + TsModels.kt)、应用层桥接(TSBridge.kt)。
一、目标
- Go 侧 TSClient 结构体及方法导出(
go/teamspeak/bridge.go) - gomobile 编译为 AAR(
android/app/libs/teamspeak.aar) - Kotlin 友好封装层(
TsClient.kt+TsModels.kt) - 应用层桥接(
TSBridge.kt)
二、架构总览
┌─────────────────────────────────────────────────────────────┐
│ Kotlin 应用层 │
│ │
│ ViewModel ──→ TSBridge (object) ──→ TsClient (object) │
│ │ │ │
│ │ TsListener 回调 │ Kotlin 友好 API │
│ ↑ │ │
│ └── TsListener ─────────┘ │
│ │
├─────────────────────────────────────────────────────────────┤
│ gomobile 自动生成层 │
│ │
│ teamspeak.TSClient (Java) ← Go TSClient struct │
│ teamspeak.EventCallback (Java) ← Go EventCallback interface│
│ teamspeak.* 数据类 (Java) ← Go 导出结构体 │
│ │
├─────────────────────────────────────────────────────────────┤
│ Go 层 │
│ │
│ bridge.go (TSClient + EventCallback) ──→ teamspeak-go SDK │
│ │ │ │
│ │ 事件队列 + 单消费者 goroutine │ On* 回调 │
│ └──→ JNI 回调 ──→ EventCallback │ │
│ │
└─────────────────────────────────────────────────────────────┘
数据流:
| 方向 | 路径 | 说明 |
|---|---|---|
| Kotlin → Go | TSBridge → TsClient → gomobile TSClient → Go Client |
用户操作 |
| Go → Kotlin | Go On* → 事件队列 → JNI → EventCallback → TsClient 转换 → TsListener → TSBridge |
事件推送 |
三、三层架构详解
3.1 Go 层(bridge.go)
文件:go/teamspeak/bridge.go
Go 侧 Bridge 是 gomobile 导出的核心,将 teamspeak-go SDK 封装为可供 Kotlin 调用的 TSClient 类。
导出的接口与结构
// EventCallback — gomobile 导出的回调接口
type EventCallback interface {
OnConnected()
OnDisconnected(message string)
OnTextMessage(msg *TextMsg)
OnClientEnter(client *ServerClientView)
OnClientLeave(client *ServerClientView)
OnClientMoved(client *ClientMoved)
OnKicked(reason *ServerError)
OnTalkStatusChanged(talker *TalkStatusChange)
OnClientIDsDone()
OnServerError(error *ServerError)
}
// TSClient — gomobile 导出的桥接客户端
type TSClient struct {
client *teamspeak.Client
callback EventCallback
}
// 导出的数据结构(适配 gomobile 限制)
type ServerClientView struct { ... } // 频道成员视图
type ClientInfo struct { ... } // 完整客户端信息
type ChannelListItem struct { ... } // 频道列表项
type TextMsg struct { ... } // 文本消息
type ClientMoved struct { ... } // 客户端移动事件
type ServerError struct { ... } // 服务器错误
type TalkStatusChange struct { ... } // 说话状态变化
type ChannelListResult struct { ... } // 频道列表结果(含数组)
关键设计决策
- 字符串传递 ID:gomobile 不支持
uint64→ Kotlin Long 的安全映射,统一用string - 结构体包装:gomobile 不支持
[]*T,用ChannelListResult包装ChannelListItem[] - 错误用字符串:gomobile 不支持
error返回值,用空串=成功 - 事件队列:SDK 内部
evtQueue+ 单消费者 goroutine 串行分发,JNI 回调线程安全
3.2 Kotlin 友好封装层(TsClient.kt + TsModels.kt)
文件:android/app/src/main/java/com/tsmobile/app/TsClient.kt、TsModels.kt
这一层将 gomobile 生成的 Java 对象转为 Kotlin 友好接口,解决:
- 所有 API 为 getter/setter 而非属性
ChannelListItem[]需要手动转List<TsChannel>- 事件回调参数是
Object类型需要强转 - group 类型是
Int需要映射为ChanGroupType枚举
TsClient 对象
object TsClient {
// 连接
fun connect(identity, host, nickname, password, defaultChannel): Boolean
fun disconnect()
// 事件监听
fun setListener(listener: TsListener?)
// 查询(返回 Kotlin 友好类型)
fun getChannelList(): List<TsChannel>
fun getClientList(): List<TsClientInfo>
fun getClientId(): Long
fun getClientInfo(clientId: Long): TsClientInfo?
fun getChannelInfo(channelId: Long): TsChannel?
fun getSelf(): TsSelf
fun getChannelIdsByUid(uid: String, maxDepth: Int): List<Long>
// 属性
val serverVersion: String
val serverIp: String
val serverPlatform: String
val serverName: String
val serverCreated: Long
val serverUptime: Long
val maxClients: Int
val clientsOnline: Int
val channelsOnline: Int
// 操作
fun sendTextMessage(targetMode: Int, targetId: Long, msg: String): Boolean
fun clientMove(clientId: Long, channelId: Long, password: String): Boolean
fun clientPoke(clientId: Long, msg: String): Boolean
fun kickClient(clientId: Long, reasonId: Int, reasonMsg: String): Boolean
fun banClient(uid: String, timeInSeconds: Long, reasonMsg: String): Boolean
fun channelCreate(name: String, properties: Map<String, String>, permissions: List<TSPermission>): Long
fun channelUpdate(channelId: Long, properties: Map<String, String>): Boolean
fun channelDelete(channelId: Long, force: Boolean): Boolean
fun sendVoice(clientId: Long, codec: Int, data: ByteArray): Boolean
}
TsListener 接口
interface TsListener {
fun onConnected()
fun onDisconnected(error: String?)
fun onClientEnter(client: TsClientInfo)
fun onClientMoved(moved: TsClientMoved)
fun onClientLeave(client: ServerClientView)
fun onTalkStatusChanged(talker: TsTalker)
fun onClientIDsDone()
fun onTextMessage(msg: TsTextMessage)
fun onPoked(msg: TsTextMessage)
fun onKicked(reason: ServerError?)
fun onServerError(error: ServerError)
fun onChannelListChanged()
}
TsModels 数据类
// 频道
data class TsChannel(val channelListItem: ChannelListItem) {
val channelId get() = channelListItem.getChannelID()
val parentId get() = channelListItem.getParentChannelID()
val name get() = channelListItem.getName()
val order get() = channelListItem.getOrder()
val isPermanent get() = channelListItem.getIsPermanent()
val totalClients get() = channelListItem.getTotalClients()
// ... 更多属性
}
// 客户端
data class TsClientInfo(val serverClientView: ServerClientView) {
val clientId get() = serverClientView.getID()
val channelId get() = serverClientView.getChannelID()
val nickname get() = serverClientView.getNickname()
val uid get() = serverClientView.getUid()
val isTalker get() = serverClientView.getIsTalker()
// ... 更多属性
}
// 自身信息(可写)
class TsSelf internal constructor(
private val view: ServerClientView,
private val info: ClientInfo?
) {
var nickname
get() = view.getNickname()
set(value) { info?.setNickname(value) }
var isTalker
get() = view.getIsTalker()
set(value) { info?.setIsTalker(value) }
var inputMuted
get() = info?.getInputMuted() ?: false
set(value) { info?.setInputMuted(value) }
var outputMuted
get() = info?.getOutputMuted() ?: false
set(value) { info?.setOutputMuted(value) }
// ... 更多可写属性
}
// 移动事件
data class TsClientMoved(
val theClient: ServerClientView?,
val oldChannel: ChannelListItem?,
val newChannel: ChannelListItem?,
val visibility: Int
)
// 说话状态
data class TsTalker(
val client: ServerClientView?,
val isTalking: Boolean
)
// 文本消息
data class TsTextMessage(
val message: String,
val invokerUid: String,
val invokerName: String,
val invokerId: Long,
val targetMode: Int,
val targetClientId: Long,
val targetChannelId: Long
)
// 频道组/服务器组类型
enum class ChanGroupType(val value: Int) { ... }
enum class ChannelGroupType(val value: Int) { ... }
3.3 应用层桥接(TSBridge.kt)
文件:android/app/src/main/java/com/tsmobile/app/TSBridge.kt
TSBridge 是 ViewModel 层访问 Go 桥接的唯一入口,采用 object 单例模式。
核心职责
- 连接管理:封装
TsClient.connect()/disconnect() - 监听注册:在
connect()时自动设置TsListener - 查询转发:所有查询方法委托给
TsClient - 操作转发:所有操作方法委托给
TsClient
完整 API
object TSBridge {
// === 连接管理 ===
fun connect(identity: Any, host: String, nickname: String,
password: String = "", defaultChannel: String = "",
listener: TSBridgeListener? = null): Boolean
fun disconnect()
// === 查询 ===
fun getChannelList(): List<TsChannel>
fun getClientList(): List<TsClientInfo>
fun getClientId(): Long
fun getSelf(): TsSelf
fun getChannelInfo(channelId: Long): TsChannel?
fun getClientInfo(clientId: Long): TsClientInfo?
fun getChannelIdsByUid(uid: String): List<Long>
// === 操作 ===
fun sendTextMessage(targetMode: Int, targetId: Long, msg: String): Boolean
fun clientMove(clientId: Long, channelId: Long, password: String = ""): Boolean
fun clientPoke(clientId: Long, msg: String): Boolean
fun kickClient(clientId: Long, reasonId: Int, reasonMsg: String): Boolean
fun banClient(uid: String, timeInSeconds: Long, reasonMsg: String): Boolean
fun channelCreate(name: String, properties: Map<String, String> = emptyMap(),
permissions: List<TSPermission> = emptyList()): Long
fun channelUpdate(channelId: Long, properties: Map<String, String>): Boolean
fun channelDelete(channelId: Long, force: Boolean = false): Boolean
fun sendVoice(clientId: Long, codec: Int, data: ByteArray): Boolean
// === 服务器属性 ===
val serverVersion: String
val serverIp: String
val serverPlatform: String
val serverName: String
// ...
}
// 回调接口(简化版,供 ViewModel 使用)
interface TSBridgeListener {
fun onConnected()
fun onDisconnected(error: String?)
fun onClientEnter(client: TsClientInfo)
fun onClientMoved(moved: TsClientMoved)
fun onClientLeave(client: ServerClientView)
fun onTalkStatusChanged(talker: TsTalker)
fun onClientIDsDone()
fun onTextMessage(msg: TsTextMessage)
fun onPoked(msg: TsTextMessage)
fun onKicked(reason: ServerError?)
fun onServerError(error: ServerError)
}
四、数据流转详解
4.1 连接流程
ViewModel: TSBridge.connect(identity, host, nickname, password)
↓
TSBridge: TsClient.connect(identity, host, nickname, password)
↓
TsClient: TSClient.connect(...) // gomobile Java 对象
↓
Go: TSClient.Connect() → NewClient → registerHandlers → Connect → WaitConnected
↓
Go SDK: 收到 welcome → 触发 OnConnected
↓
Go Bridge: EventCallback.OnConnected() // JNI
↓
TsClient: listener?.onConnected() // Kotlin 友好回调
↓
TSBridge: listener?.onConnected()
↓
ViewModel: _state.update { it.copy(connected = true) }
4.2 查询流程
ViewModel: TSBridge.getChannelList()
↓
TSBridge: TsClient.getChannelList()
↓
TsClient: TSClient.requestChannelList() // gomobile
↓
TsClient: result.getChannels().map { TsChannel(it) } // 转为 Kotlin 类型
↓
TSBridge: 返回 List<TsChannel>
↓
ViewModel: _state.update { it.copy(channels = list) }
4.3 事件推送流程
TeamSpeak 服务器: notifyclientmoved
↓
Go SDK: handleNotification → evtQueue → startEventLoop
↓
Go Bridge: EventCallback.OnClientMoved(clientMoved)
↓ JNI
TsClient: TsListener.onClientMoved(TsClientMoved(view, old, new, vis))
↓
TSBridge: listener?.onClientMoved(moved)
↓
ViewModel: 处理移动事件,更新状态
五、gomobile 约束与应对
| 约束 | 影响 | 应对方案 |
|---|---|---|
不能导出 []string |
服务器组列表无法直接传递 | TsClientInfo 封装为逗号分隔字符串 |
不能导出 []*T |
频道/客户端列表无法返回切片 | ChannelListResult 包装 + TsClient 转 List |
不能导出 map[string]string |
属性列表无法传递 | channelCreate/channelUpdate 接受 Map,内部转 gomobile 类型 |
不能导出 error |
方法无法返回错误 | Boolean 返回值(true=成功) |
uint64 映射为 long |
频道 ID 可能溢出 | 统一用 string 传递 ID |
| 回调在 JNI 线程 | 不能直接操作 UI | TsListener 回调 → ViewModel + StateFlow 中转 |
| 所有字段为 getter/setter | Kotlin 不友好 | TsModels 包装为 val/var 属性 |
六、文件清单
| 文件 | 说明 |
|---|---|
go/teamspeak/bridge.go |
Go 侧桥接,导出 TSClient + EventCallback |
android/app/libs/teamspeak.aar |
gomobile 编译产物 |
android/app/src/main/java/com/tsmobile/app/TsClient.kt |
Kotlin 友好封装(TsClient 对象) |
android/app/src/main/java/com/tsmobile/app/TsModels.kt |
Kotlin 数据类(TsChannel, TsClientInfo 等) |
android/app/src/main/java/com/tsmobile/app/TSBridge.kt |
应用层桥接(单例) |
七、验收标准
| # | 验证项 | 验证方法 |
|---|---|---|
| 1 | bridge.go 可编译 | cd go && go build ./teamspeak 无报错 |
| 2 | gomobile 生成 AAR | gomobile bind ... 成功 |
| 3 | TsClient.connect() 可调用 | 编译通过,连接测试服务器成功 |
| 4 | TsListener 回调正常 | 连接后收到 onConnected、onClientEnter 等 |
| 5 | 查询返回 Kotlin 类型 | getChannelList() 返回 List<TsChannel> |
| 6 | 操作方法正常 | sendTextMessage、clientMove 等返回 true |
| 7 | 断开连接无崩溃 | disconnect() 后应用正常退出 |
八、参考文档
docs/sdk-bridge-api.md— TsClient API 完整参考docs/流程/00_总览.md— 运行架构、三条核心通道docs/流程/01_连接服务器.md— 连接时序、事件依赖CLAUDE.md— gomobile 限制说明、JNI 线程注意事项