WuKongIM Docs

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 Plugin 8.1.4、Gradle 8.4 与 compileSdk 34
  • 一个 /readyz 正常且 Android 设备可访问 WebSocket Gateway 的 WuKongIM 单节点集群或多节点集群;
  • 业务后端能为 Alice 和 Bob 分别返回 uid、短期 tokenwebsocketUrl

先阅读身份与 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 是进程内单例,请使用两台设备、两个模拟器或两个独立应用进程:

  1. Alice 与 Bob 分别从业务后端取得自己的连接材料;
  2. 两端都观察 CONNECT,失败时只记录稳定错误码与阶段,不记录原始错误文本;
  3. Alice 调用 sendText("bob", ...),记录发送结果;
  4. Bob 核对 fromUidchannelIdmessageId 和 Payload;
  5. Bob 向 Alice 回发,验证反向链路;
  6. 重建 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、WEB 1、PC 2,不要沿用旧整数。
  • 关闭 debugLogging 后仍在 Logcat 看到原始 JSON 或 Params:先确认 Gradle 依赖解析到 1.0.5,清理旧构建,再用非生产 canary 区分 SDK 输出、应用自身日志和第三方网络日志。若 Release 产物仍泄露 canary,应停止上线并提交最小复现。
  • 消息重复:避免重复注册监听器,并按 messageId 去重。

下一步

继续阅读消息收发上线检查。需要离线恢复、会话、未读或推送时,先查看 SDK 选择版本与验证记录保留各次验证的完整环境和范围。

本页内容