# webview-container — Android Kotlin SDK Library

腾讯电子签 H5 容器 SDK Android Kotlin 实现的核心 Library 模块，独立产出 `.aar`，客户接入只需依赖此模块。

## 模块标识

| 项 | 值 |
|---|---|
| Gradle 路径 | `:webview-container` |
| Kotlin namespace | `com.tencent.qian.webview` |
| 产物类型 | Android Library（AAR） |
| 集成方式 | Gradle `implementation project(':webview-container')` 或拷贝模块 |

## 目录结构

```
webview-container/
├── src/
│   ├── main/
│   │   ├── kotlin/com/tencent/qian/webview/
│   │   │   ├── TencentQianWebView.kt              # 容器模式公共入口
│   │   │   ├── TencentQianWebViewConfigurator.kt  # 配置模式公共入口
│   │   │   ├── TencentQianWebViewActivity.kt      # 容器内置承载页
│   │   │   ├── TencentQianWebViewComponent.kt     # 组件模式（嵌入自定义布局）
│   │   │   ├── TencentQianWebViewController.kt    # 组件模式命令对象（canGoBack/goBack/handleBack/reload）
│   │   │   ├── TencentQianWebViewPrewarmer.kt     # 预热公共入口（configure/prewarm/releaseAll）
│   │   │   ├── chrome/                            # WebChromeClient（权限/弹窗/进度/文件）
│   │   │   ├── client/                            # WebViewClient（导航/错误/SSL/scheme）
│   │   │   ├── filechooser/                       # 文件选择器 + Media Capture
│   │   │   ├── internal/                          # WebSettings / 下载 / UA / 桥接
│   │   │   ├── model/                             # Config / SignResult / SignCallback / SdkEvent / Prewarm*
│   │   │   ├── permission/                        # 运行时权限封装
│   │   │   ├── prewarm/                           # 预热池 / manifest / 去重
│   │   │   └── scheme/                            # qianapp / 外部 scheme 拦截
│   │   ├── res/                                   # 顶栏图标、字符串、主题
│   │   └── AndroidManifest.xml                    # 权限 / queries 声明
│   └── test/                                      # 单元测试（JUnit 4 + Robolectric）
├── build.gradle.kts                               # 模块级 Kotlin DSL
├── consumer-rules.pro                             # 消费方 ProGuard 规则
└── proguard-rules.pro                             # 自身 ProGuard 规则
```

## 公共 API 速览

### 容器模式 `TencentQianWebView.open`

SDK 内置承载页（含顶栏、进度条、关闭确认、Edge-to-Edge 适配），一次调用即可拉起签署 H5：

```kotlin
TencentQianWebView.open(
    activity = this,
    url = signUrl,
    config = TencentQianWebViewConfig(topBarTitle = "电子签署"),
    callback = object : SignCallback {
        override fun onSignResult(result: SignResult) {
            // result.action / result.result / result.flowId / result.extras
        }
    }
)
```

### 组件模式 `TencentQianWebViewComponent`

将签署 WebView 嵌入宿主自定义布局（tab 页签、BottomSheet 等），顶栏 / 进度条 / 错误页由宿主自绘。SDK 托管 WebView 与全部能力，宿主掌控布局与 UI：

```kotlin
val controller = TencentQianWebViewController()

val component = TencentQianWebViewComponent.create(this) {
    url = signUrl
    config = TencentQianWebViewConfig(topBarTitle = "电子签署")
    onSignResult = { result -> /* 处理签署结果 */ }
    onTitleChanged = { title, canGoBack -> /* 更新自绘顶栏标题 */ }
    onProgressChanged = { progress, visible -> /* 显示/更新进度条 */ }
    onRenderUnrecoverable = { /* 显示错误页 + controller.reload() 重试 */ }
    controller = controller
    reusePrewarmPool = false     // 默认关闭
}

myLayout.addView(component, FrameLayout.LayoutParams(
    FrameLayout.LayoutParams.MATCH_PARENT,
    FrameLayout.LayoutParams.MATCH_PARENT
))

// 页面销毁时（Activity onDestroy / Fragment onDestroyView）：component.release()
```

> 宿主 Activity 必须是 `ComponentActivity`（`AppCompatActivity` / `FragmentActivity`）。组件自建自管 WebView，`onDetachedFromWindow` 仅暂停渲染，显式 `release()` 才销毁。
>
> Edge-to-Edge：宿主持有布局，SDK 不主动避让；`setContentView` 后调用 `applyEdgeToEdgeInsets(findViewById(R.id.xxx_root))` 一行接入（系统栏 / 挖孔 / 键盘，见 Demo `ComponentDemoActivity`）。

