XuqmGroup-AndroidSDK/docs/IMPLEMENTATION_HANDOFF.md

16 KiB

Android SDK 重构实施接管文档

更新时间2026-07-28 分支:main
发布约束Maven 制品只允许通过 https://jenkins.xuqinmin.com/ 发布。

2026-07-28 Update 原生权威契约与客户端秘密清理

  • 新签发配置不再包含 signingKey/appSecret;Android 客户端删除可提取长期秘密及 HMAC 请求头生成。配置真实性仍由 Ed25519 签名保证,登录请求只使用宿主同步的短期令牌。
  • NetworkMonitor 成为唯一网络状态来源,可独立于 SDK 初始化使用,公开 NONE/WIFI/CELLULAR/CELLULAR_4G/CELLULAR_5G/ETHERNET/OTHER 与是否计费网络。 宿主自行决定是否允许蜂窝网络下载,租户平台不保存网络建议。
  • checkAppUpdate 返回 UpdateCheckResult,明确区分 UPDATE_AVAILABLE/NO_UPDATE/LOGIN_REQUIRED/SERVICE_DISABLED/FAILED,并携带网络快照; 更新服务失败、未登录或未开通不再用 null 混淆,也不阻断宿主业务。
  • 原生 Update 同时提供检查、只下载、只安装、下载并安装和应用商店跳转。下载任务不跨 进程恢复;取消和失败清理 .part。安装前校验 SHA-256、包名与签名证书。
  • 已校验 APK 可按宿主选择额外导出到系统 Downloads。Android 10+ 使用 MediaStore; Android 9 及以下遵循系统运行时存储权限,不申请所有文件访问权限。
  • 删除未接入正式构建链且会读取明文发布参数、在本机直接发布的 sdk-update/scripts/xuqm_release.gradle.kts
  • sdk-webview 已删除所有文件访问权限门禁与设置页跳转,下载统一复用 core FileSDK。

本批已验证:

./gradlew :sdk-core:testDebugUnitTest \
  :sdk-update:testDebugUnitTest \
  :sdk-update:assembleRelease \
  :sdk-webview:testDebugUnitTest \
  :sdk-webview:lintRelease \
  verifySdkManifestOwnership

上述命令在增加包名/签名校验与 Java/RN 回调后最终复验通过233 个 Gradle task, 68 executed、165 up-to-date。 Sample 全量编译被真实配置门禁按预期拦截:仓库未包含租户平台签发的 sample-app/src/main/assets/config/config.xuqmconfig。未伪造配置,真机、Jenkins 和 Maven 发布尚未执行。

2026-07-28 BugCollect 摄取防雪崩

  • 原生上传器识别 HTTP 429 与 BUGCOLLECT_RATE_LIMITED,按标准 Retry-After 暂停上传,缺失或非法值默认 60 秒,最长一小时。
  • 冷却期间定时刷新直接跳过;fatal/error 加密队列和 Crash 文件保持不变,不向宿主 业务线程抛异常,也不把临时限流误判为服务永久关闭。
  • :sdk-bugcollect:testDebugUnitTest :sdk-bugcollect:lintRelease :sdk-bugcollect:assembleRelease 已通过。服务端默认每个 appKey 每分钟最多接收 6000 个事件,实际部署可通过环境变量调整。

2026-07-28 文件能力归并终态

  • 删除独立 sdk-file 模块、版本参数、Jenkins 发布选项和文档入口,不保留空目录或 兼容制品。
  • sdk-core 成为下载、摘要、MediaStore、平台上传、FileProvider 安全打开/分享的 唯一实现;本地能力和打开文件不要求初始化,只有平台上传等待统一初始化。
  • sdk-updatesdk-imsdk-webview 直接依赖 core。专用 XuqmFileProvider 由 core 唯一声明,仍与宿主自己的 FileProvider 隔离。

当前目标

  • sdk-core 是配置、会话、网络、文件、时间等公共能力的唯一实现。
  • sdk-update 只提供更新领域 API;宿主与 RN 桥接不复制下载、校验和安装代码。
  • APK 更新必须支持 SHA-256、原子临时文件、进度、取消、有限重试、FileProvider、未知来源安装权限及确定错误码。

