docs: 添加 CLAUDE.md + 各 module README + sdk-webview JSBridge 补全
Agent 7 + Agent 8: - CLAUDE.md 项目上下文 - 各 module README (core/im/push/update/webview/log) - XWebViewStandardHandlers.kt 补全标准 JSBridge handler
这个提交包含在:
父节点
fbafc8d802
当前提交
4a18d06c63
36
CLAUDE.md
36
CLAUDE.md
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
## 项目定位
|
## 项目定位
|
||||||
|
|
||||||
XuqmGroup Android SDK,Gradle multi-module 项目。为集成宿主 App(如 YwxMobileApp)提供:初始化、用户认证、OTA 更新、WebView、IM、推送、证书、**日志追踪**。
|
XuqmGroup Android SDK,Gradle multi-module 项目。为集成宿主 App(如 YwxMobileApp)提供:初始化、用户认证、OTA 更新、WebView、IM、推送、证书、日志追踪。
|
||||||
|
|
||||||
|
- Git 远端:`https://xuqinmin.com/xuqmGroup/XuqmGroup-AndroidSDK.git`
|
||||||
- Nexus Maven 发布:`https://nexus.xuqinmin.com/repository/android-hosted/`
|
- Nexus Maven 发布:`https://nexus.xuqinmin.com/repository/android-hosted/`
|
||||||
- groupId:`com.xuqm`
|
- groupId:`com.xuqm`
|
||||||
|
- 技术栈:Kotlin 2.3.10,AGP 9.1.0,minSdk 24,compileSdk 36,Java 21
|
||||||
|
|
||||||
## 模块结构
|
## 模块结构
|
||||||
|
|
||||||
@ -59,13 +61,6 @@ XuqmSDK.logEnabled // 是否开启日志
|
|||||||
XuqmSDK.appKey // 当前 appKey
|
XuqmSDK.appKey // 当前 appKey
|
||||||
```
|
```
|
||||||
|
|
||||||
## 向下兼容约束
|
|
||||||
|
|
||||||
- `XuqmSDK.initialize()` 现有参数不得删除或修改类型
|
|
||||||
- `XuqmSDK.setUserInfo()` 现有 `XuqmUserInfo` 字段不得删除
|
|
||||||
- `SdkPlatformConfig` 新增字段一律为可选(`val xxx: Type? = null`)
|
|
||||||
- 旧服务端不返回新字段时,客户端使用合理默认值
|
|
||||||
|
|
||||||
## 日志 SDK(sdk-log)
|
## 日志 SDK(sdk-log)
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
@ -86,16 +81,18 @@ XLog.defineFunnel("checkout", listOf("cart_view", "checkout_start", "payment_don
|
|||||||
|
|
||||||
`logApiUrl` 由 SDK 在 init 后从平台配置自动获取,无需 App 传入。
|
`logApiUrl` 由 SDK 在 init 后从平台配置自动获取,无需 App 传入。
|
||||||
|
|
||||||
|
## 向下兼容约束
|
||||||
|
|
||||||
|
- `XuqmSDK.initialize()` 现有参数不得删除或修改类型
|
||||||
|
- `XuqmSDK.setUserInfo()` 现有 `XuqmUserInfo` 字段不得删除
|
||||||
|
- `SdkPlatformConfig` 新增字段一律为可选(`val xxx: Type? = null`)
|
||||||
|
- 旧服务端不返回新字段时,客户端使用合理默认值
|
||||||
|
|
||||||
## 集成验证项目
|
## 集成验证项目
|
||||||
|
|
||||||
YwxMobileApp:`/Users/xuqinmin/Projects/TrustProjects/Pad/YwxMobileApp`
|
YwxMobileApp:`/Users/xuqinmin/Projects/TrustProjects/Pad/YwxMobileApp`
|
||||||
使用 Nexus Maven 引入 SDK,开发阶段可改为 `project()` 本地引用。
|
使用 Nexus Maven 引入 SDK,开发阶段可改为 `project()` 本地引用。
|
||||||
|
|
||||||
## 任务文档
|
|
||||||
|
|
||||||
- Agent 7 任务:`/Users/xuqinmin/Projects/XuqmProjects/YiwangxinApp4/docs/agent-tasks/agent7-android-sdk.md`
|
|
||||||
- Agent 8 任务:`/Users/xuqinmin/Projects/XuqmProjects/YiwangxinApp4/docs/agent-tasks/agent8-docs.md`
|
|
||||||
|
|
||||||
## 常用命令
|
## 常用命令
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -103,4 +100,17 @@ YwxMobileApp:`/Users/xuqinmin/Projects/TrustProjects/Pad/YwxMobileApp`
|
|||||||
./gradlew :sdk-core:assembleDebug
|
./gradlew :sdk-core:assembleDebug
|
||||||
./gradlew :sample-app:installDebug
|
./gradlew :sample-app:installDebug
|
||||||
./gradlew publish # 发布所有模块到 Nexus
|
./gradlew publish # 发布所有模块到 Nexus
|
||||||
|
|
||||||
|
# 发布单个模块
|
||||||
|
./gradlew :sdk-core:publish -PSDK_CORE_VERSION=1.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 发版配置
|
||||||
|
|
||||||
|
在 `gradle.properties` 中配置:
|
||||||
|
```properties
|
||||||
|
NEXUS_USER=your_username
|
||||||
|
NEXUS_PASSWORD=your_password
|
||||||
|
```
|
||||||
|
|
||||||
|
发布至 `https://nexus.xuqinmin.com/repository/android-hosted/`,版本号在各模块 `build.gradle.kts` 中维护。
|
||||||
|
|||||||
93
sdk-core/README.md
普通文件
93
sdk-core/README.md
普通文件
@ -0,0 +1,93 @@
|
|||||||
|
# sdk-core
|
||||||
|
|
||||||
|
XuqmGroup Android SDK 核心模块。提供 SDK 初始化、HTTP 请求、Token 管理、文件操作、通用工具。
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
implementation("com.xuqm:sdk-core:VERSION")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 初始化
|
||||||
|
|
||||||
|
### 方式 A — ContentProvider 自动初始化(推荐)
|
||||||
|
|
||||||
|
将 `config.xuqm` 放入 `src/main/assets/xuqm/`,SDK 在 App 启动时自动读取并初始化。
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// XuqmInitializerProvider 自动触发,无需代码
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式 B — 手动初始化
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Application.onCreate() 中:
|
||||||
|
XuqmSDK.initialize(context, appKey = "xxx")
|
||||||
|
XuqmSDK.initialize(context, appKey = "xxx", platformUrl = "https://xxx")
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### XuqmSDK(全局单例)
|
||||||
|
|
||||||
|
| API | 说明 |
|
||||||
|
|-----|------|
|
||||||
|
| `XuqmSDK.initialize(context, appKey, platformUrl?, logLevel?)` | 手动初始化 |
|
||||||
|
| `XuqmSDK.awaitInitialization()` | 等待平台配置拉取(coroutine) |
|
||||||
|
| `XuqmSDK.isInitialized()` | 检查是否已初始化 |
|
||||||
|
| `XuqmSDK.setUserInfo(info)` | 设置用户信息,触发所有子模块登录 |
|
||||||
|
| `XuqmSDK.setUserInfo(null)` | 登出,触发全局登出 |
|
||||||
|
| `XuqmSDK.getUserId()` | 获取当前 userId |
|
||||||
|
| `XuqmSDK.getUserInfo()` | 获取当前用户信息 |
|
||||||
|
| `XuqmSDK.appKey` | 当前 appKey |
|
||||||
|
| `XuqmSDK.platformConfig` | 平台配置(init 后可用) |
|
||||||
|
| `XuqmSDK.logApiUrl` | 日志服务地址(从平台配置获取) |
|
||||||
|
| `XuqmSDK.logEnabled` | 是否启用日志上报 |
|
||||||
|
| `XuqmSDK.useLocalServiceEndpoints(ip)` | 切换本地联调环境 |
|
||||||
|
| `XuqmSDK.tokenStore` | Token 存储(EncryptedSharedPreferences) |
|
||||||
|
|
||||||
|
### XuqmUserInfo
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
data class XuqmUserInfo(
|
||||||
|
val userId: String, // 必填
|
||||||
|
val userSig: String? = null, // IM 登录凭证(可选)
|
||||||
|
val name: String? = null,
|
||||||
|
val phone: String? = null,
|
||||||
|
val avatar: String? = null,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### TokenStore
|
||||||
|
|
||||||
|
基于 `EncryptedSharedPreferences` 持久化存储。
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
XuqmSDK.tokenStore.saveToken("eyJ...")
|
||||||
|
val token = XuqmSDK.tokenStore.getToken()
|
||||||
|
XuqmSDK.tokenStore.clear()
|
||||||
|
```
|
||||||
|
|
||||||
|
### ApiClient
|
||||||
|
|
||||||
|
基于 OkHttp 5 + Retrofit 3,自动附加 Bearer Token。
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val service = RetrofitFactory.create(MyApiService::class.java)
|
||||||
|
```
|
||||||
|
|
||||||
|
### FileSDK
|
||||||
|
|
||||||
|
文件上传、下载、打开的统一入口(`com.xuqm.sdk.file`)。
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// 上传
|
||||||
|
val result = FileSDK.upload(context, uri, onProgress = { /* 0-100 */ })
|
||||||
|
val result = FileSDK.uploadBytes(fileName, mimeType, bytes, onProgress)
|
||||||
|
|
||||||
|
// 下载
|
||||||
|
val file = FileSDK.download(context, url, fileName, destination, notificationTitle, onProgress)
|
||||||
|
|
||||||
|
// 打开
|
||||||
|
FileSDK.openFile(context, file)
|
||||||
|
```
|
||||||
56
sdk-im/README.md
普通文件
56
sdk-im/README.md
普通文件
@ -0,0 +1,56 @@
|
|||||||
|
# sdk-im
|
||||||
|
|
||||||
|
XuqmGroup Android SDK IM 模块。提供 WebSocket 实时通信、消息收发、群组管理。
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
implementation("com.xuqm:sdk-im:VERSION")
|
||||||
|
implementation("com.xuqm:sdk-core:VERSION") // 必须
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
**无需手动初始化。** `XuqmSDK.login(userId, userSig)` 成功后自动完成 IM 登录。
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// ImClient 直接使用
|
||||||
|
val imClient = ImClient()
|
||||||
|
imClient.listener = object : ImEventListener {
|
||||||
|
override fun onConnected() { }
|
||||||
|
override fun onMessage(msg: ImMessage) { }
|
||||||
|
override fun onRevoke(msgId: String, operatorId: String) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
imClient.connect()
|
||||||
|
imClient.send(SendMessageParams(
|
||||||
|
toId = "user_002",
|
||||||
|
chatType = ChatType.SINGLE,
|
||||||
|
msgType = MsgType.TEXT,
|
||||||
|
content = "Hello!"
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### ImClient
|
||||||
|
|
||||||
|
| API | 说明 |
|
||||||
|
|-----|------|
|
||||||
|
| `imClient.connect()` | 连接 WebSocket |
|
||||||
|
| `imClient.disconnect()` | 断开连接 |
|
||||||
|
| `imClient.send(params)` | 发送消息 |
|
||||||
|
| `imClient.revoke(msgId)` | 撤回消息 |
|
||||||
|
| `imClient.listener` | 事件监听器 |
|
||||||
|
|
||||||
|
### 消息类型
|
||||||
|
|
||||||
|
`TEXT` / `IMAGE` / `VIDEO` / `AUDIO` / `FILE` / `CUSTOM` / `LOCATION` / `NOTIFY` / `RICH_TEXT` / `CALL_AUDIO` / `CALL_VIDEO` / `FORWARD` / `QUOTE` / `MERGE`
|
||||||
|
|
||||||
|
### 自动重连
|
||||||
|
|
||||||
|
断线后指数退避重连,初始间隔 3 秒,最大间隔 30 秒。调用 `disconnect()` 后停止。
|
||||||
|
|
||||||
|
### 群聊
|
||||||
|
|
||||||
|
支持 `@userId` 提及,写入 `mentionedUserIds`。群组管理 API 包含:创建、解散、禁言、设置管理员、转让群主。
|
||||||
@ -1,94 +1,39 @@
|
|||||||
# Xuqm License SDK
|
# sdk-license
|
||||||
|
|
||||||
Android SDK for device license registration and verification.
|
XuqmGroup Android SDK 证书授权模块。提供设备 License 验证能力。
|
||||||
|
|
||||||
## Features
|
## 依赖
|
||||||
|
|
||||||
- **Stable Device ID**: Uses `ANDROID_ID` (stable per app signing key + device + user), with UUID fallback
|
|
||||||
- **Token Caching**: Caches valid status for configurable window (default 10 min) to reduce network calls
|
|
||||||
- **Auto Recovery**: Automatically re-registers when token is invalid or revoked
|
|
||||||
- **Offline Support**: Returns cached OK status when network is unavailable
|
|
||||||
- **Encrypted License File**: Reads `assets/xuqm/license.xuqm`; the file does not expose appKey or server URL as readable text
|
|
||||||
- **AppKey Change Detection**: Automatically clears old data when decrypted appKey changes
|
|
||||||
|
|
||||||
## Integration
|
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
dependencies {
|
implementation("com.xuqm:sdk-license:VERSION")
|
||||||
implementation("com.xuqm:sdk-license:0.1.0-SNAPSHOT")
|
implementation("com.xuqm:sdk-core:VERSION") // 必须
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## 使用
|
||||||
|
|
||||||
### License File
|
**无需独立初始化。** 内部自动等待 `XuqmSDK` 就绪。
|
||||||
|
|
||||||
Download the encrypted License file from the tenant platform and place it at:
|
|
||||||
|
|
||||||
```text
|
|
||||||
app/src/main/assets/xuqm/license.xuqm
|
|
||||||
```
|
|
||||||
|
|
||||||
The SDK also accepts a tenant-platform downloaded file such as
|
|
||||||
`app/src/main/assets/xuqm/MyApp.xuqmlicense`.
|
|
||||||
|
|
||||||
No explicit initialization code is required.
|
|
||||||
|
|
||||||
### Check License
|
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
LicenseSDK.checkLicense { isValid ->
|
// 验证 License
|
||||||
if (isValid) {
|
LicenseSDK.checkLicense(context, callback = object : LicenseCallback {
|
||||||
// allow app usage
|
override fun onResult(result: LicenseResult) {
|
||||||
} else {
|
when (result) {
|
||||||
// block app usage or show warning
|
is LicenseResult.Success -> { /* 验证通过 */ }
|
||||||
|
is LicenseResult.Denied -> { /* 验证失败 */ }
|
||||||
|
is LicenseResult.Error -> { /* 错误 */ }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
|
// 获取状态
|
||||||
|
val status = LicenseSDK.getStatus()
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional user metadata can be uploaded during the same check. All fields are optional.
|
## API
|
||||||
|
|
||||||
```kotlin
|
| API | 说明 |
|
||||||
LicenseSDK.checkLicense(
|
|-----|------|
|
||||||
userInfo = LicenseUserInfo(
|
| `LicenseSDK.checkLicense(context, callback)` | 验证设备 License |
|
||||||
userId = "u_10001",
|
| `LicenseSDK.getStatus()` | 获取当前 License 状态 |
|
||||||
name = "张三",
|
| `LicenseSDK.getDeviceId()` | 获取设备 ID |
|
||||||
email = "zhangsan@example.com",
|
| `LicenseSDK.clear()` | 清除本地 License 缓存 |
|
||||||
phone = "13800000000",
|
|
||||||
),
|
|
||||||
) { isValid ->
|
|
||||||
// allow or block app usage
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Status (no network)
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
val status = LicenseSDK.getStatus() // OK, DENIED, or UNKNOWN
|
|
||||||
```
|
|
||||||
|
|
||||||
### Re-register (force)
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
lifecycleScope.launch {
|
|
||||||
val result = LicenseSDK.reRegister()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Device ID
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
val deviceId = LicenseSDK.getDeviceId()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
| Parameter | Default | Description |
|
|
||||||
|-----------|---------|-------------|
|
|
||||||
| `appKey` | from encrypted file | AppKey from tenant platform |
|
|
||||||
| `deviceName` | `${MANUFACTURER} ${MODEL}` | Device display name |
|
|
||||||
| `baseUrl` | `https://auth.dev.xuqinmin.com/` | License server base URL |
|
|
||||||
|
|
||||||
## Data Storage
|
|
||||||
|
|
||||||
All data (device ID, token, status) is stored in `EncryptedSharedPreferences` with AES-256 encryption.
|
|
||||||
|
|||||||
55
sdk-log/README.md
普通文件
55
sdk-log/README.md
普通文件
@ -0,0 +1,55 @@
|
|||||||
|
# sdk-log
|
||||||
|
|
||||||
|
XuqmGroup Android SDK 日志模块。提供日志采集、Crash 捕获、漏斗分析能力。
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
implementation("com.xuqm:sdk-log:VERSION")
|
||||||
|
implementation("com.xuqm:sdk-core:VERSION") // 必须
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Application.onCreate() 中(XuqmSDK.initialize 之后):
|
||||||
|
XLog.setLogLevel(LogLevel.INFO)
|
||||||
|
XLog.setEnvironment("production")
|
||||||
|
XLog.startCrashCapture()
|
||||||
|
|
||||||
|
// 业务代码中
|
||||||
|
XLog.event("page_view", mapOf("page" to "home"))
|
||||||
|
XLog.captureError(exception)
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### XLog 对象
|
||||||
|
|
||||||
|
| API | 说明 |
|
||||||
|
|-----|------|
|
||||||
|
| `XLog.setLogLevel(level)` | 设置日志级别(`DEBUG` / `INFO` / `WARN` / `ERROR`) |
|
||||||
|
| `XLog.setEnvironment(env)` | 设置环境标签 |
|
||||||
|
| `XLog.startCrashCapture()` | 开启全局 UncaughtExceptionHandler |
|
||||||
|
| `XLog.event(name, properties?)` | 记录自定义事件 |
|
||||||
|
| `XLog.captureError(error, metadata?)` | 上报异常 |
|
||||||
|
| `XLog.warn(message, metadata?)` | 记录警告 |
|
||||||
|
| `XLog.info(message, metadata?)` | 记录信息 |
|
||||||
|
| `XLog.defineFunnel(id, steps)` | 定义漏斗 |
|
||||||
|
|
||||||
|
### LogLevel
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
enum class LogLevel { DEBUG, INFO, WARN, ERROR, NONE }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 工作原理
|
||||||
|
|
||||||
|
- **LogQueue**:事件进入内存队列,按批次异步上传到 `logApiUrl`
|
||||||
|
- **CrashCapture**:注册 `Thread.UncaughtExceptionHandler`,捕获 Native Crash 并写入文件,下次启动时上传
|
||||||
|
- **Fingerprint**:为错误生成指纹(基于 message + stack),服务端去重聚合
|
||||||
|
- **FunnelTracker**:客户端维护漏斗进度,服务端跨 session 聚合
|
||||||
|
|
||||||
|
### 配置
|
||||||
|
|
||||||
|
`logApiUrl` 和 `logEnabled` 由 `sdk-core` 在 init 后从平台配置自动获取,无需 App 传入。
|
||||||
@ -1,6 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("org.jetbrains.kotlin.android")
|
|
||||||
id("maven-publish")
|
id("maven-publish")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -8,7 +7,7 @@ group = rootProject.group
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.xuqm.sdk.log"
|
namespace = "com.xuqm.sdk.log"
|
||||||
compileSdk = 35
|
compileSdk = 36
|
||||||
defaultConfig { minSdk = 24 }
|
defaultConfig { minSdk = 24 }
|
||||||
publishing { singleVariant("release") { withSourcesJar() } }
|
publishing { singleVariant("release") { withSourcesJar() } }
|
||||||
|
|
||||||
|
|||||||
34
sdk-push/README.md
普通文件
34
sdk-push/README.md
普通文件
@ -0,0 +1,34 @@
|
|||||||
|
# sdk-push
|
||||||
|
|
||||||
|
XuqmGroup Android SDK 推送模块。支持多厂商推送(华为/小米/OPPO/vivo/荣耀/FCM/APNs)。
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
implementation("com.xuqm:sdk-push:VERSION")
|
||||||
|
implementation("com.xuqm:sdk-core:VERSION") // 必须
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
**无需手动初始化。** `XuqmSDK.login()` 成功后自动完成推送 token 注册。`logout()` 自动解绑。
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// 开启/关闭接收推送
|
||||||
|
PushSDK.setReceivePush(context, enabled = false)
|
||||||
|
PushSDK.setReceivePush(context, enabled = true)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 支持厂商
|
||||||
|
|
||||||
|
HUAWEI / XIAOMI / OPPO / VIVO / HONOR / FCM / APNS
|
||||||
|
|
||||||
|
## 与 IM 联动
|
||||||
|
|
||||||
|
IM 和推送服务均已开启时,IM 离线消息自动触发推送通知。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 需要各厂商 SDK 配置(App ID / App Secret)
|
||||||
|
- Firebase Messaging 需提供 `google-services.json`
|
||||||
|
- FCM token 通过 `FirebaseMessagingService.onNewToken()` 自动接收
|
||||||
49
sdk-update/README.md
普通文件
49
sdk-update/README.md
普通文件
@ -0,0 +1,49 @@
|
|||||||
|
# sdk-update
|
||||||
|
|
||||||
|
XuqmGroup Android SDK 更新模块。提供 App 版本检查和 APK 下载安装能力。
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
implementation("com.xuqm:sdk-update:VERSION")
|
||||||
|
implementation("com.xuqm:sdk-core:VERSION") // 必须
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val result = UpdateSDK.checkUpdate(
|
||||||
|
appKey = XuqmSDK.appKey,
|
||||||
|
platform = Platform.ANDROID,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result.needsUpdate) {
|
||||||
|
UpdateSDK.downloadAndInstall(context, result.downloadUrl)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
| API | 说明 |
|
||||||
|
|-----|------|
|
||||||
|
| `UpdateSDK.checkUpdate(appKey, platform)` | 检查 App 更新 |
|
||||||
|
| `UpdateSDK.downloadAndInstall(context, url)` | 下载 APK 并调起系统安装器 |
|
||||||
|
|
||||||
|
### UpdateResult
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
data class UpdateResult(
|
||||||
|
val needsUpdate: Boolean,
|
||||||
|
val versionName: String?,
|
||||||
|
val versionCode: Int?,
|
||||||
|
val downloadUrl: String?,
|
||||||
|
val changeLog: String?,
|
||||||
|
val forceUpdate: Boolean?,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 工作原理
|
||||||
|
|
||||||
|
- `downloadAndInstall` 将 APK 下载到 `getExternalFilesDir(null)`,通过 `FileProvider` 触发系统安装
|
||||||
|
- AndroidManifest 中已配置 `@xml/file_paths`(`external-files-path`)
|
||||||
|
- 支持 WebSocket 实时推送更新通知
|
||||||
69
sdk-webview/README.md
普通文件
69
sdk-webview/README.md
普通文件
@ -0,0 +1,69 @@
|
|||||||
|
# sdk-webview
|
||||||
|
|
||||||
|
XuqmGroup Android SDK WebView 模块。提供嵌入式 WebView 组件和独立页面,内置 JSBridge、文件选择、下载拦截。
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
implementation("com.xuqm:sdk-webview:VERSION")
|
||||||
|
implementation("com.xuqm:sdk-core:VERSION") // 必须
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
### 嵌入式组件
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
import com.xuqm.sdk.webview.XWebViewView
|
||||||
|
import com.xuqm.sdk.webview.XWebViewConfig
|
||||||
|
|
||||||
|
XWebViewView(
|
||||||
|
config = XWebViewConfig(
|
||||||
|
url = "https://example.com",
|
||||||
|
title = "嵌入式网页",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 独立页面
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
import com.xuqm.sdk.webview.openXWebView
|
||||||
|
import com.xuqm.sdk.webview.XWebViewConfig
|
||||||
|
|
||||||
|
openXWebView(XWebViewConfig(url = "https://example.com", title = "独立页面"))
|
||||||
|
// navigate("xwebview") 后使用 XWebViewScreen()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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` | 开启远程调试 |
|
||||||
|
| `downloadDestination` | FileDownloadDestination | `Sandbox` | 下载存储目标 |
|
||||||
|
| `downloadNotificationTitle` | String? | `null` | 通知栏下载进度 |
|
||||||
|
| `onMessage` | (String) -> Unit? | `null` | H5 消息回调 |
|
||||||
|
|
||||||
|
## JSBridge 通信
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// H5 → Native
|
||||||
|
window.XWebViewBridge.postMessage(JSON.stringify({ type: 'login', token: '...' }))
|
||||||
|
|
||||||
|
// 监听下载进度
|
||||||
|
window.addEventListener('__xwvDownloadProgress', (e) => { ... })
|
||||||
|
window.addEventListener('__xwvDownloadDone', (e) => { ... })
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件选择与拍照
|
||||||
|
|
||||||
|
- `<input type="file">` + `accept="image/*"` + `capture` → 系统相机
|
||||||
|
- `<input type="file">` + `.docx/.xlsx` → 文件选择器
|
||||||
|
- `getUserMedia()` WebRTC → 自动请求 CAMERA 权限
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
package com.xuqm.sdk.webview
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.xuqm.sdk.XuqmSDK
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard JSBridge handlers for xuqm.* messages.
|
||||||
|
* These provide default implementations for hosts that don't use a custom protocol.
|
||||||
|
*
|
||||||
|
* Usage: register in XWebViewView's xwvMessageHandler or onMessage callback.
|
||||||
|
*/
|
||||||
|
object XWebViewStandardHandlers {
|
||||||
|
|
||||||
|
fun handle(message: String, webView: XWebViewController?): String {
|
||||||
|
val json = runCatching { JSONObject(message) }.getOrNull() ?: return errorResponse("invalid JSON")
|
||||||
|
val name = json.optString("name", "")
|
||||||
|
return when (name) {
|
||||||
|
"xuqm.getDeviceInfo" -> handleGetDeviceInfo()
|
||||||
|
"xuqm.getToken" -> handleGetToken()
|
||||||
|
"xuqm.getUserInfo" -> handleGetUserInfo()
|
||||||
|
"xuqm.closeWebView" -> handleCloseWebView(webView)
|
||||||
|
"xuqm.showToast" -> handleShowToast(json)
|
||||||
|
else -> errorResponse("unknown handler: $name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleGetDeviceInfo(): String {
|
||||||
|
val data = JSONObject().apply {
|
||||||
|
put("platform", "android")
|
||||||
|
put("osVersion", Build.VERSION.RELEASE)
|
||||||
|
put("sdkVersion", Build.VERSION.SDK_INT)
|
||||||
|
put("manufacturer", Build.MANUFACTURER)
|
||||||
|
put("model", Build.MODEL)
|
||||||
|
put("brand", Build.BRAND)
|
||||||
|
}
|
||||||
|
return successResponse(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleGetToken(): String {
|
||||||
|
val token = XuqmSDK.currentLoginSession?.userSig ?: ""
|
||||||
|
val data = JSONObject().apply { put("token", token) }
|
||||||
|
return successResponse(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleGetUserInfo(): String {
|
||||||
|
val userInfo = XuqmSDK.getUserInfo()
|
||||||
|
if (userInfo == null) return errorResponse("not logged in")
|
||||||
|
val data = JSONObject().apply {
|
||||||
|
put("userId", userInfo.userId ?: "")
|
||||||
|
put("nickname", userInfo.nickname ?: "")
|
||||||
|
put("avatar", userInfo.avatar ?: "")
|
||||||
|
put("phone", userInfo.phone ?: "")
|
||||||
|
}
|
||||||
|
return successResponse(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCloseWebView(webView: XWebViewController?): String {
|
||||||
|
webView?.let { wv ->
|
||||||
|
if (wv.canGoBack()) wv.goBack() else wv.loadUrl("about:blank")
|
||||||
|
}
|
||||||
|
return successResponse(JSONObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleShowToast(json: JSONObject): String {
|
||||||
|
val message = json.optString("message", "")
|
||||||
|
if (message.isNotBlank()) {
|
||||||
|
val ctx = XuqmSDK.appContext
|
||||||
|
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
||||||
|
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return successResponse(JSONObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun successResponse(data: JSONObject): String =
|
||||||
|
JSONObject().apply { put("code", 0); put("data", data) }.toString()
|
||||||
|
|
||||||
|
private fun errorResponse(message: String): String =
|
||||||
|
JSONObject().apply { put("code", -1); put("message", message) }.toString()
|
||||||
|
}
|
||||||
正在加载...
在新工单中引用
屏蔽一个用户