完整参数面、生命周期与关键约束详见 [../../docs/usage-guide.md §1.5](../../docs/usage-guide.md#15-组件模式嵌入自定义布局)。

### 配置模式 `TencentQianWebViewConfigurator.apply`

把 SDK 能力叠加到客户已有 `WebView` 上，保留客户自有 UI 与导航：

```kotlin
val handle = TencentQianWebViewConfigurator.apply(
    activity = this,
    webView = myExistingWebView,
    config = TencentQianWebViewConfig(),
    callback = signCallback,
    progressListener = { progress -> /* 同步到客户进度条 */ },
)
myExistingWebView.loadUrl(signUrl)

// 退出页面：handle.detachAndDestroy() 解绑并销毁 WebView
// 复用 WebView：handle.detach() 仅解绑 SDK 注入
```

> `apply` 必须在 `Activity.onCreate` 中调用，且 `activity` 必须是 `androidx.activity.ComponentActivity`（用于注册 `ActivityResultContracts.RequestMultiplePermissions`）。
>
> Edge-to-Edge：SDK 不干预宿主布局；`setContentView` 后调用 `applyEdgeToEdgeInsets(findViewById(R.id.xxx_root))` 一行接入（见 Demo `ConfiguratorDemoActivity`）。

### 预热（可选，提升首屏速度）

预热与容器/配置模式**正交**，不预热时行为与现状完全一致，预热是纯增量收益。`configure()` 可省（首次 `prewarm` 会以默认 `PROD` 懒初始化）；`MINIMAL` 仅 DNS、收益小，真正提速靠 `LIGHT` / `HEAVY`，按意图触发即可：

```kotlin
// 进入签署业务入口前（如列表页 / 详情页）
TencentQianWebViewPrewarmer.configure(PrewarmConfig(environment = PrewarmEnvironment.PROD))
// LIGHT / HEAVY 需传 context（建池 / 加载 warmup.html 依赖它；缺省静默退化为仅 DNS）
TencentQianWebViewPrewarmer.prewarm(PrewarmLevel.LIGHT, "single-sign", context)
TencentQianWebViewPrewarmer.prewarm(PrewarmLevel.HEAVY, "single-sign", context)

// 签署流程结束、不再需要时主动释放（回收预热态 WebView 占用的内存）
TencentQianWebViewPrewarmer.releaseAll()
```

完整档位语义、触发时机、池生命周期与端差异详见 [../../docs/usage-guide.md §3.5](../../docs/usage-guide.md#35-预热可选提升首屏速度)。

## 关键 Config 字段

| 字段 | 默认值 | 说明 |
|---|---|---|
| `topBarTitle` | `""` | 顶栏标题（容器模式生效） |
| `confirmBeforeClose` | `false` | 点关闭时是否弹「离开页面?」二次确认 |
| `confirmCloseTitle` | `""` | 二次确认弹窗标题，空走默认「离开页面?」 |
| `confirmCloseMessage` | `""` | 二次确认弹窗正文，空走默认 |
| `confirmLeaveText` | `""` | 二次确认「仍要离开」按钮文案，空走默认 |
| `confirmStayText` | `""` | 二次确认「留在本页」按钮文案，空走默认 |
| `injectUserAgent` | `true` | 是否注入 `qianwv/<version>` UA 标识 |
| `userAgentExtra` | `""` | 客户附加的 UA 段（拼接在 `qianwv/x.x.x` 之后） |
| `externalSchemes` | `["weixin", "alipays"]` | 允许跳出的外部 scheme 白名单 |
| `cameraCaptureBehavior` | `PREFER_FRONT` | `<input capture>` 拉相机时的镜头偏好 |
| `legacyFaceCaptureMimeTypes` | `[video/kyc, video/faceid]` | 老协议人脸核身兜底的 MIME 列表 |

完整字段见 `model/TencentQianWebViewConfig.kt` 的 KDoc。

## 单元测试

```bash
bash scripts/test-android-kotlin.sh
```

测试报告输出到 `webview-container/build/reports/tests/`。

## 集成与排错

- 接入指南：[../../docs/usage-guide.md](../../docs/usage-guide.md)
- 接入自检清单：[../../docs/integration-checklist.md](../../docs/integration-checklist.md)
- 排错决策树 + FAQ：[../../docs/troubleshooting.md](../../docs/troubleshooting.md)
- `qianapp://` 协议规范：[../../docs/qianapp-protocol.md](../../docs/qianapp-protocol.md)