2026-07-17 已完成

  • FileSDK 新增详细下载进度、SHA-256 与哈希匹配公共 API。
  • 下载改为 .part 临时文件完成后替换目标文件;HTTP 非 2xx 失败,协程取消会中断连接并清理临时文件。
  • 当时的 FileProvider 归属已被后续终态替代:当前由 core 唯一声明。
  • sdk-update 删除无版本 APK 兼容路径和弃用下载入口。
  • sdk-update 2.0.0-SNAPSHOT 强制要求合法 SHA-256,并提供下载重试、取消、缓存文件复验和确定错误码。
  • 增加 Java/RN 可直接使用的 startDownloadAndInstallUpdateInstallTask、进度/结果回调和安装权限设置 Intent。
  • sdk-coresdk-updatesample-app 编译通过。
  • 收敛 update/push/im/sample 的空 package/resource 目录;源码树禁止保留空目录或 .gitkeep 占位。
  • 清理后 :sdk-core:testDebugUnitTest :sdk-update:testDebugUnitTest 重新执行并通过。
  • sdk-core 1 个单元测试、sdk-update 2 个单元测试通过。
  • 修复通知权限检查、MediaStore API 等级和 java.time minSdk 24 反向移植,core/update Release Lint 均通过。

2026-07-18 RN 宿主网络兼容修复

  • App4 真机链路发现 sdk-core:1.1.6-SNAPSHOT 将 OkHttp 提升到 5.3.2, 与 React Native 0.86 的 okhttp-urlconnection:4.9.2 混装后会在首次请求时 因 okhttp3.internal.Util 缺失导致原生进程崩溃。
  • sdk-core 统一使用 OkHttp BOM 4.12.0;这是 RN 0.86 当前可用的最新 4.x 稳定兼容线,不允许下游单独提升某个 OkHttp 组件到 5.x。
  • check 增加 verifyNetworkDependencyAlignment,发布前强制确认所有 com.squareup.okhttp3 组件版本一致。
  • sdk-update 的 Gson 改为复用版本目录定义,删除模块内的重复版本事实来源。

后续外部验收

  1. 从租户平台下载与 Sample 包名匹配的真实 config.xuqmconfig,再执行 Sample Debug/Release 整包和仪器测试。
  2. 在目标宿主中验证下载取消、错误码、权限跳转和安装器唤起。
  3. 发布只允许通过 Jenkins 执行;当前开发机未发布任何 Maven 制品。

已验证命令

./gradlew :sdk-core:compileReleaseKotlin :sdk-update:compileReleaseKotlin :sample-app:compileDebugKotlin
./gradlew :sdk-core:testDebugUnitTest :sdk-update:testDebugUnitTest :sample-app:compileDebugKotlin
./gradlew :sdk-core:lintRelease :sdk-update:lintRelease :sdk-update:assembleRelease

不得在开发机执行 Maven 发布。

