# 三端接入自检清单(Integration Checklist)

> **使用方法**:对照表头逐条勾选;SDK 与本清单字段保持双向同步,如有差异以代码为准并联系技术支持。

## 重要前置说明

- **自检页 URL**:`https://quick.beta.qian.tencent.cn/compatibilityTest`(SDK 默认指向 beta 环境,兼容性测试在 beta 验证即可,无需切换生产域名),
  客户可通过 `TencentQianWebViewConfig.compatibilityTestUrl` 覆盖。
- **`action` / `result` 未知值**:SDK 会**原样透传**到业务层 String,客户务必在 `when` / `switch`
  上加 `else` / `default` 分支兜底未来新增值。
- **不保证 X5 / TBS 兼容**:本 SDK 仅适配 Android 系统 WebView、iOS WKWebView 与 HarmonyOS ArkWeb。

---

## Android 接入

### A.1 工程要求

- [ ] AGP **8.7.3+**(对齐 Android Studio Panda / 2025.2)
- [ ] Gradle **8.10.2+**
- [ ] Kotlin **1.9.25+**
- [ ] JDK **17**
- [ ] `compileSdk = 35` / `targetSdk = 35`(满足 Google Play 2025-08-31 要求)
- [ ] `minSdk = 21`(Android 5.0)，Java 编译目标 1.8（源码不使用 JDK 9+ 语法或 API，兼容客户的 Java 8 编译目标，无需 desugaring）
  > **功能门槛说明**：`minSdk = 21` 仅保证 SDK 可安装运行 + `<input type="file" capture>` 系统相机录制/拍照上传路径可用。
  > **实时人脸采集（WebRTC / `getUserMedia`）仍需 Android 7.0+（API 24）**，由系统 WebView 内核决定，降 minSdk 不改变此门槛。
  > Android 5.0–6.x 设备的音视频采集请由 H5 侧特性检测后降级到 `<input capture>` 路径承接。

### A.1.1 Android 15 Edge-to-Edge 适配（targetSdk ≥ 35 必读）

`targetSdk = 35` 时系统**强制启用 edge-to-edge**，App 内容会绘制到状态栏和导航栏下方，
`window.statusBarColor` / `setDecorFitsSystemWindows(true)` 均已废弃且不生效。

- **容器模式**（`TencentQianWebView.open`）：SDK 已内部适配（全版本统一 E2E：系统栏 / 挖孔 / 键盘 inset 自动避让，
  并清零下发给 WebView 的系统栏 inset，H5 页面 `env(safe-area-inset-*)` 归零），无需额外操作。
- **配置 / 组件模式**（`TencentQianWebViewConfigurator.apply` / `TencentQianWebViewComponent`）：SDK **不干预**客户的布局，
  在 `setContentView` 后对根布局调用 `applyEdgeToEdgeInsets(root)`（Java：`EdgeToEdge.apply(root)`）一行接入同款避让；
  若宿主已有自己的 E2E / WindowInsets 处理则**不要**调用（避免叠加出双 padding）。
  浅色界面建议同时配置深色状态栏文字（`windowLightStatusBar`）。

