2026-04-21 22:25:33 +08:00
|
|
|
|
# XuqmGroup Android SDK 文档
|
|
|
|
|
|
|
|
|
|
|
|
> Kotlin 2.3.10 · AGP 9.1.0 · minSdk 24 · compileSdk 36
|
|
|
|
|
|
|
|
|
|
|
|
## 模块结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
XuqmGroup-AndroidSDK/
|
2026-04-28 09:45:20 +08:00
|
|
|
|
├── sdk-core/ # 核心:初始化、HTTP、Token 存储、文件上传、通用工具/组件
|
2026-04-21 22:25:33 +08:00
|
|
|
|
├── sdk-im/ # IM:WebSocket 实时通信
|
|
|
|
|
|
├── sdk-push/ # 推送:设备 Token 注册
|
2026-04-28 09:45:20 +08:00
|
|
|
|
├── sdk-update/ # 版本管理:检查更新、下载安装
|
2026-05-07 19:39:38 +08:00
|
|
|
|
├── sdk-webview/ # WebView:嵌入式组件 / 独立页面
|
2026-04-21 22:25:33 +08:00
|
|
|
|
└── sample-app/ # 示例 App(Jetpack Compose)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 集成
|
|
|
|
|
|
|
|
|
|
|
|
### Gradle(Maven 私有仓库)
|
|
|
|
|
|
|
|
|
|
|
|
在项目根 `settings.gradle.kts` 添加仓库:
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
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 ?: ""
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
在 `gradle.properties` 或环境变量中配置:
|
|
|
|
|
|
```properties
|
|
|
|
|
|
NEXUS_USER=your_username
|
|
|
|
|
|
NEXUS_PASSWORD=your_password
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
引入依赖:
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
dependencies {
|
2026-05-11 15:21:54 +08:00
|
|
|
|
implementation("com.xuqm:sdk-core:0.4.2")
|
|
|
|
|
|
implementation("com.xuqm:sdk-im:0.4.2") // 可选
|
|
|
|
|
|
implementation("com.xuqm:sdk-push:0.4.2") // 可选
|
|
|
|
|
|
implementation("com.xuqm:sdk-update:0.4.2") // 可选
|
|
|
|
|
|
implementation("com.xuqm:sdk-webview:0.4.2") // 可选
|
2026-04-21 22:25:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 快速开始
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 初始化(Application.onCreate)
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
2026-04-27 19:00:54 +08:00
|
|
|
|
XuqmSDK.initialize(
|
2026-04-21 22:25:33 +08:00
|
|
|
|
context = this,
|
2026-04-29 15:46:39 +08:00
|
|
|
|
appKey = "ak_your_app_key",
|
2026-04-27 19:23:11 +08:00
|
|
|
|
logLevel = if (BuildConfig.DEBUG) LogLevel.DEBUG else LogLevel.WARN
|
2026-04-21 22:25:33 +08:00
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-27 19:00:54 +08:00
|
|
|
|
### 2. 用户登录后初始化 IM
|
2026-04-21 22:25:33 +08:00
|
|
|
|
|
|
|
|
|
|
```kotlin
|
2026-04-27 19:23:11 +08:00
|
|
|
|
// 调用业务登录接口,拿到 userSig 后只需要登录一次 SDK
|
2026-04-27 19:00:54 +08:00
|
|
|
|
val userSig = api.getUserSig(userId)
|
2026-04-27 19:23:11 +08:00
|
|
|
|
XuqmSDK.login(
|
|
|
|
|
|
userId = userId,
|
|
|
|
|
|
userSig = userSig,
|
|
|
|
|
|
)
|
2026-04-27 19:00:54 +08:00
|
|
|
|
|
2026-05-01 22:18:47 +08:00
|
|
|
|
// 如果工程里集成了 sdk-im / sdk-push / sdk-update,
|
|
|
|
|
|
// SDK 会自动完成对应模块的登录与初始化
|
2026-04-21 22:25:33 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-07 19:39:38 +08:00
|
|
|
|
### 3. WebView 独立模块
|
|
|
|
|
|
|
|
|
|
|
|
`sdk-webview` 不需要额外初始化,直接依赖 `sdk-core` 即可使用。
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
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()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-27 19:30:06 +08:00
|
|
|
|
### 3. 切换联调环境
|
|
|
|
|
|
|
|
|
|
|
|
默认使用外网域名。若要本地联调,可在 `Application.onCreate()` 里切换:
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
2026-04-30 14:59:47 +08:00
|
|
|
|
XuqmSDK.useLocalServiceEndpoints("192.168.113.37")
|
2026-04-27 19:30:06 +08:00
|
|
|
|
// 物理设备可改成你的电脑局域网 IP
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
如果是 sample-app,也可以直接调用:
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
2026-04-30 14:59:47 +08:00
|
|
|
|
SampleEnvironmentConfig.useLocalhost("192.168.113.37")
|
2026-04-27 19:30:06 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
切换后,HTTP API 会立即走新端点;如果 IM 已登录,SDK 会自动重新连接。
|
|
|
|
|
|
|
2026-04-27 19:33:08 +08:00
|
|
|
|
sample-app 里也提供了一个“环境设置”页面,可以直接在外网和本地联调之间切换。
|
|
|
|
|
|
|
2026-04-21 22:25:33 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## sdk-core
|
|
|
|
|
|
|
|
|
|
|
|
### SDKConfig
|
|
|
|
|
|
|
|
|
|
|
|
| 参数 | 类型 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
2026-04-29 15:46:39 +08:00
|
|
|
|
| `appKey` | String | 应用标识(租户平台获取) |
|
2026-04-27 19:23:11 +08:00
|
|
|
|
| `logLevel` | LogLevel | 日志等级 |
|
2026-04-21 22:25:33 +08:00
|
|
|
|
|
|
|
|
|
|
### TokenStore
|
|
|
|
|
|
|
2026-04-27 19:23:11 +08:00
|
|
|
|
基于 `EncryptedSharedPreferences` 持久化存储。
|
2026-04-21 22:25:33 +08:00
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 存储
|
2026-04-27 19:23:11 +08:00
|
|
|
|
XuqmSDK.tokenStore.saveToken("eyJ...")
|
2026-04-21 22:25:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 读取(协程)
|
2026-04-27 19:23:11 +08:00
|
|
|
|
val token = XuqmSDK.tokenStore.getToken()
|
2026-04-21 22:25:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 清除(登出)
|
|
|
|
|
|
XuqmSDK.tokenStore.clear()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### ApiClient
|
|
|
|
|
|
|
|
|
|
|
|
基于 OkHttp 5 + Retrofit 3,自动附加 Bearer Token,统一解析 `ApiResponse<T>`。
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 通过 RetrofitFactory 获取接口实例
|
|
|
|
|
|
val service = RetrofitFactory.create(MyApiService::class.java)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-05 15:48:08 +08:00
|
|
|
|
### FileSDK
|
|
|
|
|
|
|
|
|
|
|
|
文件上传、下载、打开的统一入口,位于 `com.xuqm.sdk.file`。
|
|
|
|
|
|
|
|
|
|
|
|
#### 上传
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 从 Uri(文件选择器返回值)上传,自动解析文件名和 MIME 类型
|
|
|
|
|
|
val result: FileUploadResult = FileSDK.upload(
|
|
|
|
|
|
context = context,
|
|
|
|
|
|
uri = uri,
|
|
|
|
|
|
onProgress = { progress -> /* 0–100 */ },
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 直接上传字节数组(如相机拍照后的 ByteArray)
|
|
|
|
|
|
val result = FileSDK.uploadBytes(
|
|
|
|
|
|
fileName = "photo.jpg",
|
|
|
|
|
|
mimeType = "image/jpeg",
|
|
|
|
|
|
bytes = byteArray,
|
|
|
|
|
|
onProgress = { progress -> },
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 上传 File 对象
|
|
|
|
|
|
val result = FileSDK.upload(file = file)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`FileUploadResult` 字段:`url`、`thumbnailUrl`、`hash`、`size`、`originalName`、`mimeType`、`ext`
|
|
|
|
|
|
|
|
|
|
|
|
#### 下载
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 下载存储目标
|
|
|
|
|
|
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+)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 打开文件
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 用系统应用打开本地文件(通过 FileProvider + ACTION_VIEW)
|
|
|
|
|
|
FileSDK.openFile(context, file)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
需在 `AndroidManifest.xml` 中配置 `FileProvider`:
|
|
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
|
<provider
|
|
|
|
|
|
android:name="androidx.core.content.FileProvider"
|
|
|
|
|
|
android:authorities="${applicationId}.fileprovider"
|
|
|
|
|
|
android:exported="false"
|
|
|
|
|
|
android:grantUriPermissions="true">
|
|
|
|
|
|
<meta-data
|
|
|
|
|
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
|
|
|
|
android:resource="@xml/file_paths" />
|
|
|
|
|
|
</provider>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-21 22:25:33 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## sdk-im
|
|
|
|
|
|
|
2026-04-27 19:47:48 +08:00
|
|
|
|
### Android sample 已具备
|
|
|
|
|
|
|
|
|
|
|
|
- 会话列表先读本地缓存,再刷新网络
|
|
|
|
|
|
- 联系人列表先读本地缓存,再刷新网络
|
|
|
|
|
|
- 聊天历史分页加载
|
|
|
|
|
|
- 当前会话本地搜索
|
2026-04-27 23:41:58 +08:00
|
|
|
|
- 输入草稿自动保存
|
|
|
|
|
|
- 群设置支持编辑群名和群公告
|
2026-04-28 09:45:20 +08:00
|
|
|
|
- 群组扩展 API 已补齐:管理员设置、禁言、解散群
|
|
|
|
|
|
- 公开群支持搜索、创建和加群审批
|
2026-04-27 23:41:58 +08:00
|
|
|
|
- 会话支持本地删除
|
|
|
|
|
|
- 显示总未读数
|
|
|
|
|
|
- 消息状态直接展示
|
|
|
|
|
|
- 会话置顶/免打扰/已读/草稿/删除同步服务端
|
2026-04-27 19:47:48 +08:00
|
|
|
|
- IM 连接状态提示
|
|
|
|
|
|
- SDK 登录态恢复后自动重连
|
2026-05-02 11:45:43 +08:00
|
|
|
|
- 重复调用登录会覆盖当前 IM 会话并自动重连,SDK 侧不做生命周期检测或维护
|
2026-04-28 09:45:20 +08:00
|
|
|
|
- 群聊支持 `@userId` 提及,并写入 `mentionedUserIds`
|
|
|
|
|
|
- 关系链支持好友申请、接受/拒绝、黑名单
|
|
|
|
|
|
- 支持图片 / 视频 / 音频 / 文件消息,文件通过独立文件服务上传后再发 IM
|
|
|
|
|
|
- 图片支持拍照、摄像、图库选择,文件支持文件管理器选择
|
|
|
|
|
|
- 语音支持长按录音、抬手发送
|
|
|
|
|
|
- 支持引用回复、群聊已读人数展示
|
|
|
|
|
|
- 支持 `LOCATION` / `CUSTOM` / `RICH_TEXT` / `FORWARD` / `QUOTE` / `MERGE` / `CALL_AUDIO` / `CALL_VIDEO` 等通用消息类型发送
|
|
|
|
|
|
- 单聊支持已读回执,服务端会把 `READ` 状态推回发送者
|
2026-05-07 19:39:38 +08:00
|
|
|
|
- `sdk-webview` 作为独立模块提供嵌入式组件和独立页面两种形态,可与 IM / Push / Update 任意组合
|
2026-04-27 19:47:48 +08:00
|
|
|
|
|
2026-04-21 22:25:33 +08:00
|
|
|
|
### ImClient
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
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!"
|
|
|
|
|
|
))
|
|
|
|
|
|
|
2026-04-28 09:45:20 +08:00
|
|
|
|
// 群聊提及
|
|
|
|
|
|
imClient.send(SendMessageParams(
|
|
|
|
|
|
toId = "group_001",
|
|
|
|
|
|
chatType = ChatType.GROUP,
|
|
|
|
|
|
msgType = MsgType.TEXT,
|
|
|
|
|
|
content = "@user_002 你好",
|
|
|
|
|
|
mentionedUserIds = "user_002",
|
|
|
|
|
|
))
|
|
|
|
|
|
|
2026-04-21 22:25:33 +08:00
|
|
|
|
// 撤回消息
|
|
|
|
|
|
imClient.revoke(msgId = "uuid")
|
|
|
|
|
|
|
|
|
|
|
|
// 断开连接(Activity/Fragment 销毁时调用)
|
|
|
|
|
|
imClient.disconnect()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 消息类型(MsgType)
|
|
|
|
|
|
|
2026-04-28 09:45:20 +08:00
|
|
|
|
`TEXT` / `IMAGE` / `VIDEO` / `AUDIO` / `FILE` / `CUSTOM` / `LOCATION` / `NOTIFY` / `RICH_TEXT` / `CALL_AUDIO` / `CALL_VIDEO` / `FORWARD` / `QUOTE` / `MERGE`
|
2026-04-21 22:25:33 +08:00
|
|
|
|
|
|
|
|
|
|
### ImMessage 结构
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2026-04-27 19:00:54 +08:00
|
|
|
|
### 推送接入
|
2026-04-21 22:25:33 +08:00
|
|
|
|
|
2026-04-29 09:50:09 +08:00
|
|
|
|
在 `XuqmSDK.login()` 成功后,SDK 会自动完成当前系统推送 token 的注册与上传,不再要求业务侧手工传入 token。`logout()` 时会自动注销当前设备绑定。
|
|
|
|
|
|
|
|
|
|
|
|
还可以按用户设置接收开关:
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
PushSDK.setReceivePush(context, enabled = false)
|
|
|
|
|
|
PushSDK.setReceivePush(context, enabled = true)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
如果项目接入了 Firebase Messaging,`sdk-push` 会通过 `FirebaseMessagingService.onNewToken()` 自动接收并上报 FCM token。对应服务已经随库注册,只要应用工程提供 Firebase 配置即可生效。
|
2026-04-21 22:25:33 +08:00
|
|
|
|
|
|
|
|
|
|
### 与 IM 联动
|
|
|
|
|
|
|
|
|
|
|
|
当 IM 和推送服务均已开启时,IM 服务在目标用户离线时会自动调用推送服务发送离线推送通知。
|
|
|
|
|
|
业务方只需分别注册推送 Token 和 IM 登录,无需额外配置。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## sdk-update
|
|
|
|
|
|
|
|
|
|
|
|
### 检查原生 App 更新
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
val result: UpdateResult = UpdateSDK.checkUpdate(
|
2026-05-08 18:31:59 +08:00
|
|
|
|
appKey = "ak_xxx",
|
2026-04-21 22:25:33 +08:00
|
|
|
|
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`)。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-05 15:48:08 +08:00
|
|
|
|
## 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 自动拦截以下两种场景,下载完成后调用 `FileSDK.openFile()` 打开文件:
|
|
|
|
|
|
|
|
|
|
|
|
- 带 `download` 属性的 `<a>` 标签,或链接以可下载扩展名(`.pdf`、`.zip`、`.docx` 等)结尾
|
|
|
|
|
|
- Blob URL(自动转 base64 后传给 native 处理)
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
XWebViewView(
|
|
|
|
|
|
config = XWebViewConfig(
|
|
|
|
|
|
url = "https://example.com",
|
|
|
|
|
|
downloadDestination = FileDownloadDestination.PublicDownloads, // 存入系统 Downloads
|
|
|
|
|
|
downloadNotificationTitle = "正在下载", // 通知栏进度
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### H5 监听下载进度
|
|
|
|
|
|
|
|
|
|
|
|
H5 页面可通过 `window.addEventListener` 接收下载进度和完成事件:
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 下载进度(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 消息通信
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// H5 发消息给 Native(触发 onMessage 回调)
|
|
|
|
|
|
window.XWebViewBridge.postMessage(JSON.stringify({ type: 'login', token: '...' }))
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
XWebViewConfig(
|
|
|
|
|
|
onMessage = { raw ->
|
|
|
|
|
|
val json = JSONObject(raw)
|
|
|
|
|
|
when (json.optString("type")) {
|
|
|
|
|
|
"login" -> { /* 处理登录 */ }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// Native 发消息给 H5
|
|
|
|
|
|
val controller = getXWebViewController()
|
|
|
|
|
|
controller?.postMessageToWeb("window.dispatchEvent(new CustomEvent('nativeMsg', { detail: { key: 'value' } }))")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-04-21 22:25:33 +08:00
|
|
|
|
## 发版
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 在 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` 中维护。
|