2026-07-26 初始化、Update 与 BugCollect 收敛

  • sdk-core 新增唯一初始化状态机:IDLE / INITIALIZING / READY / DEGRADED / FAILED, 对外失败使用 XuqmSdkException + XuqmErrorCode
  • 平台配置以 appKey + packageName + platformUrl 隔离,使用加密 LKG 保存七天; 远端配置最多执行三次指数退避重试,有有效 LKG 时降级运行,否则只停用扩展能力。
  • 初始化文件唯一位置为 assets/config/config.xuqmconfig。运行时只接受 XUQM-CONFIG-V2Ed25519 签名覆盖前五段 ASCII,先验签、后 AES 解密, canonical JSON、schema、UUID、revision 和 RFC3339 时间均强校验;V1 与未知 keyId 拒绝。
  • 已同时内置开发签发公钥 xuqm-config-dev-2026-07 与生产签发公钥 xuqm-config-prod-2026-07;私钥只保存在 Jenkins 凭据库和服务端运行环境, 不属于 SDK 仓库或发布制品。
  • BugCollect 必须同时满足平台启用、隐私已同意、上传地址有效。关闭时恢复宿主原 UncaughtExceptionHandler 并删除队列;服务端 BUGCOLLECT_DISABLED 会形成持久停用锁, 仅新的成功平台配置可解除。
  • error/fatal 加密持久化,最多 500 条且保留七天;普通事件仅内存。后台上传最多 重试三次并加入抖动,不向宿主线程抛错;统一脱敏入口在入队前清理敏感键、手机号、 身份证号和 Bearer Token。
  • Update 使用平台动态 updateRequiresLogin:未登录正常跳过;登录后同一会话自动补检 一次;退出或切换用户会取消旧任务并清除灰度检查缓存,回调统一切回主线程。
  • BugCollect Gradle 插件拒绝 V1,Release 读取同一 V2 文件;支持 -Pxuqm.bugcollect=disabled 应急构建,此时保留本地 mapping 产物但不访问上传服务。
  • Android R8 mapping.txt 通过 POST /bugcollect/v1/artifacts/uploadartifactType=R8_MAPPING 上传,并携带 appKey、platform、appVersion、buildId 与文件 SHA-256。它不是 RN Source Map,不改名为 .map,也不伪造 module/bundle 身份。 上传使用构建机 XUQM_API_TOKEN Bearer 认证;令牌不进入配置、任务输入快照或日志。 平台启用 BugCollect 但缺少令牌时 Release 明确失败;应急 disabled 构建不要求令牌。
  • 所有 Release无论是否开启混淆都会在打包前调用 POST /api/sdk/build/config/validate,提交原始签名配置和最终 applicationId。服务不可达、 配置撤销、过期或包名不匹配都会阻止打包;应急关闭 BugCollect 也不能绕过配置校验。
  • 删除不再参与 Manifest 的 BugCollectInitProvider,避免扩展模块平行初始化。
  • 删除公开手动初始化与旧 Provider 兼容类。sdk-core 单独集成不注册初始化 Provider 或通知权限;需要平台能力的扩展统一合并同一个 XuqmMergedProvider。文件 Provider 归唯一 core,通知权限归 sdk-push
  • Sample 删除硬编码 appKey,只接受被 Git 忽略的租户平台签发配置;缺少文件时 Debug/ Release 构建门禁给出明确错误。Release 签名只从用户级 Gradle 属性读取。
  • 敏感存储收敛为唯一 SecureStoreAndroid Keystore 不可导出 AES-256-GCM 密钥, namespace/key 作为 AAD;删除废弃 security-crypto 依赖。
  • sdk-webview/XWebViewView.kt 的既有未提交修改始终作为用户基线保留;本轮只在该基线 上将打开文件能力切换到唯一 FileSDK,未覆盖其它改动。

本轮最终已验证:

./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process \
  :sdk-core:testDebugUnitTest \
  :sdk-bugcollect:testDebugUnitTest \
  :sdk-update:testDebugUnitTest \
  :sdk-bugcollect-plugin:test \
  :sdk-core:lintRelease \
  :sdk-bugcollect:lintRelease \
  :sdk-update:lintRelease

./gradlew --no-daemon \
  :sdk-core:assembleRelease \
  :sdk-bugcollect:assembleRelease \
  :sdk-update:assembleRelease \
  :sdk-bugcollect-plugin:assemble \
  verifySdkManifestOwnership

./gradlew :sample-app:compileDebugKotlin \
  -x :sample-app:validateSampleSdkConfig

./gradlew :sample-app:compileDebugAndroidTestKotlin \
  -x :sample-app:validateSampleSdkConfig

Sample 源码编译时只为静态验证跳过真实签发文件的存在性门禁;正常执行 :sample-app:validateSampleSdkConfig 已确认会以明确错误阻止缺配置构建。合并 Manifest 静态结果确认多个扩展最终只有一个初始化 Provider。

尚未执行 Jenkins、Maven 发布、真机仪器测试或带真实租户配置的 Sample 整包。在线校验的不可达、HTTP 失败、 CONFIG_REVOKEDCONFIG_EXPIREDPACKAGE_MISMATCH 和成功响应已有单元测试覆盖; 服务端真实生成的 V2 固定向量已在 Android 端完成验签、解密和 canonical JSON 精确 比对。R8 multipart 上传字段、Bearer Header、SHA-256、.txt 文件名与令牌不进入请求 正文均有本地 HTTP 契约测试。

2026-07-26 审计后终态修复

  • 新增唯一 com.xuqm.common 构建插件:扫描宿主 src/**,所有变体只接受 src/main/assets/config/config.xuqmconfig;改名、嵌套或其它 sourceSet 中出现任何 .xuqmconfig 都会失败。Debug 执行本地 V2 验签,Release 额外在线校验。buildId 与运行时 BugCollect 构建门禁均归此插件;可选 BugCollect 插件只上传 R8 mapping。
  • 解密后的 serverUrl 是唯一且必填的平台地址;已删除旧业务默认地址和运行时回退, 配置缺失或地址为空时初始化明确失败。
  • 删除 configureServiceEndpointsuseExternalServiceEndpointsuseLocalServiceEndpoints 及 Sample 的环境设置页面、路由和持久化。服务端点仅可由 签发配置的 serverUrl 与该平台远程配置产生;远程字段缺失时只回退到同一 serverUrl,不会切换平台。
  • Release buildId 优先读取 Jenkins/CLI 的 XUQM_BUILD_ID;本地缺失时使用 UUID。 为防止 Gradle configuration cache 复用随机身份,启用配置缓存的 Release 强制要求显式 buildId。
  • -Pxuqm.bugcollect=disabled 是不可覆盖的构建禁用,同时关闭运行时采集/上传与 mapping 上传;mapping 仍保留为本地产物。Debug 只关闭自动采集,可调试宿主在构建未 禁用、隐私同意、平台启用时可通过隐藏开发入口显式发送一次测试事件。
  • common 插件按变体生成唯一 Manifest 标记、buildId 与 BugCollect 构建开关;扩展 AAR 的合并 Provider 未检测到该标记时只退出,不执行初始化,core-only 不触发初始化。
  • Update 先服从平台 features.update,相同用户重复同步不会再次补检;下载与安装授权 绑定当前 generation 和 userId,切换或登出会取消活动下载、清除授权并阻止旧灰度结果安装。
  • BugCollect 队列 flush 使用 Mutex 单飞,定时与满批触发不会重复上传同一批数据。
  • 内部 BugCollect 状态桥接改为私有反射 SPI,不进入 Java/Kotlin 公开 API。
  • sdk-core 统一承载本地文件、租户平台上传与 FileProvider 打开;IM、Update、WebView 直接复用 core,不存在第二个文件模块。
  • Uri、File、ByteArray 三个上传入口统一先等待平台初始化,再创建请求体 和读取服务端点;平台失败统一抛出保留 code/status/message 且不暴露响应体的 FileUploadExceptionopenFile 仍为不依赖初始化的纯本地能力。
  • 全仓日志、标准输出和异常文案不再输出 token、userId、groupId、消息目标、H5 payload、请求/响应正文、文件 Uri/绝对路径等实际值。根任务 verifyNoSensitiveLogging 在各模块 testDebugUnitTestlintReleasecheck 前扫描 Kotlin/Java/Gradle 源码,阻止凭据或用户标识重新进入输出边界。
  • 删除文档中不存在的 RetrofitFactory 示例;公开文档同步 common/file 的真实边界。

本批最终验证

  • core/file/bugcollect/update/im/push 与 BugCollect Gradle 插件单元测试通过; sdk-common-plugin 独立构建测试通过,包含严格 data.valid 防伪响应和单配置路径测试。
  • core/file/bugcollect/update/im/push/webview 的 lintReleaseassembleRelease 全部通过。
  • Sample 的 Debug Kotlin、AndroidTest 编译以及合并 Manifest 校验通过;因仓库不提交被忽略的 config.xuqmconfig,验证编译时仅显式跳过 xuqmValidateConfigDebug
  • 单独执行 xuqmValidateConfigDebug 已按预期失败,并明确提示唯一缺失路径 src/main/assets/config/config.xuqmconfig;未伪造配置、未绕过在线 Release 校验。
  • 普通 Release 生成结果为 buildEnabled=true/automaticEnabled=true;应急参数生成结果为 false/false;Debug 为 true/false,显式开发测试仍受隐私和平台开关约束。
  • Release 未提供 XUQM_BUILD_ID 且启用 configuration cache 时按预期失败;提供显式 buildId 后配置缓存可保存、复用,生成 Manifest 中的值保持不变。

2026-07-27 Jenkins Git 认证收口

  • sdk-android-publish 的 Jenkins SCM 已改为 Gitea SSH URL 并绑定 jenkins-ssh-key;正式版本回写 gradle.properties 时也通过 sshagent 使用同一 凭据,不依赖 checkout 临时环境或 URL 内嵌令牌。
  • Jenkins 作业配置二次读取确认无内嵌凭据。该 Jenkinsfile 修改仍需在下一次实际 发布中验证版本回写阶段。