参考 Demo `ConfiguratorDemoActivity` / `ComponentDemoActivity` 或 [troubleshooting.md Q12](./troubleshooting.md#q12-android-15-targetSdk-35-配置模式下页面内容顶到状态栏被导航栏遮挡) 的示例代码。

### A.2 Manifest 必备权限(逐条勾选)

```xml
<!-- 网络 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- 相机 + 麦克风(人脸核身) -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

<!-- 文件读取(API <= 32 兼容) -->
<uses-permission
    android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />

<!-- 相机硬件 feature 标可选,无相机机型也能装 -->
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.microphone" android:required="false" />
```

### A.3 Android 11+ `<queries>`(必备)

启动 weixin / alipays / 系统浏览器 PDF 时,Android 11+ 必须声明可见性:

```xml
<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="weixin" />
    </intent>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="alipays" />
    </intent>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="https" />
    </intent>
    <!--
      老录制模式人脸核身兜底:H5 触发 <input accept="video/kyc"|"video/faceid"> 时,
      SDK 用 ACTION_VIDEO_CAPTURE 拉系统相机;targetSdk 30+ 必须声明此 queries 才能 resolveActivity。
    -->
    <intent>
        <action android:name="android.media.action.VIDEO_CAPTURE" />
    </intent>
</queries>
```

> 不需要兜底老录制模式的客户(确认 H5 全部走 TRTC)可省略 `VIDEO_CAPTURE`,并在初始化时传
> `TencentQianWebViewConfig(legacyFaceCaptureMimeTypes = emptyList())` 关闭 SDK 侧检测。

> **HTML Media Capture 联动**:本 `<intent android:name="android.media.action.VIDEO_CAPTURE"/>` 同时也是
> `<input capture>` 处理(`config.cameraCaptureBehavior`,默认 `PREFER_FRONT`)的必备前置 ——
> H5 写 `<input accept="video/*" capture="user">` 自拍录像时,SDK 同样走 `ACTION_VIDEO_CAPTURE`。
> 若 H5 还会出现 `<input accept="image/*" capture>` 拍照场景,需追加:
>
> ```xml
> <intent>
>     <action android:name="android.media.action.IMAGE_CAPTURE" />
> </intent>
> ```
>
> 客户不需要拍照拉相机(全场景文件上传)可显式关闭:`cameraCaptureBehavior = CameraCaptureBehavior.DISABLED`。

### A.4 `application` 配置

- [ ] `android:usesCleartextTraffic="true"`(生产可视情况关闭,但电子签自检页含 http 子资源)
- [ ] `android:hardwareAccelerated="true"`(默认即开,但务必不要被某些机型适配代码关闭)

### A.5 ProGuard

SDK 自带 `consumer-rules.pro`,客户 `minifyEnabled true` 时**不需要额外配置**。
如需校验,可参考(与 SDK `consumer-rules.pro` 一致):

```pro
# 公开 API 入口
-keep public class com.tencent.qian.webview.TencentQianWebView { *; }
-keep public class com.tencent.qian.webview.TencentQianWebViewActivity { *; }
-keep public class com.tencent.qian.webview.TencentQianWebViewComponent { *; }
-keep public class com.tencent.qian.webview.TencentQianWebViewController { *; }
-keep public class com.tencent.qian.webview.TencentQianWebViewConfigurator { *; }
-keep public class com.tencent.qian.webview.TencentQianWebViewConfigurator$Handle { *; }

# 数据模型(model.** 含全部嵌套类)
-keep public class com.tencent.qian.webview.model.** { *; }
```

> **必需性说明**(源码集成 include 本模块):
> - **必需**:`TencentQianWebViewActivity`——被 AndroidManifest 以类名字符串引用,
>   类名混淆会导致启动容器 `ActivityNotFoundException`。
> - **非必需**:其余 4 条。源码集成下 R8 全程序混淆会同步重命名 SDK 与客户代码的
>   静态引用,不 keep 也能运行;保留仅为线上 crash 堆栈可读 + 未来 AAR 分发准备。

### A.6 Demo 四按钮跑通

> android-kotlin 与 android-java 两个 Demo 均含以下四按钮，组件模式两端行为一致（Java 版公开 API 为 Builder 形态，参数面与 Kotlin DSL 一一同名）。

- [ ] 在 Demo `MainActivity` 输入框粘贴客户后端获取的合同签署链接
- [ ] 「① 容器模式打开签署页」→ 顶栏 + WebView 正常,签署/拒签后能拿到 `SignResult`
- [ ] 「② 组件模式嵌入自定义布局」→ 自绘顶栏标题随 H5 更新(`onTitleChanged`)、进度条浮在 Web 顶部且显隐不跳动(`onProgressChanged`,FrameLayout 叠加)、返回按钮先退 H5 历史再退出(`controller.handleBack()`)、渲染崩溃无法恢复时错误页动态出现且可重试(`onRenderUnrecoverable`),签署/拒签结果正常
- [ ] 「③ 配置模式改造既有 WebView」→ 客户布局 + Configurator,行为同上
- [ ] 「打开兼容性自检页(beta)」→ 自检页全绿,能识别 cookie / 第三方 cookie / mediaCapture / clipboard 等

### A.7 常见漏配

- ❌ 漏 `CAMERA` / `RECORD_AUDIO` → 人脸核身活体检测黑屏
- ❌ 漏 `<queries>` → Android 11+ 拉起微信/支付宝 `ActivityNotFoundException`
- ❌ 客户自行 `setWebViewClient` 后再 `Configurator.apply` → SDK 会替换客户的 client(本期不支持 chained delegate)
- ❌ 组件模式宿主不是 `ComponentActivity`(如纯 `Activity`) → 组件显示错误占位,不加载页面
- ❌ 组件模式忘记 `release()` → WebView 泄漏;应在 Activity `onDestroy` / Fragment `onDestroyView` 调用

---

## iOS 接入

### B.1 工程要求

- [ ] Xcode **15+**
- [ ] Swift **5.9+**（消费 Swift 版 SDK）或 Objective-C（消费 OC 版 SDK）
- [ ] iOS Deployment Target **11.0+**（WebRTC / getUserMedia 仍需 iOS 14.3+，低版本走 `<input capture>` 系统相机路径）
- [ ] 集成方式：SPM 本地包（推荐）> xcframework（备选）> 源文件直拖（兜底），XcodeGen 仅开发调试用

### B.2 Info.plist 必备 key(逐条勾选)

```xml
<!-- 权限说明文案,不写客户 App 装上就崩 -->
<key>NSCameraUsageDescription</key>
<string>用于身份核验、拍摄证件照片及签署过程中的拍照上传</string>
<key>NSMicrophoneUsageDescription</key>
<string>用于身份核验过程中的活体检测</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>用于选取并上传图片或证件照</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>用于保存合同文件或签署凭证</string>
```

### B.3 `LSApplicationQueriesSchemes`(必备,与 SDK `externalSchemes` 联动)

`UIApplication.canOpenURL` / `open` 在 iOS 9+ 必须先在 plist 声明 scheme,否则永远返回 false。

```xml
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>weixin</string>
    <string>weixinULAPI</string>
    <string>alipay</string>
    <string>alipays</string>
    <!-- 与 TencentQianWebViewConfig.externalSchemes 一致;新增需在两处同步 -->
</array>
```

> **联动规则**:`externalSchemes` 是 SDK 拦截白名单,plist `LSApplicationQueriesSchemes` 是系统调起白名单,**必须双向一致**。SDK 默认 `["weixin", "alipays"]`,plist 也得包含同名。

### B.4 ATS(App Transport Security)

Demo 为方便打开本地网页默认开 `NSAllowsArbitraryLoadsInWebContent=true`,生产环境电子签页面走 https 可收紧:

```xml
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoadsInWebContent</key>
    <true/>  <!-- 仅 WKWebView 内允许 http,App 主体网络仍走 https -->
</dict>
```

### B.5 集成方式

> 优先级：**SPM 本地包（推荐）** > xcframework（备选）> 源文件直拖（兜底）
>
> 两个版本可选：`ios-swift/`（Swift，模块名 `TencentQianWebViewSDK`）或 `ios-objective-c/`（纯 OC，模块名 `TencentQianWebView`，与 Swift 版功能 1:1 对齐）。
> `ios-swift/`（Swift）仅支持 Swift 消费；`ios-objective-c/`（OC）支持 OC + Swift 消费。

#### B.5.1 SPM 本地包（推荐）

将 `ios-swift/`（或 `ios-objective-c/`）目录作为本地 Swift Package 引入客户工程：

1. Xcode → `File → Add Packages → Add Local…` → 选择 `ios-swift/`（或 `ios-objective-c/`）目录
2. 勾选 `TencentQianWebViewSDK`（Swift 版）或 `TencentQianWebView`（OC 版）library → Add Package
3. 在 App target 的 `Frameworks, Libraries, and Embedded Content` 中确认已添加

- [ ] `import TencentQianWebViewSDK`（Swift 版）或 `import TencentQianWebView`（OC 版）编译通过（Swift 客户）
- [ ] `@import TencentQianWebView;` 编译通过（OC 版，OC 客户）
- [ ] Apple Silicon Mac 模拟器运行无 arm64 冲突
- [ ] 本地化文案正常显示（SPM 下 OC target 不含 `.strings`，SDK 自动 fallback 到内置中文默认值；如需多语言支持，将 `Resources/` 下的 `.strings` 拖入 App 的 Copy Bundle Resources）

> SPM 本地包由 Xcode 自动编译、自动处理架构、保留模块边界（`internal` 对客户不可见），零配置。

#### B.5.2 xcframework（备选）

适合不想/不能拿源码的客户。先构建 xcframework：

```bash
# Swift 版
bash scripts/build-ios-swift.sh xcframework
# 产物：ios-swift/build/TencentQianWebViewSDK.xcframework

# OC 版
bash scripts/build-ios-objectivec.sh xcframework
# 产物：ios-objective-c/build/TencentQianWebView.xcframework
```

集成步骤：
1. 将 `TencentQianWebViewSDK.xcframework`（Swift 版）或 `TencentQianWebView.xcframework`（OC 版）拖入 Xcode 工程的 `Frameworks, Libraries, and Embedded Content`
2. Embed 选项设为 `Embed & Sign`

- [ ] xcframework 含 `ios-arm64` + `ios-arm64_x86_64-simulator` 两个 slice
- [ ] device 构建选择 `ios-arm64` slice
- [ ] simulator 构建选择 `ios-arm64_x86_64-simulator` slice（Apple Silicon 无 arm64 冲突）
- [ ] `#import <TencentQianWebView/TencentQianWebView.h>` 编译通过（OC 版，OC 客户）

> xcframework 通过 variant 区分 device/simulator，解决传统 fat framework 在 Apple Silicon 上的 arm64 冲突。
> Swift 版 xcframework 含 swiftinterface（`BUILD_LIBRARY_FOR_DISTRIBUTION=YES`），支持跨 Swift 编译器版本分发。
> OC 客户使用 framework-style 导入 `#import <TencentQianWebView/TencentQianWebView.h>`（与 SPM 的 `@import` 不同）。

#### B.5.3 XcodeGen（开发调试）

从源码生成完整 Xcode 工程（含 Demo app），用于开发调试：

```bash
brew install xcodegen
cd ios-swift  # 或 ios-objective-c
xcodegen generate
open Demo.xcodeproj
```

- [ ] `Demo` scheme 能在模拟器跑起
- [ ] `TencentQianWebViewSDK`（Swift 版）或 `TencentQianWebView`（OC 版）Framework target 能 build 通过

#### B.5.4 源文件直拖（兜底）

将 `TencentQianWebView/Sources/` 下的源文件直接拖入客户 App target 的 Compile Sources。

- [ ] 编译通过
- ⚠️ **无模块边界**：`internal` 访问级别对客户代码可见，无 `import` 语句
- ⚠️ 资源文件（`.strings`）需手动加入 App target 的 Copy Bundle Resources

### B.6 Demo 四按钮跑通

> Swift 版（`ios-swift/`）跑 `Demo` scheme；OC 版（`ios-objective-c/`）跑 `Demo` scheme（OC 消费）或 `DemoSwift` scheme（Swift 消费 OC SDK）。三种集成方式（容器 / 组件 / 配置）+ 兼容性自检页。

- [ ] `DemoViewController` 输入框粘贴客户后端获取的合同签署链接
- [ ] 「① 容器模式打开签署页」→ `TencentQianWebView.open(...)` / `[TQWebView openFromPresenter:...]` 全屏 modal,正常签署/拒签后回调
- [ ] 「② 组件模式嵌入自定义布局」→ `TencentQianWebViewComponent` / `TQWebViewComponent` 嵌入自定义布局,签署/拒签结果正常
- [ ] 「③ 配置模式改造既有 WebView」→ `TencentQianWebViewConfigurator.apply(...)` / `[TQWebViewConfigurator applyToWebView:...]` 在客户 WKWebView 上注入
- [ ] 「打开兼容性自检页(beta)」→ 全绿(尤其 mediaCapture / cookie / 第三方 cookie)

### B.7 常见漏配

- ❌ 漏 `NSCameraUsageDescription` → iOS 11+ App 加载到含相机调用的 H5 直接崩
- ❌ 漏 `LSApplicationQueriesSchemes` → `UIApplication.open(weixin://)` 永远 false,事件流为 `schemeNotMatched`
- ❌ 客户自行 `webView.navigationDelegate = self` 后再 `Configurator.apply` → SDK 会替换 delegate
- ❌ SPM 集成 OC 版用 `#import <TencentQianWebView/...>` → 编译失败,应改用 `@import TencentQianWebView;`
- ❌ SPM 集成 OC 版本地化文案显示 key → C/OC target 不含 `.strings`,SDK 自动 fallback 内置中文；如需多语言将 `.strings` 拖入 App Copy Bundle Resources
- ⚠️ 组件 / 配置模式下指望 SDK 自动键盘避让 → SDK 不主动修改宿主布局，需一行调用 `[TQKeyboardAvoidance applyToWebView:inView:]`（Swift：`KeyboardAvoidance.apply(webView:in:)`）接入同款避让（容器模式已内建，见 [troubleshooting Q15](./troubleshooting.md#q15-h5-底部-fixed-弹框中的输入框被软键盘遮挡三端键盘避让机制)）

---

## HarmonyOS 接入

### C.1 工程要求

- [ ] DevEco Studio **5.0+**
- [ ] HarmonyOS SDK **API 12+**(HarmonyOS NEXT 5.0+)
- [ ] ArkTS(严格模式)
- [ ] ohpm(DevEco Studio 内置) / hvigor 6.x

### C.2 module.json5 必备权限(逐条勾选)

```json5
{
  "module": {
    "requestPermissions": [
      { "name": "ohos.permission.INTERNET" },
      { "name": "ohos.permission.GET_NETWORK_INFO" },
      {
        "name": "ohos.permission.CAMERA",
        "reason": "$string:camera_permission_reason",
        "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
      },
      {
        "name": "ohos.permission.MICROPHONE",
        "reason": "$string:microphone_permission_reason",
        "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
      }
    ]
  }
}
```

> **`reason` 必填**:鸿蒙对敏感权限要求声明 `reason` + `usedScene`,缺失会导致权限申请被系统拒绝。
> 客户需在 `resources/base/element/string.json` 中补 `camera_permission_reason` / `microphone_permission_reason` 文案。

### C.3 外部 scheme 可见性

鸿蒙没有 Android `<queries>` / iOS `LSApplicationQueriesSchemes` 的显式白名单声明,
SDK 通过 `TencentQianWebViewConfig.externalSchemes`(默认 `["weixin", "alipays"]`)拦截后调用
`context.startAbility({ action: 'ohos.want.action.viewData', uri })` 拉起。客户新增外部 scheme 只需改 config,
**无需改 module.json5**。

### C.4 Web 组件链式属性(配置模式 / 自行适配必查)

容器模式由 SDK 内部全包,客户无需关心。配置模式 / 自行适配需在 `Web({})` 组件上逐项绑定:

- [ ] `.javaScriptAccess(true)` / `.domStorageAccess(true)` / `.databaseAccess(true)`
- [ ] `.mediaPlayGestureAccess(false)`(视频流自动播放)
- [ ] `.fileAccess(false)` / `.multiWindowAccess(false)`(安全 + 防诡异跳出)
- [ ] `.textZoomRatio(100)` / `.zoomAccess(true)` / `.wideViewModeAccess(true)` / `.overviewModeAccess(true)`(视图适配)
- [ ] `.keyboardAvoidMode(WebKeyboardAvoidMode.RESIZE_CONTENT)`(API 12+,软键盘弹出收缩 Web 区域,H5 fixed 底栏跟随键盘上方;**ArkWeb 默认值即此模式**,核对未被覆盖为其他模式即可,建议显式绑定固化语义;容器 / 组件模式 SDK 已内建)
- [ ] `.onControllerAttached` 中注入 `qianwv/<version>` UA(详见 [usage-guide.md §3.3](./usage-guide.md#33-harmonyos-自行适配清单))

### C.5 集成方式

#### C.5.1 源码集成(推荐)

将 `tencentqianwebview` 模块以 `file` 依赖引入客户工程,SDK 源码与客户代码在同一编译单元内
一起编译、一起混淆,所有符号引用天然一致,**无需配置任何混淆规则**。

在客户工程(Entry 模块)的 `oh-package.json5` 中添加:

```json5
{
  "dependencies": {
    "tencentqianwebview": "file:../tencentqianwebview"
  }
}
```

> 路径 `file:../tencentqianwebview` 为相对路径,请按实际目录结构调整(将 `tencentqianwebview`
> 模块源码目录放在客户工程同级或指定位置)。

#### C.5.2 Demo 验证

直接用 DevEco Studio 打开 `harmonyos-arkts/` 目录,首次打开自动执行 `ohpm install`。
选 `entry` 模块,运行到 HarmonyOS NEXT 设备 / 模拟器。

### C.6 Demo 四按钮跑通

- [ ] 在 `Index` 页输入框粘贴客户后端获取的合同签署链接
- [ ] 「① 容器模式打开签署页」→ 全屏签署页 + WebView 正常,签署/拒签后回调 `SignResult`
- [ ] 「② 组件模式嵌入自定义布局」→ 嵌入自定义布局的 `TencentQianWebViewComponent`,行为同上
- [ ] 「③ 配置模式改造既有 WebView」→ 客户 Web 组件 + handlers 绑定,行为同上
- [ ] 「打开兼容性自检页(beta)」→ 自检页全绿,能识别 cookie / 第三方 cookie / mediaCapture / clipboard 等

### C.7 常见漏配

- ❌ 漏 `ohos.permission.CAMERA` / `MICROPHONE` 的 `reason` → 权限申请被系统拒绝,人脸核身黑屏
- ❌ 漏 `ohos.permission.GET_NETWORK_INFO` → SDK 注入的网络状态检测脚本失效
- ❌ 配置模式下未把 `handlers` 逐个绑定到 `Web` 组件回调 → SDK 能力未生效(配置模式是声明式,无法像 Android/iOS 那样运行时替换 delegate)
- ❌ 自行 `onLoadIntercept` 后再调 `Configurator.apply` → SDK 返回的 handlers 会被客户实现覆盖
- ❌ 配置模式把 `.keyboardAvoidMode` 覆盖为 `OFFSET` / `RESIZE_VISUAL` 等 → 键盘遮挡 H5 底部 fixed 弹框(默认 `RESIZE_CONTENT` 天然正确,容器 / 组件模式 SDK 已内建)

---

## 三端共同提示

- **协议未知值**:`action` / `result` 是 String 直透,客户务必加 `else` / `default` 分支
- **不依赖网络鉴权**:SDK 不做 H5 域名校验、不做 referer 注入,所有鉴权由 H5 + 后端完成
- **DOM Storage / Cookie / 第三方 Cookie 默认强制开启**,不开放关闭参数,详见
  [docs/troubleshooting.md](./troubleshooting.md) FAQ
