17 KiB
XuqmGroup Android SDK 文档
Kotlin 2.3.10 · AGP 9.1.0 · minSdk 24 · compileSdk 36
模块结构
XuqmGroup-AndroidSDK/
├── sdk-core/ # 核心:初始化、基础 HTTP、本地文件、时间、安全存储
├── sdk-file/ # 平台文件上传与 FileProvider 打开能力
├── sdk-bugcollect/ # 日志监控:错误上报、Crash 捕获、漏斗分析(含 Gradle Plugin)
├── sdk-im/ # IM:WebSocket 实时通信
├── sdk-push/ # 推送:设备 Token 注册
├── sdk-update/ # 版本管理:检查更新、下载安装
├── sdk-webview/ # WebView:嵌入式组件 / 独立页面
└── sample-app/ # 示例 App(Jetpack Compose)
集成
Gradle(Maven 私有仓库)
在项目根 settings.gradle.kts 添加仓库:
dependencyResolutionManagement {
repositories {
maven {
url = uri("https://nexus.xuqinmin.com/repository/android-hosted/")
credentials {
username = providers.gradleProperty("NEXUS_USER").orNull ?: ""
password = providers.gradleProperty("NEXUS_PASSWORD").orNull ?: ""
}
}
}
}
所有宿主变体必须应用 common 构建插件。它会扫描宿主 src/**,只接受唯一固定路径
src/main/assets/config/config.xuqmconfig;任何改名、嵌套或其它 sourceSet 中的
.xuqmconfig 文件都会直接失败。
Debug 执行本地 V2 验签,Release 额外执行租户平台在线有效性校验,并负责不可变 buildId 和
BugCollect 构建门禁,并生成扩展自动初始化所需的唯一 Manifest 标记;BugCollect 插件
仅负责 R8 mapping 上传。Debug 自动采集固定关闭,但非应急禁用的可调试宿主可通过内部
开发入口显式发送一次测试事件。
plugins {
id("com.xuqm.common") version "VERSION"
// 使用 BugCollect 且 Release 开启 R8 时再添加:
id("com.xuqm.bugcollect") version "VERSION"
}
在 gradle.properties 或环境变量中配置:
NEXUS_USER=your_username
NEXUS_PASSWORD=your_password
引入依赖:
dependencies {
implementation("com.xuqm:sdk-core:VERSION")
implementation("com.xuqm:sdk-file:VERSION") // 平台文件能力可选
implementation("com.xuqm:sdk-bugcollect:VERSION") // 可选
implementation("com.xuqm:sdk-im:VERSION") // 可选
implementation("com.xuqm:sdk-push:VERSION") // 可选
implementation("com.xuqm:sdk-update:VERSION") // 可选
implementation("com.xuqm:sdk-webview:VERSION") // 可选
}
快速开始
1. 初始化
从租户平台下载签名的 config.xuqmconfig,原样放入
app/src/main/assets/config/config.xuqmconfig。ContentProvider 会自动初始化,宿主无需
编写初始化代码,也不得修改、复制、自行生成或在 flavor/buildType 下放置第二份文件。
配置中的 serverUrl 是唯一平台地址且必填,运行时没有默认地址或跨平台回退。
仅依赖 sdk-core 时不会合并初始化 Provider、通知权限或 FileProvider,也不要求配置
文件、初始化或登录。引入任一需要平台能力的扩展后,各 AAR 中同名声明会合并为唯一
XuqmMergedProvider;Provider 只有检测到 common 构建插件生成的标记后才会自动读取
上述配置,避免未应用插件的宿主发生隐式初始化。
2. 用户登录后初始化 IM
// 调用业务登录接口,拿到 userSig 后只需要登录一次 SDK
val userSig = api.getUserSig(userId)
XuqmSDK.setUserInfo(XuqmUserInfo(userId = userId, userSig = userSig))
// 如果工程里集成了 sdk-im / sdk-push / sdk-update,
// SDK 会自动完成对应模块的登录与初始化
3. WebView 独立模块
sdk-webview 不需要额外初始化,直接依赖 sdk-core 即可使用。
import com.xuqm.sdk.webview.XWebViewConfig
import com.xuqm.sdk.webview.XWebViewView
import com.xuqm.sdk.webview.XWebViewScreen
import com.xuqm.sdk.webview.openXWebView
// 嵌入式组件:直接放到页面中,不包含导航栏 / 状态栏
XWebViewView(
config = XWebViewConfig(
url = "https://example.com",
title = "嵌入式网页",
),
)
// 独立页面:先设置配置,再跳转到页面
openXWebView(
XWebViewConfig(
url = "https://example.com",
title = "独立页面",
)
)
// navigate("xwebview") 后使用 XWebViewScreen()
sdk-core
SDKConfig
| 参数 | 类型 | 说明 |
|---|---|---|
appKey |
String | 应用标识(租户平台获取) |
logLevel |
LogLevel | 日志等级 |
TokenStore
基于统一的 SecureStore 持久化:密钥由 Android Keystore 生成且不可导出,
值使用 AES-256-GCM 加密并通过 namespace/key 关联数据认证。
Sample Release 签名
示例应用的 Release 签名只从用户级 ~/.gradle/gradle.properties 读取,仓库不保存
密钥库路径或凭据。需要配置的属性名为:
YIWANGXIN_STORE_FILEYIWANGXIN_STORE_PASSWORDYIWANGXIN_ALIASYIWANGXIN_KEY_PASSWORD
缺少属性或密钥库文件不可读时,Release 构建会在签名前失败;Debug 使用 Android 默认调试签名,不读取生产签名材料。
Sample 还要求把租户平台签发且包名匹配的文件放到
sample-app/src/main/assets/config/config.xuqmconfig。该文件被 Git 忽略;Debug 与
Release 构建都会在文件缺失时给出明确错误,禁止提交、伪造或手工修改配置。
运行 Sample 仪器集成测试时,测试密码通过
-e XUQM_TEST_PASSWORD <value> 注入,禁止写入源码或 Gradle 配置。
// 存储
XuqmSDK.tokenStore.saveToken("eyJ...")
// 读取(协程)
val token = XuqmSDK.tokenStore.getToken()
// 清除(登出)
XuqmSDK.tokenStore.clear()
网络边界
sdk-core 的 CommonHttpClient 与下载/摘要能力无需 SDK 初始化;依赖租户平台认证、
服务地址和 FileProvider 的上传、打开能力统一由 sdk-file 的 PlatformFileSDK 提供。
FileSDK
纯本地能力位于 com.xuqm.sdk.file.FileSDK;平台上传与 FileProvider 打开位于
com.xuqm.sdk.file.platform.PlatformFileSDK。
上传
// 从 Uri(文件选择器返回值)上传,自动解析文件名和 MIME 类型
val result: FileUploadResult = PlatformFileSDK.upload(
context = context,
uri = uri,
onProgress = { progress -> /* 0–100 */ },
)
// 直接上传字节数组(如相机拍照后的 ByteArray)
val result = PlatformFileSDK.uploadBytes(
fileName = "photo.jpg",
mimeType = "image/jpeg",
bytes = byteArray,
onProgress = { progress -> },
)
// 上传 File 对象
val result = PlatformFileSDK.upload(file = file)
FileUploadResult 字段:url、thumbnailUrl、hash、size、originalName、mimeType、ext
。上传入口会自动等待平台初始化;服务端业务失败或响应结构错误统一抛出保留
code/status/message 的 PlatformFileException,不会暴露原始响应体。
下载
// 下载存储目标
sealed class FileDownloadDestination {
data object Sandbox : FileDownloadDestination() // 应用私有目录(无需权限)
data object PublicDownloads : FileDownloadDestination() // 系统 Downloads 文件夹
}
// 下载到指定目标,支持通知栏进度
val file: File = FileSDK.download(
context = context,
downloadUrl = "https://example.com/report.pdf",
fileName = "report.pdf", // 可选,默认从 URL 推断
destination = FileDownloadDestination.PublicDownloads,
notificationTitle = "正在下载", // 非 null 时显示通知栏进度条
onProgress = { progress -> /* 0–100,同步进度到 H5 或 UI */ },
)
通知栏进度:设置
notificationTitle后,下载过程中通知栏会显示带进度条的持续通知,完成后自动切换为完成图标。需在AndroidManifest.xml中声明POST_NOTIFICATIONS权限(Android 13+)。
打开文件
// 用系统应用打开本地文件(通过 FileProvider + ACTION_VIEW)
PlatformFileSDK.openFile(context, file)
openFile 是纯本地操作,不要求 SDK 初始化。
sdk-file 是该 FileProvider 的唯一所有者;sdk-update、sdk-im 和 sdk-webview
均复用它。只集成 sdk-core 不会向宿主注入 Provider,也不存在 core 内的上传或打开
转发 API。
sdk-im
Android sample 已具备
- 会话列表先读本地缓存,再刷新网络
- 联系人列表先读本地缓存,再刷新网络
- 聊天历史分页加载
- 当前会话本地搜索
- 输入草稿自动保存
- 群设置支持编辑群名和群公告
- 群组扩展 API 已补齐:管理员设置、禁言、解散群
- 公开群支持搜索、创建和加群审批
- 会话支持本地删除
- 显示总未读数
- 消息状态直接展示
- 会话置顶/免打扰/已读/草稿/删除同步服务端
- IM 连接状态提示
- SDK 登录态恢复后自动重连
- 重复调用登录会覆盖当前 IM 会话并自动重连,SDK 侧不做生命周期检测或维护
- 群聊支持
@userId提及,并写入mentionedUserIds - 关系链支持好友申请、接受/拒绝、黑名单
- 支持图片 / 视频 / 音频 / 文件消息,文件通过独立文件服务上传后再发 IM
- 图片支持拍照、摄像、图库选择,文件支持文件管理器选择
- 语音支持长按录音、抬手发送
- 支持引用回复、群聊已读人数展示
- 支持
LOCATION/CUSTOM/RICH_TEXT/FORWARD/QUOTE/MERGE/CALL_AUDIO/CALL_VIDEO等通用消息类型发送 - 单聊支持已读回执,服务端会把
READ状态推回发送者 sdk-webview作为独立模块提供嵌入式组件和独立页面两种形态,可与 IM / Push / Update 任意组合
ImClient
val imClient = ImClient()
// 注册事件监听
imClient.listener = object : ImEventListener {
override fun onConnected() { /* WebSocket 连接成功 */ }
override fun onDisconnected(code: Int, reason: String) { /* 断开 */ }
override fun onMessage(msg: ImMessage) { /* 收到新消息 */ }
override fun onRevoke(msgId: String, operatorId: String) { /* 消息被撤回 */ }
override fun onError(t: Throwable) { /* 连接错误 */ }
}
// 连接
imClient.connect()
// 发送消息
imClient.send(SendMessageParams(
toId = "user_002",
chatType = ChatType.SINGLE,
msgType = MsgType.TEXT,
content = "Hello!"
))
// 群聊提及
imClient.send(SendMessageParams(
toId = "group_001",
chatType = ChatType.GROUP,
msgType = MsgType.TEXT,
content = "@user_002 你好",
mentionedUserIds = "user_002",
))
// 撤回消息
imClient.revoke(msgId = "uuid")
// 断开连接(Activity/Fragment 销毁时调用)
imClient.disconnect()
消息类型(MsgType)
TEXT / IMAGE / VIDEO / AUDIO / FILE / CUSTOM / LOCATION / NOTIFY / RICH_TEXT / CALL_AUDIO / CALL_VIDEO / FORWARD / QUOTE / MERGE
ImMessage 结构
data class ImMessage(
val id: String,
val fromId: String,
val toId: String,
val chatType: ChatType, // SINGLE / GROUP
val msgType: MsgType,
val content: String,
val extra: String?,
val revoked: Boolean,
val createdAt: String
)
自动重连
断线后指数退避重连,初始间隔 3 秒,最大间隔 30 秒。调用 disconnect() 后停止重连。
sdk-push
推送接入
宿主调用 XuqmSDK.setUserInfo(XuqmUserInfo(...)) 后,SDK 会自动完成当前系统推送
token 的注册与上传,不再要求业务侧手工传入 token;传入 null 时自动注销当前设备绑定。
还可以按用户设置接收开关:
PushSDK.setReceivePush(context, enabled = false)
PushSDK.setReceivePush(context, enabled = true)
如果项目接入了 Firebase Messaging,sdk-push 会通过 FirebaseMessagingService.onNewToken() 自动接收并上报 FCM token。对应服务已经随库注册,只要应用工程提供 Firebase 配置即可生效。
与 IM 联动
当 IM 和推送服务均已开启时,IM 服务在目标用户离线时会自动调用推送服务发送离线推送通知。
业务方只需分别注册推送 Token 和 IM 登录,无需额外配置。
sdk-update
检查原生 App 更新
val result: UpdateResult = UpdateSDK.checkUpdate(
appKey = "ak_xxx",
platform = Platform.ANDROID
)
if (result.needsUpdate) {
// result.versionName, result.downloadUrl, result.forceUpdate
UpdateSDK.downloadAndInstall(context, result.downloadUrl)
}
downloadAndInstall 会将 APK 下载到 getExternalFilesDir(null),通过 FileProvider 触发系统安装。
AndroidManifest 中已配置 @xml/file_paths(external-files-path)。
sdk-webview
XWebViewView 是基于 android.webkit.WebView 封装的 Jetpack Compose 组件,内置文件选择、拍照、下载拦截和 JS 通信能力。
XWebViewConfig 完整参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
url |
String | "" |
初始加载地址 |
title |
String | "" |
页面标题(独立页面模式使用) |
hideToolbar |
Boolean | false |
隐藏独立页面顶栏 |
hideStatusBar |
Boolean | false |
隐藏状态栏 |
userAgent |
String? | null |
自定义 User-Agent |
injectedJavaScript |
String? | null |
页面加载后注入的额外 JS |
jsBridgeName |
String | "XWebViewBridge" |
JS 桥接对象名 |
debugEnabled |
Boolean | false |
开启 WebView 远程调试 |
downloadDestination |
FileDownloadDestination | Sandbox |
下载文件存储目标 |
downloadNotificationTitle |
String? | null |
非 null 时通知栏显示下载进度 |
onMessage |
(String) -> Unit? |
null |
H5 发送消息的回调 |
文件选择与拍照
WebView 内 <input type="file"> 和 <input type="file" capture> 均已内置处理:
accept="image/*"+capture→ 调起系统相机,自动申请CAMERA权限accept=".docx,.xlsx"等扩展名格式 → 自动映射为正确的 MIME 类型后调起文件选择器getUserMedia()WebRTC 摄像头 → 自动请求CAMERA权限后授权
下载拦截
注入的 JS 自动拦截以下两种场景,下载完成后调用 PlatformFileSDK.openFile() 打开文件:
- 带
download属性的<a>标签,或链接以可下载扩展名(.pdf、.zip、.docx等)结尾 - Blob URL(自动转 base64 后传给 native 处理)
公共 Downloads 复用 FileSDK 的 MediaStore 实现,不申请
MANAGE_EXTERNAL_STORAGE,也不会跳转“所有文件访问权限”设置页。Android 9 及以下
仍按系统规范处理 WRITE_EXTERNAL_STORAGE 运行时权限。
XWebViewView(
config = XWebViewConfig(
url = "https://example.com",
downloadDestination = FileDownloadDestination.PublicDownloads, // 存入系统 Downloads
downloadNotificationTitle = "正在下载", // 通知栏进度
)
)
H5 监听下载进度
H5 页面可通过 window.addEventListener 接收下载进度和完成事件:
// 下载进度(0–100)
window.addEventListener('__xwvDownloadProgress', (e) => {
console.log(e.detail.url, e.detail.progress)
})
// 下载完成
window.addEventListener('__xwvDownloadDone', (e) => {
if (e.detail.success) {
console.log('下载成功', e.detail.url)
} else {
console.error('下载失败', e.detail.error)
}
})
H5 ↔ Native 消息通信
// H5 发消息给 Native(触发 onMessage 回调)
window.XWebViewBridge.postMessage(JSON.stringify({ type: 'login', token: '...' }))
XWebViewConfig(
onMessage = { raw ->
val json = JSONObject(raw)
when (json.optString("type")) {
"login" -> { /* 处理登录 */ }
}
}
)
// Native 发消息给 H5
val controller = getXWebViewController()
controller?.postMessageToWeb("window.dispatchEvent(new CustomEvent('nativeMsg', { detail: { key: 'value' } }))")
发版
# 在 gradle.properties 中配置 NEXUS_USER / NEXUS_PASSWORD
./gradlew :sdk-core:publish
./gradlew :sdk-im:publish
./gradlew :sdk-push:publish
./gradlew :sdk-update:publish
发布至 https://nexus.xuqinmin.com/repository/android-hosted/,groupId com.xuqm,版本号在各模块 build.gradle.kts 中维护。
Jenkins SSH
Windows Jenkins 节点通过 jenkins-ssh-key 私钥凭据生成构建期临时
git-ssh.cmd 执行版本提交推送,作用域结束后立即删除该脚本。