diff --git a/README.md b/README.md index 57f3709..feb70cc 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,86 @@ # web_android_shell -Android 平板专用 H5 壳项目。 +Android 平板专用 H5 壳应用 — Monorepo 多品牌架构。 + +## 项目结构 + +``` +web_android_shell/ +├── apps/ # 品牌应用(每个品牌一个 Flutter App) +│ └── quanxue/ # 全学通 +├── packages/ +│ ├── web_shell_core/ # 核心库(WebView 引擎 + Bridge + 服务) +│ └── web_android_shell/ # 旧版入口(已迁移至 apps/quanxue) +├── flavors/ # 品牌配置 YAML +│ └── quanxue.yaml +├── tool/ +│ ├── generate_app.dart # 一键生成新品牌应用 +│ └── flutter_run_fresh.ps1 # Windows 调试脚本(自动杀旧进程) +└── doc/ # 项目文档 +``` + +## 快速开始 + +### 1. 运行已有品牌 + +```bash +cd apps/quanxue +flutter run +``` + +### 2. 生成新品牌 + +```bash +# 1) 在 flavors/ 下创建品牌配置 +cp flavors/quanxue.yaml flavors/新品牌.yaml +# 2) 修改配置中的 app_name, application_id, app_key, theme +# 3) 运行生成脚本 +dart run tool/generate_app.dart 新品牌 +``` + +生成脚本会自动完成: +- 创建 Flutter 应用 → `apps/新品牌/` +- 添加 `web_shell_core` 依赖 +- 覆写 `MainActivity` 继承 `CoreShellActivity` +- 生成品牌入口 `main.dart` +- 配置 `flutter_launcher_icons` + +### 3. 品牌配置格式 + +```yaml +app_name: "全学通" # 应用名 +application_id: "com.wanmake.quanxue" # 包名 +app_key: "quanxue_prod" # 业务标识 +theme: + accent_color: "0xFF3ED37B" # 主题色 + bg_color: "0xFFFFFFFF" # 背景色 + text_color: "0xFF1F2937" # 主文字色 + muted_text_color: "0xFF6B7280" # 次要文字色 +``` ## 调试说明 -这个项目在部分设备上,如果直接用 `Ctrl+C` 结束 `flutter run`,设备里的上一次 App 进程可能还留在后台,下一次运行时会影响内嵌 WebView 启动。 +部分教育平板设备使用 `Ctrl+C` 结束 `flutter run` 后,旧进程可能留在后台影响 WebView 启动。 -更稳的做法: - -- 调试结束时优先在 `flutter run` 里按 `q` -- 或者使用项目自带脚本,先自动杀掉旧进程再启动 +**推荐做法:** +- 调试结束时在 `flutter run` 控制台按 `q` 退出 +- 或使用调试脚本自动杀旧进程再启动: ```powershell -.\tool\flutter_run_fresh.ps1 +.\tool\flutter_run_fresh.ps1 # 自动选设备 +.\tool\flutter_run_fresh.ps1 -d F136A # 指定设备 ``` -如果要指定设备,也可以继续透传给 `flutter run`: +## 技术栈 -```powershell -.\tool\flutter_run_fresh.ps1 -d F136A -``` +| 组件 | 技术 | +|---|---| +| 框架 | Flutter 3.x (Dart 3.11+) | +| WebView | `webview_flutter` + `webview_flutter_android` | +| 宿主能力 | `image_picker` · `file_picker` · `permission_handler` · `url_launcher` | +| 原生层 | Kotlin Plugin + Java `CoreShellActivity` | +| 代码规范 | `very_good_analysis` | -脚本会自动: +## 平台约束 -- 读取 `android/local.properties` 中的 `sdk.dir` -- 调用 `adb shell am force-stop com.yuanxuan.webshell.web_android_shell` -- 然后执行 `flutter run` +**仅支持 Android 平板。** iOS / Web / Desktop 平台已移除。 diff --git a/apps/quanxue/README.md b/apps/quanxue/README.md index 213693b..6b7e444 100644 --- a/apps/quanxue/README.md +++ b/apps/quanxue/README.md @@ -1,17 +1,35 @@ -# quanxue +# 全学通 (quanxue) -A new Flutter project. +「全学通」品牌的 Android 平板 H5 壳应用。 -## Getting Started +## 概述 -This project is a starting point for a Flutter application. +本应用是 `web_shell_core` 核心库的品牌实例。`main.dart` 仅 16 行代码,只负责传入品牌配置,所有业务逻辑由核心库提供。 -A few resources to get you started if this is your first Flutter project: +## 运行 -- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter) -- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources) +```bash +flutter run +``` -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +## 配置 + +| 配置项 | 值 | +|---|---| +| 应用名 | 全学通 | +| 包名 | `com.wanmake.quanxue` | +| 业务标识 | `quanxue_prod` | +| 主题色 | `#3ED37B` | + +品牌配置源文件:[`flavors/quanxue.yaml`](../../flavors/quanxue.yaml) + +## 构建 + +```bash +flutter build apk --release +``` + +## 依赖 + +- `web_shell_core`(本地 path 引用) +- `cupertino_icons`(兼容历史代码) diff --git a/apps/quanxue/pubspec.yaml b/apps/quanxue/pubspec.yaml index 72d8264..7fd8e90 100644 --- a/apps/quanxue/pubspec.yaml +++ b/apps/quanxue/pubspec.yaml @@ -1,5 +1,5 @@ name: quanxue -description: "A new Flutter project." +description: "全学通 — Android 平板 H5 壳应用。" # 阻止误发布到 pub.dev。 publish_to: 'none' diff --git a/doc/architecture.md b/doc/architecture.md new file mode 100644 index 0000000..9f93302 --- /dev/null +++ b/doc/architecture.md @@ -0,0 +1,93 @@ +# 架构设计 + +## 整体架构 + +``` +┌──────────────────────────────────────────────────────┐ +│ apps/quanxue apps/品牌B apps/品牌C │ 品牌应用层 +│ (16 行 main.dart) │ 只传 ShellEnvironment +├──────────────────────────────────────────────────────┤ +│ web_shell_core │ 核心库 +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ config │ │ engine │ │ bridge │ │ +│ │ 环境配置 │ │ 兼容引擎 │ │ JS 桥接 │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ services │ │ ui │ │ testing │ │ +│ │ 宿主服务 │ │ 壳层界面 │ │ 测试钩子 │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +├──────────────────────────────────────────────────────┤ +│ CoreShellActivity (Java) │ 原生层 +│ 进程隔离 · WebView 信息查询 · 深度重置 │ +└──────────────────────────────────────────────────────┘ +``` + +## 核心流程 + +### 1. 启动流程 + +``` +main() → runShellApp(env) + → WidgetsFlutterBinding.ensureInitialized() + → 设置屏幕方向(竖屏锁定) + → 进入沉浸式模式 + → runApp(ShellApp) + → Android? → WebShellPage(WebView 容器) + → 其他? → UnsupportedPlatformPage(兜底页) +``` + +### 2. WebView 启动与恢复 + +``` +WebShellPage.initState() + → 查询 Android WebView 信息(SDK / 包名 / 版本号) + → 生成兼容性策略(renderModes / useWideViewPort / aggressiveRecovery) + → 创建 WebView(默认 texture 模式) + → 首帧就绪后加载初始 URL + → 启动看门狗计时器 + → 超时? → 切换渲染模式(hybrid)→ 深度清理 → 自动重试 + → 再超时? → 展示错误页 + 兼容性提示 +``` + +### 3. JS Bridge 协议 + +``` +H5 页面 Flutter 壳 + │ │ + │ AppShellChannel. │ + │ postMessage(JSON) │ + │ ──────────────────────→ │ 解析 action + payload + │ │ 执行对应 handler + │ window. │ + │ __appShellReceiveResponse│ + │ ←────────────────────── │ 返回 { requestId, success, data/error } +``` + +**支持的 Action:** + +| Action | 说明 | 返回 | +|---|---|---| +| `pickImage` | 从图库选图(支持多选) | `[{name, uri, mimeType, size, dataUrl}]` | +| `captureImage` | 相机拍照 | `{name, uri, mimeType, size, dataUrl}` | +| `pickFile` | 文件选择 | `[{name, uri, mimeType, size, dataUrl}]` | +| `openExternal` | 打开外部应用 | `boolean` | +| `requestPermissions` | 请求系统权限 | `{type: statusName}` | +| `reloadPage` | 重新加载当前页面 | `true` | +| `goBack` | 返回上一页 | `boolean` | +| `closeApp` | 关闭应用 | 无(直接退出) | + +## 兼容性策略 + +| 条件 | 渲染模式 | 恢复策略 | +|---|---|---| +| SDK ≥ 29 + WebView ≥ 113 | texture 优先 | 标准恢复 | +| SDK ≤ 28 或 WebView < 113 | hybrid 优先 | 激进恢复(2 次重试) | +| F136A 设备 | hybrid 优先 | 激进恢复 + 建议更新 WebView | + +## 新增品牌 + +1. 创建 `flavors/品牌名.yaml` +2. 运行 `dart run tool/generate_app.dart 品牌名` +3. 脚本自动生成完整的 Flutter App 在 `apps/品牌名/` +4. 修改图标后运行 `flutter pub run flutter_launcher_icons` +5. 构建 APK:`flutter build apk --release` diff --git a/doc/plan.md b/doc/plan.md deleted file mode 100644 index d16bdd1..0000000 --- a/doc/plan.md +++ /dev/null @@ -1,125 +0,0 @@ -# Role & Context - -你是一个精通 Flutter 混合架构(Hybrid App)与移动端 APM(应用性能监控)的资深架构师。 - -当前任务:为一款基于“单工程多风味(Flavors)”架构的教育平板应用(包含“劝学”和“点智学”两个 Flavor)生成核心 MVP 代码框架与监控埋点方案。 - -底层环境约定:兼容 Android 14(量产基线)并向下兼容处理 Android 15 的 WindowInsets(Edge-to-Edge)特性。 - - - -# Product Architecture (Flutter 壳与 H5 的边界界定) - -本 MVP 采用“瘦壳重网页”模式: - -1. **Flutter 壳职责:** 仅负责生命周期管理、沉浸式/Kiosk 模式控制、设备硬件能力桥接(相机/录音/持久化存储)、全局骨架屏(Loading)、以及高可用 WebView 容器的维护。 - -2. **H5 职责:** 承载所有教学业务逻辑(题库、视频播放、个人中心)。 - -3. **通信机制:** 统一通过注入的 `JSBridge` 进行双向通信。 - - - -# MVP Core Modules (核心功能模块需求) - - - -## 1. 动态环境与配置引擎 (Config Engine) - -- **需求:** 读取 `--dart-define=APP_FLAVOR`。 - -- **功能:** 根据 Flavor 动态下发不同的 `baseUrl`(H5 首页地址)、主题色、以及持久化缓存策略。 - - - -## 2. 高可用 WebView 容器 (Core Web Container) - -- **核心依赖:** 使用 `webview_flutter` 或 `flutter_inappwebview`。 - -- **Android 15 兼容性(关键):** WebView Widget 必须包裹在自定义的 `SafeArea` 或响应 `WindowInsets` 的布局中,将顶部的 `statusBarHeight` 和底部的 `navigationBarHeight` 动态获取,并作为 URL 参数或注入的 JS 变量传给 H5,供前端写 CSS 避让区。 - -- **Cookie 策略:** 必须显式开启 `Third-Party Cookies` 和 `DOM Storage`,确保跨域 SSO 单点登录不掉线。 - - - -## 3. 标准化 JSBridge 协议层 - -要求实现一个健壮的 JSBridge 类,至少包含以下基础方法,供 H5 调用: - -- `getDeviceInfo()`: 返回系统版本、电量、当前网络状态(WiFi/4G)。 - -- `setImmersiveMode(boolean)`: 控制隐藏/显示系统状态栏和底部导航栏。 - -- `openCamera(config)`: 唤起原生相机并返回 Base64 或文件路径。 - -- `reportEvent(eventName, params)`: H5 将核心业务埋点转发给 Flutter 壳进行统一上报。 - - - -## 4. Kiosk Mode (专注模式/设备管控) - -- 预留 `MethodChannel` 接口,命名为 `DeviceControlChannel`。 - -- 包含方法 `enableKioskMode()` 和 `disableKioskMode()`(具体 Android 原生端 `startLockTask` 逻辑暂用 TODO 占位,需定义好 Dart 侧的调用规范)。 - - - ---- - - - -# APM & Observability Metrics (应用性能与稳定性指标体系) - - - -请在代码中创建一个 `AppMonitor` 单例类,负责拦截并上报以下三大类核心指标(目前先在控制台打印日志,预留后续接入 Firebase / Sentry / 自研后端的接口): - - - -## 1. 容器加载性能指标 (Performance) - -- `Shell_Launch_Time`: Flutter 引擎初始化到原生首屏(Splash 结束)的耗时。 - -- `WebView_Init_Time`: 从触发加载到 WebView 实例创建完成的耗时。 - -- `H5_TTFB (Time to First Byte)`: Web 页面发出请求到收到第一个字节的耗时(需注入 JS 获取)。 - -- `H5_FCP (First Contentful Paint)`: 白屏时间(从 `onPageStarted` 到 `onPageFinished`,结合 JS 注入获取真实渲染时间)。 - - - -## 2. 稳定性指标 (Stability) - -- `WebView_Crash_Rate`: 捕获 `onWebResourceError`,特别是 `OOM`(内存溢出)导致的白屏终止。 - -- `JSBridge_Fail_Count`: 统计 H5 调用原生能力失败的次数与错误码(如权限被拒、参数格式错误)。 - -- `Http_Error_Rate`: 拦截并统计 WebView 内发生的 404/500 等静态资源或接口请求错误。 - - - -## 3. 业务与设备指标 (Business & Device) - -- `Session_Duration`: 从应用 `resumed` 到 `paused` 的有效停留时间。 - -- `Network_Switch_Count`: 监听网络状态,记录用户在弱网/断网环境下的掉线频次。 - - - ---- - - - -# Execution Steps for Code X (执行步骤) - - - -请一步步为我生成上述方案的代码骨架: - -1. **Step 1:** 生成目录结构建议(基于 feature 驱动或分层架构)。 - -2. **Step 2:** 生成 `Config Engine` 与 `AppMonitor` 的单例类代码,实现核心指标的日志打印结构。 - -3. **Step 3:** 编写核心的 `HybridWebView` Widget,包含 Insets 处理、Cookie 策略配置和首屏加载时间计算。 - -4. **Step 4:** 编写 `JSBridge` 核心处理类,实现拦截 H5 消息并路由到对应原生方法的逻辑。 \ No newline at end of file diff --git a/packages/web_android_shell/README.md b/packages/web_android_shell/README.md index 191f589..f5015e2 100644 --- a/packages/web_android_shell/README.md +++ b/packages/web_android_shell/README.md @@ -1,28 +1,7 @@ -# web_android_shell +# web_android_shell(已迁移) -H5 壳子项目。 +> ⚠️ 本包为旧版入口,已迁移至 Monorepo 架构。 -## 调试说明 +当前用途:为旧版 `MainActivity` 提供壳层入口,调用 `web_shell_core` 启动应用。 -这个项目在部分设备上,如果直接用 `Ctrl+C` 结束 `flutter run`,设备里的上一次 App 进程可能还留在后台,下一次运行时会影响内嵌 WebView 启动。 - -更稳的做法: - -- 调试结束时优先在 `flutter run` 里按 `q` -- 或者使用项目自带脚本,先自动杀掉旧进程再启动 - -```powershell -.\tool\flutter_run_fresh.ps1 -``` - -如果要指定设备,也可以继续透传给 `flutter run`: - -```powershell -.\tool\flutter_run_fresh.ps1 -d F136A -``` - -脚本会自动: - -- 读取 `android/local.properties` 中的 `sdk.dir` -- 调用 `adb shell am force-stop com.yuanxuan.webshell.web_android_shell` -- 然后执行 `flutter run` +新品牌应用请使用 `apps/` 目录下的独立应用,参考 [`apps/quanxue/`](../../apps/quanxue/)。 diff --git a/packages/web_shell_core/CHANGELOG.md b/packages/web_shell_core/CHANGELOG.md index 41cc7d8..93b39fc 100644 --- a/packages/web_shell_core/CHANGELOG.md +++ b/packages/web_shell_core/CHANGELOG.md @@ -1,3 +1,16 @@ +# Changelog + ## 0.0.1 -* TODO: Describe initial release. +### 新增 +- 从 `web_android_shell` 提取核心库,支持多品牌白标架构 +- `runShellApp()` 唯一入口 + `ShellEnvironment` 品牌配置 +- 15 个模块文件拆分(config / engine / bridge / services / ui) +- Android WebView 兼容性自动检测(`AndroidWebViewInfo` + `AndroidCompatibilityPlan`) +- 双渲染模式(texture / hybrid)自动切换 + 启动看门狗恢复链 +- `window.AppShell` JS Bridge 协议(8 种 Action) +- 旧相机 JS 兼容层(`openCamera` / `captureImage` monkey-patch) +- 媒体序列化支持 `base64` / `dataUrl` / `uri` 三种格式 +- 54 个单元测试 + Widget 测试 +- `CoreShellActivity` 原生层(进程隔离 + WebView 信息查询 + 深度重置) +- 全中文 `debugPrint` 日志 diff --git a/packages/web_shell_core/README.md b/packages/web_shell_core/README.md index c082dad..b354953 100644 --- a/packages/web_shell_core/README.md +++ b/packages/web_shell_core/README.md @@ -1,12 +1,92 @@ # web_shell_core -Android 平板专用的 H5 壳核心库,负责: +Android 平板专用的 H5 壳核心库。所有品牌应用共享此库,只需传入 `ShellEnvironment` 即可启动。 -- WebView 启动与恢复 -- H5 / Native JS Bridge -- 文件、相机、权限等宿主能力 -- 品牌化壳层 UI +## 功能 + +| 模块 | 说明 | +|---|---| +| **WebView 引擎** | 自动兼容低版本 Android WebView,支持 texture / hybrid 双渲染模式自动切换 | +| **启动恢复** | 看门狗超时检测 → 渲染模式降级 → 深度清理 → 自动重试 | +| **JS Bridge** | `window.AppShell` 协议,支持 8 种 Action(pickImage · captureImage · pickFile · openExternal · requestPermissions · reloadPage · goBack · closeApp) | +| **旧相机兼容** | Monkey-patch `openCamera` / `captureImage` 兼容老 H5 页面 | +| **媒体服务** | 相机拍照 · 图库选图 · 文件选择 · base64 / dataUrl / uri 三种序列化格式 | +| **权限服务** | camera · microphone · location · photos · videos · storage 统一映射 | +| **导航服务** | URL scheme 白名单路由,非 WebView 协议自动跳转外部应用 | +| **壳层 UI** | 启动加载动画 · 错误恢复页 · 进度条 · 不支持平台兜底页 | + +## 使用方式 + +```dart +import 'package:web_shell_core/web_shell_core.dart'; + +void main() { + runShellApp( + ShellEnvironment( + appName: '全学通', + appKey: 'quanxue_prod', + accentColor: Color(0xFF3ED37B), + backgroundColor: Color(0xFFFFFFFF), + textColor: Color(0xFF1F2937), + mutedTextColor: Color(0xFF6B7280), + initialUrl: 'example.com/login', // 可选,不传使用默认地址 + ), + ); +} +``` + +## 代码结构 + +``` +lib/ +├── core_app.dart # 库入口 + part 指令 + runShellApp() +├── web_shell_core.dart # 公开 API 导出 +└── src/ + ├── config/ + │ ├── shell_environment.dart # 品牌配置数据类 + │ └── url_resolver.dart # URL 解析与归一化 + ├── engine/ + │ ├── compatibility.dart # Android WebView 兼容检测 + │ └── recovery.dart # 启动看门狗 + 错误映射 + ├── bridge/ + │ ├── bridge_protocol.dart # JS Bridge 注入与响应 + │ ├── bridge_actions.dart # Action handler(占位) + │ └── legacy_camera_compat.dart # 旧相机 JS 兼容层 + ├── services/ + │ ├── media_service.dart # 相机/图库/文件 + 序列化 + │ ├── permission_service.dart # 权限类型映射 + │ └── navigation_service.dart # URL 路由 + 外链跳转 + ├── ui/ + │ ├── shell_app.dart # MaterialApp 入口 + │ ├── shell_page.dart # WebView 主页面 + │ ├── launch_overlay.dart # 启动加载动画 + │ ├── error_overlay.dart # 错误恢复页 + │ ├── progress_bar.dart # 顶部进度条 + │ └── unsupported_platform_page.dart # 平台兜底页 + └── testing/ + └── test_hooks.dart # 测试钩子(@visibleForTesting) +``` + +## 测试 + +```bash +cd packages/web_shell_core +flutter test +``` + +当前 **54 个测试用例**,覆盖: +- 平台检测 · URL 解析 · 兼容性策略 · 错误映射 +- Bridge 注入/响应/异常处理 · 媒体序列化 · 权限映射 +- 导航路由 · 所有独立 UI 组件 ## 平台约束 -当前仅支持 Android。 +仅支持 **Android**。其他平台会展示兜底提示页。 + +## Android 原生层 + +`CoreShellActivity`(Java)提供: +- WebView 数据目录隔离(避免多进程冲突) +- 旧进程自动终止 +- WebView 信息查询(SDK 版本、WebView 包名/版本号) +- WebView 状态深度重置 diff --git a/packages/web_shell_core/pubspec.yaml b/packages/web_shell_core/pubspec.yaml index e076185..03417c9 100644 --- a/packages/web_shell_core/pubspec.yaml +++ b/packages/web_shell_core/pubspec.yaml @@ -1,5 +1,5 @@ name: web_shell_core -description: "A new Flutter plugin project." +description: "Android 平板专用 H5 壳核心库,提供 WebView 引擎、JS Bridge 和宿主服务。" version: 0.0.1 homepage: