Android 快速接入
安装固定版本 SDK,通过业务后端提供的连接材料完成在线双向收发和退出清理。
本教程使用 1.0.5,让 Alice 与 Bob 在两个独立客户端中在线收发。连接由 WebSocket JSON-RPC CONNECT 完成鉴权。
1. 准备接入
准备以下条件:
- Android 5.0(API 21)或更高版本;
- 一个 AndroidX 应用工程。作为精确参考,该 tag 自身使用 Kotlin
1.9.0、Android Gradle Plugin8.1.4、Gradle8.4与 compileSdk34; - 一个
/readyz正常且 Android 设备可访问 WebSocket Gateway 的 WuKongIM 单节点集群或多节点集群; - 业务后端能为 Alice 和 Bob 分别返回
uid、短期token与websocketUrl。
先阅读身份与 Token。示例变量来自业务后端,不应把真实 Token 写进 APK、源码、Logcat 或崩溃报告。
想先看到实际收发效果,可按运行官方示例准备两端;下文说明如何接入自己的应用。
默认设备类别为 APP 0;业务后端保存 Token 时使用相同的 device_flag。APP 为 0,WEB 为 1,PC 为 2。
2. 安装 SDK
在应用模块的 build.gradle.kts 中添加:
dependencies {
implementation("com.githubim:easysdk-android:1.0.5")
}Groovy DSL 对应写法为:
dependencies {
implementation 'com.githubim:easysdk-android:1.0.5'
}确认 gradle.properties 已启用 AndroidX:
android.useAndroidX=true网络权限
在 AndroidManifest.xml 中加入:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />生产环境使用 wss://。Android 9 及以后默认会限制明文网络,不要通过全局放开 cleartext 来绕过生产 TLS。
3. 连接与监听
data class IMBootstrap(
val uid: String,
val token: String,
val websocketUrl: String,
)业务后端负责认证当前产品用户并选择 WebSocket 地址。Android 客户端只得到自己的连接材料,不获得 Product HTTP 管理凭据。
Android EasySDK 是进程内单例,而且 v1.0.5 初始化后不能用另一份配置再次 init。下面示例适合一个进程只登录一个 UID 的最小验证;生产应用若支持账号切换,必须先针对该限制设计并实测完整退出策略。
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.githubim.easysdk.WuKongConfig
import com.githubim.easysdk.WuKongEasySDK
import com.githubim.easysdk.enums.WuKongChannelType
import com.githubim.easysdk.enums.WuKongEvent
import com.githubim.easysdk.listener.WuKongEventListener
import com.githubim.easysdk.model.ConnectResult
import com.githubim.easysdk.model.DisconnectInfo
import com.githubim.easysdk.model.Message
import com.githubim.easysdk.model.WuKongError
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
class ChatActivity : AppCompatActivity() {
private val easySDK = WuKongEasySDK.getInstance()
private var listenersRegistered = false
private var connected = false
private val connectListener = object : WuKongEventListener<ConnectResult> {
override fun onEvent(result: ConnectResult) = runOnUiThread {
connected = true
Log.i("EasySDK", "connected")
}
}
private val messageListener = object : WuKongEventListener<Message> {
override fun onEvent(message: Message) = runOnUiThread {
Log.i("EasySDK", "message received")
// 以 messageId 去重后再更新 UI。
}
}
private val disconnectListener = object : WuKongEventListener<DisconnectInfo> {
override fun onEvent(info: DisconnectInfo) = runOnUiThread {
connected = false
Log.i("EasySDK", "disconnected")
}
}
private val errorListener = object : WuKongEventListener<WuKongError> {
override fun onEvent(error: WuKongError) = runOnUiThread {
Log.e("EasySDK", "SDK operation failed")
}
}
fun connect(bootstrap: IMBootstrap) {
val config = WuKongConfig.Builder()
.serverUrl(bootstrap.websocketUrl)
.uid(bootstrap.uid)
.token(bootstrap.token)
.connectionTimeout(15_000)
.requestTimeout(15_000)
.maxReconnectAttempts(5)
.debugLogging(false)
.build()
val current = easySDK.getConfig()
if (current == null) {
easySDK.init(applicationContext, config)
} else {
check(
current.uid == bootstrap.uid &&
current.token == bootstrap.token &&
current.serverUrl == bootstrap.websocketUrl &&
current.connectionTimeoutMs == config.connectionTimeoutMs &&
current.requestTimeoutMs == config.requestTimeoutMs &&
current.maxReconnectAttempts == config.maxReconnectAttempts &&
current.deviceFlag == config.deviceFlag &&
current.debugLogging == config.debugLogging
) {
"WuKongEasySDK v1.0.5 cannot apply changed identity or configuration; " +
"stop and restart the process or upgrade the SDK"
}
}
registerListeners()
connected = easySDK.isConnected()
if (connected) return
lifecycleScope.launch {
runCatching { withTimeout(20_000) { easySDK.connect() } }
.onFailure {
connected = false
easySDK.disconnect()
Log.e("EasySDK", "connect failed")
}
}
}
private fun registerListeners() {
if (listenersRegistered) return
easySDK.addEventListener(WuKongEvent.CONNECT, connectListener)
easySDK.addEventListener(WuKongEvent.DISCONNECT, disconnectListener)
easySDK.addEventListener(WuKongEvent.MESSAGE, messageListener)
easySDK.addEventListener(WuKongEvent.ERROR, errorListener)
listenersRegistered = true
}
override fun onDestroy() {
if (listenersRegistered) {
easySDK.removeEventListener(WuKongEvent.CONNECT, connectListener)
easySDK.removeEventListener(WuKongEvent.DISCONNECT, disconnectListener)
easySDK.removeEventListener(WuKongEvent.MESSAGE, messageListener)
easySDK.removeEventListener(WuKongEvent.ERROR, errorListener)
listenersRegistered = false
}
if (isFinishing) easySDK.disconnect()
super.onDestroy()
}
}监听器必须在 init 之后注册,并保留同一个对象引用供 removeEventListener 使用。外层 withTimeout 把完整连接等待限制为 20 秒,失败后断开;SDK 内部连接与请求上限为 15 秒,自动重连最多 5 次。Activity 重建时先读取 isConnected(),不会等待一个不会重放的 CONNECT 事件。身份、Token、URL 或连接配置变化会显式停止,而不是静默沿用旧值。示例显式设置 debugLogging(false);Maven 1.0.5 会让协议解析和事件分发诊断同样服从该总开关,并对公开模型字符串脱敏。生产应用通常由 Application 级连接管理器持有 SDK,Activity 或 Fragment 只订阅 UI 所需事件;上面的页面级写法用于看清最小生命周期。
4. 收发第一条消息
把下面方法放进上一节的 ChatActivity 类,连接成功后从发送按钮调用。
fun sendText(targetUid: String, text: String) {
check(connected) { "EasySDK is not connected" }
lifecycleScope.launch {
val payload: String = org.json.JSONObject()
.put("type", 1)
.put("version", 1)
.put("content", text)
.toString()
runCatching {
easySDK.send(
channelId = targetUid,
channelType = WuKongChannelType.PERSON,
payload = payload,
)
}.onSuccess {
Log.i("EasySDK", "SEND completed")
}.onFailure {
Log.e("EasySDK", "send failed")
}
}
}send 返回只代表 Alice 得到了发送请求的结果。Bob 必须在自己的 MESSAGE 监听器中独立观察消息;不要在 Alice 的发送回调里把对端状态直接标为“已收到”。
由于 SDK 是进程内单例,请使用两台设备、两个模拟器或两个独立应用进程:
- Alice 与 Bob 分别从业务后端取得自己的连接材料;
- 两端都观察
CONNECT,失败时只记录稳定错误码与阶段,不记录原始错误文本; - Alice 调用
sendText("bob", ...),记录发送结果; - Bob 核对
fromUid、channelId、messageId和 Payload; - Bob 向 Alice 回发,验证反向链路;
- 重建 Activity,确认监听器没有重复;退出应用时确认连接已关闭。
5. 清理连接
onDestroy() 移除本页面的监听器;页面结束时断开连接。应用级连接应由 Application 持有。1.0.5 不能重新 init 另一份身份或配置,退出后也不能静默复用旧 Token。
6. 常见问题
SDK is not initialized:严格执行init → addEventListener → connect顺序。- 同一 UID 刷新 Token 或路由后提示配置变化:
v1.0.5没有公开重置配置 API;显式停止流程并重启进程,不能继续使用旧 Token 或旧地址。 - 连接地址在模拟器可用、真机不可用:检查地址是否只在宿主机或容器内部可达;让业务后端返回设备可达的 WSS 入口。
- CONNECT、SEND 或 RECVACK 解析失败:确认服务端包含 EasySDK JSON-RPC 兼容实现;当前服务端接受 snake_case 字段与 Android README 对齐的 JSON 文本字符串 Payload,也兼容 JSON 对象和 Base64 Payload,并为 Android 返回 snake_case 结果。设备值使用 APP
0、WEB1、PC2,不要沿用旧整数。 - 关闭
debugLogging后仍在 Logcat 看到原始 JSON 或Params:先确认 Gradle 依赖解析到1.0.5,清理旧构建,再用非生产 canary 区分 SDK 输出、应用自身日志和第三方网络日志。若 Release 产物仍泄露 canary,应停止上线并提交最小复现。 - 消息重复:避免重复注册监听器,并按
messageId去重。
下一步
继续阅读消息收发与上线检查。需要离线恢复、会话、未读或推送时,先查看 SDK 选择。版本与验证记录保留各次验证的完整环境和范围。