diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd37925..ac431ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -203,6 +203,11 @@ jobs: - name: Run plugin unit tests run: flutter test + # 发版版本串一致性(pubspec / podspec / inspector_version / README / CHANGELOG) + # Release version-string consistency (pubspec / podspec / inspector_version / README / CHANGELOG) + - name: Check release version consistency + run: dart run tool/check_release.dart + # ========== Example 应用 / Example app ========== - name: Install example dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b4472f..116b1f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 1.12.0 + +### Added / 新增 +- 统一会话时间线 `TimelineViewer`(面板首个 Tab):把网络 / 日志 / 异常 / 路由 / 告警五类数据按时间归并成一条流(`TimelineService.build`),可逐来源筛选,点任意事件即「聚焦它前后 ±N 秒」的全部来源事件(`TimelineService.contextAround`,默认 10s,可切 5/30s),一眼看清因果(如「路由跳转 → 两个请求 → error 日志 → 5xx 告警」)。数据仍归属各自服务,视图只做归并,面板未打开时零开销。 / Unified session timeline `TimelineViewer` (first panel tab): merges network / logs / errors / routes / alerts into one time-ordered stream (`TimelineService.build`), with per-source filtering and a "focus ±N seconds around any event" mode (`TimelineService.contextAround`, default 10s, toggle 5/30s) to see causality at a glance (e.g. "route push → two requests → error log → 5xx alert"). Data still lives in its own service; the view only merges, costing nothing while the panel is closed. +- 主线程阻塞看门狗(FPS 页内 `BlockingWatchdogCard`):用一个 100ms 低频心跳直接测量 UI isolate 的响应间隔,默认关闭、在 FPS 页内有独立开关(不随 FPS 总开关联动,不开启则零开销)。超过 300ms 无响应即记录一条阻塞事件(时长 / 时间 / 附近日志),专门补上 FPS 的盲区——真正卡死时不产帧、FPS 反而显示空闲。 / Main-thread blocking watchdog (`BlockingWatchdogCard` inside the FPS tab): a 100ms low-frequency heartbeat measures the UI isolate's responsiveness directly, off by default with its own switch (not tied to the FPS master switch, so it costs nothing unless you opt in). A gap past 300ms records a blocking event (duration / time / nearby logs), closing the FPS blind spot where a real stall produces no frames and FPS reads as idle. +- 发版版本一致性自检 `tool/check_release.dart`(及 `test/version_consistency_test.dart`):以 `pubspec.yaml` 的 `version` 为唯一真值,核对 podspec、`InspectorVersion.value`、两个 README 的安装片段与升级提示、`CHANGELOG.md` 顶部是否一致,并在 CI 的 `Analyze & Test` 之后单独一步拦截版本漂移。 / Release version-consistency self-check `tool/check_release.dart` (and `test/version_consistency_test.dart`): takes `pubspec.yaml`'s `version` as the single source of truth and verifies the podspec, `InspectorVersion.value`, both READMEs' install snippets and upgrade callouts, and the top of `CHANGELOG.md`; CI runs it as its own step after `Analyze & Test` to block version drift. + +### Fixed / 修复 +- `runAppWithInspector` 在 Flutter binding 已被调用方(外层 Zone)初始化时不再崩溃:此前只要调用方按常规先 `WidgetsFlutterBinding.ensureInitialized()` 或 `await` 了会触发 platform channel 的插件(如 `SharedPreferences`),就会抛 "Zone mismatch" 断言。现已探测 binding 状态,命中则跳过 Zone 包裹、降级为直接 `runApp`(debugPrint 日志仍捕获,仅 `print` 直出不捕获)并打一次提示。 / `runAppWithInspector` no longer crashes when the Flutter binding was already initialized by the caller (outer Zone): previously, any caller that ran `WidgetsFlutterBinding.ensureInitialized()` or awaited a platform-channel plugin (e.g. `SharedPreferences`) first hit a "Zone mismatch" assertion. It now detects the binding state and degrades to a plain `runApp` (debugPrint logs still captured, only raw print() not), logging one notice. + ## 1.11.1 ### Fixed / 修复 diff --git a/README.md b/README.md index cb039b9..b700896 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ An in-app developer console for Flutter: inspect HTTP, WebSocket & gRPC traffic, [![Dart](https://img.shields.io/badge/Dart-✓-0175C2?logo=dart)](https://dart.dev) [![Style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) -> **🔔 Upgrade recommended:** This release fixes route arguments being cast directly to `Map`, which threw a `TypeError` for any non-Map argument (custom page-argument classes, data models, or `Map` literals) and aborted host-app navigation. Arguments are now normalized defensively (null kept / Map copied with String keys / JSON-expanded / `toString` fallback), and `_logRoute` is wrapped so an inspector failure can never break navigation again. All users are encouraged to upgrade to the latest version (`^1.11.1`). +> **🔔 Upgrade recommended:** This release adds a unified session timeline and a main-thread blocking watchdog (catches stalls invisible to FPS); `runAppWithInspector` also degrades gracefully instead of crashing with "Zone mismatch" when the binding is pre-initialized. All users are encouraged to upgrade to the latest version (`^1.12.0`). 🌐 **[Official Website](https://www.zerolabsco.com/)**  ·  📦 **[View on pub.dev](https://pub.dev/packages/zero_inspector_kit)**  ·  🔗 **[View on GitHub](https://github.com/zero-labsco/zero_inspector_kit)** @@ -102,7 +102,7 @@ An in-app developer console for Flutter: inspect HTTP, WebSocket & gRPC traffic, ```yaml dependencies: - zero_inspector_kit: ^1.11.1 + zero_inspector_kit: ^1.12.0 ``` ### GitHub @@ -112,7 +112,7 @@ dependencies: zero_inspector_kit: git: url: https://github.com/zero-labsco/zero_inspector_kit.git - ref: release/v1.11.1 # replace 1.11.1 with the version you need + ref: release/v1.12.0 # replace 1.12.0 with the version you need ``` --- diff --git a/README_zh.md b/README_zh.md index 614ab7b..5454370 100644 --- a/README_zh.md +++ b/README_zh.md @@ -19,7 +19,7 @@ [![Dart](https://img.shields.io/badge/Dart-✓-0175C2?logo=dart)](https://dart.dev) [![Style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) -> **🔔 推荐升级:** 本次修复了路由参数被直接强转为 `Map` 的问题——任何非 Map 参数(自定义页面参数类、数据模型,或推断为 `Map` 的字面量)都会抛出 `TypeError` 并中断宿主 App 的导航;现已改为防御式归一化(null 保持 / Map 做 String 键浅拷贝 / 尽量经 JSON 展开 / 否则 `toString`),并将 `_logRoute` 包裹保护,检查器自身出错也绝不会再中断导航。建议所有用户升级到最新版本(`^1.11.1`)。 +> **🔔 推荐升级:** 本版本新增统一会话时间线,以及在 FPS 之下看不见的「零帧卡死」主线程阻塞看门狗;当 binding 已被预先初始化时,`runAppWithInspector` 不再以 "Zone mismatch" 崩溃,而是优雅降级。建议所有用户升级到最新版本(`^1.12.0`)。 🌐 **[官方网站](https://www.zerolabsco.com/)**  ·  📦 **[在 pub.dev 查看](https://pub.dev/packages/zero_inspector_kit)**  ·  🔗 **[查看 GitHub 仓库](https://github.com/zero-labsco/zero_inspector_kit)** @@ -101,7 +101,7 @@ ```yaml dependencies: - zero_inspector_kit: ^1.11.1 + zero_inspector_kit: ^1.12.0 ``` ### GitHub @@ -111,7 +111,7 @@ dependencies: zero_inspector_kit: git: url: https://github.com/zero-labsco/zero_inspector_kit.git - ref: release/v1.11.1 # 将 1.11.1 替换为你需要的版本号 + ref: release/v1.12.0 # 将 1.12.0 替换为你需要的版本号 ``` --- diff --git a/docs/404.html b/docs/404.html index 245bc8d..833639f 100644 --- a/docs/404.html +++ b/docs/404.html @@ -1 +1 @@ -404: This page could not be found

404

This page could not be found.

\ No newline at end of file +404: This page could not be found

404

This page could not be found.

\ No newline at end of file diff --git a/docs/404/index.html b/docs/404/index.html index 245bc8d..833639f 100644 --- a/docs/404/index.html +++ b/docs/404/index.html @@ -1 +1 @@ -404: This page could not be found

404

This page could not be found.

\ No newline at end of file +404: This page could not be found

404

This page could not be found.

\ No newline at end of file diff --git a/docs/Alerts/index.html b/docs/Alerts/index.html index 11543a3..dbededb 100644 --- a/docs/Alerts/index.html +++ b/docs/Alerts/index.html @@ -1,4 +1,4 @@ -
🔔 Alerts

Alerts / 告警系统

+
🔔 Alerts

Alerts / 告警系统

Overview / 概述

Available since v1.3.0

@@ -62,4 +62,4 @@

Alerts are evaluated in-process and shown in the developer console only; they do not send data off-device / 告警仅在本机开发者控制台内评估与展示,不会将数据发送到设备外
  • Persisted to disk (since v1.11.0): triggered alerts are flushed to the SQLite ring buffer and replay into the Alerts tab after an app restart, and are included in the session archive export / 已落盘持久化(v1.11.0 起):已触发的告警会写入 SQLite 环形缓冲,重启后回放至 Alerts 标签页,并包含在会话存档导出中
  • The alert system is tree-shaken out in release builds, like the rest of the inspector / 与检查器其余部分一样,告警系统在 release 构建中被 tree-shake 移除
  • -


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Configuration/index.html b/docs/Configuration/index.html index 0f21937..b24101f 100644 --- a/docs/Configuration/index.html +++ b/docs/Configuration/index.html @@ -1,4 +1,4 @@ -
    ⚙️ Configuration

    Configuration / 配置说明

    +
    ⚙️ Configuration

    Configuration / 配置说明

    ZeroInspectorKit.init() Parameters / 初始化参数

    @@ -567,4 +567,4 @@

    PropertyTypeDescriptionisRunningboolWhether monitoring is currently active / 是否正在监控currentFpsdoubleCurrent FPS (updated every 500ms) / 当前 FPS(每 500ms 更新)jankRatedoubleJank rate as percentage / 卡顿率(百分比)totalFrameCountintTotal frames captured / 总帧数totalJankyCountintTotal janky frames (>16ms) / 总卡顿帧数(>16ms)lastFrameJankyboolWhether the most recent frame was janky / 最近一帧是否卡顿fpsHistoryList<double>Recent 60 FPS values (unmodifiable) / 最近 60 个 FPS 值(不可变)frameRecordsList<FrameRecord>Recent frame records (unmodifiable, up to 3600) / 最近帧记录(不可变,最多 3600 条)

    See FPS Viewer for full feature details.

    -

    完整功能详情见 FPS Viewer。


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +

    完整功能详情见 FPS Viewer。


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Custom-Database-Provider/index.html b/docs/Custom-Database-Provider/index.html index 88b8e2b..39dfcfd 100644 --- a/docs/Custom-Database-Provider/index.html +++ b/docs/Custom-Database-Provider/index.html @@ -1,4 +1,4 @@ -
    🔌 Custom Database Provider

    Custom Database Provider / 自定义数据库提供者

    +
    🔌 Custom Database Provider

    Custom Database Provider / 自定义数据库提供者

    Overview / 概述

    Zero Inspector Kit supports extending database inspection beyond SQLite through the DatabaseProvider interface.

    Zero Inspector Kit 通过 DatabaseProvider 接口支持扩展数据库检查功能到 SQLite 以外的数据库。

    @@ -129,4 +129,4 @@

    插件内置 SqliteDatabaseProvider,当 enableDatabaseScan 为 true(默认)时自动注册。

    You can also register it manually:

    也可以手动注册:

    -
    DatabaseRegistry.instance.registerProvider(SqliteDatabaseProvider());


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +
    DatabaseRegistry.instance.registerProvider(SqliteDatabaseProvider());

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Database-Viewer/index.html b/docs/Database-Viewer/index.html index a29e488..80adaad 100644 --- a/docs/Database-Viewer/index.html +++ b/docs/Database-Viewer/index.html @@ -1,4 +1,4 @@ -
    💾 Database Viewer

    Database Viewer / 数据库查看器

    +

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +

    对于非 SQLite 数据库,请参阅 自定义数据库提供者。


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Errors/index.html b/docs/Errors/index.html index 484a94e..454f063 100644 --- a/docs/Errors/index.html +++ b/docs/Errors/index.html @@ -1,4 +1,4 @@ -
    🚨 Errors

    Errors / 异常聚合查看器

    +

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/FAQ/index.html b/docs/FAQ/index.html index e6f6bec..38bf795 100644 --- a/docs/FAQ/index.html +++ b/docs/FAQ/index.html @@ -1,4 +1,4 @@ -
    ❓ FAQ

    FAQ / 常见问题

    +
    ❓ FAQ

    FAQ / 常见问题

    General / 通用

    Q: Does the inspector affect production builds? / 检查器会影响生产构建吗?

    A: No. The inspector is automatically disabled in release mode via kReleaseMode. Flutter’s tree-shaking removes all inspector-related code from production builds. You don’t need to remove any code.

    @@ -132,4 +132,4 @@

    可以。本项目采用 MPL-2.0 许可证,可用于闭源商业 App——未修改直接使用时无需公开任何源码;若修改了插件源文件,被修改的文件必须以 MPL-2.0 公开源码(你自己的 App 仍无需开源)。

    Q: Are you liable for issues in modified versions? / 修改版出问题你们负责吗?

    A: This plugin is provided “as is”, without warranty of any kind. The author assumes no responsibility or liability for the functionality, security, or any consequences arising from the use of modified versions or derivative projects.

    -

    本插件按”原样”提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及任何使用后果承担责任。


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +

    本插件按”原样”提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及任何使用后果承担责任。


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/FPS-Viewer/index.html b/docs/FPS-Viewer/index.html index 0157ea1..add1a2f 100644 --- a/docs/FPS-Viewer/index.html +++ b/docs/FPS-Viewer/index.html @@ -1,4 +1,4 @@ -
    🎯 FPS Viewer

    FPS Viewer / FPS 监控面板

    +
    🎯 FPS Viewer

    FPS Viewer / FPS 监控面板

    The FPS Viewer provides real-time frame performance analysis, including current FPS, jank rate, frame duration stats, and an FPS trend chart.

    FPS 监控面板提供实时帧性能分析,包括当前 FPS、卡顿率、帧耗时统计和 FPS 趋势图。

    @@ -64,6 +64,22 @@

    Useful for measuring a specific interaction scenario / 适合测量特定交互场景

    “Reset” 按钮清空所有统计和历史数据,适合测量特定交互场景。

    +

    Main-thread Blocking Watchdog / 主线程阻塞看门狗

    +

    The FPS panel also includes a main-thread blocking watchdog that catches stalls FPS cannot see.

    +

    FPS 面板还内置主线程阻塞看门狗,专门捕捉 FPS 看不见的卡死。

    +
    +

    Available since v1.12.0

    +

    v1.12.0 起可用

    +
    +

    Why it’s needed / 为什么需要: FPS only measures frames. When the UI isolate truly stalls (e.g. a synchronous heavy computation), it produces zero frames, so FPS reads as idle while the app is actually frozen. The watchdog closes that blind spot.

    +

    FPS 只测帧。当 UI isolate 真正卡死(如同步重计算)时,它零帧产出,于是 FPS 显示空闲,而 App 其实已冻住。看门狗补上这个盲区。

    +

    How it works / 工作原理:

    +
      +
    • A low-frequency 100ms heartbeat measures the UI isolate’s responsiveness directly / 用 100ms 低频心跳 直接测量 UI isolate 的响应间隔
    • +
    • If a gap exceeds 300ms with no response, it records a blocking event (duration / time / nearby logs) / 若超过 300ms 无响应,记录一条阻塞事件(时长 / 时间 / 附近日志)
    • +
    • Off by default with its own switch (not tied to the FPS master switch) — zero overhead unless you opt in / 默认关闭、有独立开关(不随 FPS 总开关联动),不开启则零开销
    • +
    +

    开启后,真正卡死(不产帧)也会被记成阻塞事件,配合 FPS 一起看因果。

    How It Works / 工作原理

    FPS monitoring uses Flutter’s WidgetsBinding.instance.addTimingsCallback to receive frame timing information from the engine. The callback is batched — it may return multiple FrameTiming objects per call, so each frame is recorded individually inside the loop to ensure accurate FPS calculation.

    FPS 监控使用 Flutter 的 WidgetsBinding.instance.addTimingsCallback 接收引擎的帧时序信息。该回调是批量的——每次调用可能返回多个 FrameTiming 对象,因此在循环内部为每帧单独记录时间戳,确保 FPS 计算准确。

    @@ -239,4 +255,4 @@

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Getting-Started/index.html b/docs/Getting-Started/index.html index 03afc6f..e9859cd 100644 --- a/docs/Getting-Started/index.html +++ b/docs/Getting-Started/index.html @@ -1,4 +1,4 @@ -
    🚀 Getting Started

    Getting Started / 快速开始

    +
    🚀 Getting Started

    Getting Started / 快速开始

    Quick Start / 快速开始

    Integrate with just 1 line of code:

    仅需 1 行代码 即可完成集成:

    @@ -69,6 +69,10 @@

    Note / 说明: The Memory and FPS panels are available as tabs but are off by default to avoid performance overhead. Toggle the switch at the top of each panel to start collecting data. See Memory Viewer and FPS Viewer for details.

    Memory 和 FPS 面板作为标签页可用,但默认关闭以避免性能开销。在各自面板顶部打开开关才会开始采集数据。详见 Memory Viewer 和 FPS Viewer。

    +
    +

    Pre-initialized binding / 提前初始化的 binding: If you call WidgetsFlutterBinding.ensureInitialized() or await a platform-channel plugin (e.g. SharedPreferences) before runAppWithInspector(), the binding is already initialized. In that case runAppWithInspector() degrades gracefully to a plain runApp() (capturing debugPrint logs, but not raw print()) instead of throwing a “Zone mismatch” assertion. No extra setup is needed.

    +

    binding 已提前初始化:如果你在 runAppWithInspector() 之前调用了 WidgetsFlutterBinding.ensureInitialized() 或 await 了会触发 platform channel 的插件(如 SharedPreferences),binding 已被初始化。此时 runAppWithInspector() 会优雅降级为普通 runApp()(仍捕获 debugPrint 日志,但不捕获原始 print()),而非抛出 “Zone mismatch” 断言。无需额外处理。

    +

    Production Build / 生产构建

    The inspector is automatically disabled in release mode. You don’t need to remove any code — Flutter’s tree-shaking will remove all inspector-related code from production builds.

    检查器在 release 模式下会自动禁用,无需移除任何代码,Flutter 的 tree-shaking 会自动移除所有检查器相关代码。

    @@ -84,4 +88,4 @@

    Installation — Detailed installation methods / 详细安装方式
  • Usage — Full usage guide / 完整使用指南
  • Configuration — Configuration options / 配置选项
  • -


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Installation/index.html b/docs/Installation/index.html index 33027a9..639f3f8 100644 --- a/docs/Installation/index.html +++ b/docs/Installation/index.html @@ -1,9 +1,9 @@ -
    📦 Installation

    Installation / 安装

    +

    Then run:

    然后运行:

    flutter pub get
    @@ -14,7 +14,7 @@

    zero_inspector_kit: git: url: https://github.com/zero-labsco/zero_inspector_kit.git - ref: release/v1.11.1

    + ref: release/v1.12.0

    Platform Setup / 平台配置

    Android

    No additional configuration needed.

    @@ -47,4 +47,4 @@

  • Getting Started — Quick start guide / 快速开始
  • Usage — Full usage guide / 完整使用指南
  • -


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Log-Viewer/index.html b/docs/Log-Viewer/index.html index ec6613e..52f83df 100644 --- a/docs/Log-Viewer/index.html +++ b/docs/Log-Viewer/index.html @@ -1,4 +1,4 @@ -
    📝 Log Viewer

    Log Viewer / 日志查看器

    +
    📝 Log Viewer

    Log Viewer / 日志查看器

    Overview / 概述

    The Log Viewer automatically captures logs from multiple sources with zero configuration.

    日志查看器自动从多个来源捕获日志,无需配置。

    @@ -162,4 +162,4 @@

    Starting the Log Interceptor / 启动日志拦截器

    If using runAppWithInspector(), the log interceptor starts automatically. Otherwise:

    如果使用 runAppWithInspector(),日志拦截器会自动启动。否则:

    -
    InspectorLogInterceptor.instance.start();

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +
    InspectorLogInterceptor.instance.start();

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Memory-Viewer/index.html b/docs/Memory-Viewer/index.html index 7a1cba6..501fbfb 100644 --- a/docs/Memory-Viewer/index.html +++ b/docs/Memory-Viewer/index.html @@ -1,4 +1,4 @@ -
    📊 Memory Viewer

    Memory Viewer / 内存监控面板

    +
    📊 Memory Viewer

    Memory Viewer / 内存监控面板

    The Memory Viewer provides comprehensive in-app memory analysis, including trend charts, Dart Heap details, Native memory breakdown, memory leak detection, image cache monitoring, and storage statistics.

    内存监控面板提供应用内全面内存分析,包括趋势图、Dart Heap 详情、Native 内存分项、内存泄漏检测、图片缓存监控和存储统计。

    @@ -262,4 +262,4 @@

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Network-Inspector/index.html b/docs/Network-Inspector/index.html index 01fa20d..1d21dbc 100644 --- a/docs/Network-Inspector/index.html +++ b/docs/Network-Inspector/index.html @@ -1,4 +1,4 @@ -
    🌐 Network Inspector

    Network Inspector / 网络检查器

    +
    🌐 Network Inspector

    Network Inspector / 网络检查器

    Overview / 概述

    The Network Inspector automatically captures all HTTP requests made via the http package and Dio, with zero configuration needed.

    网络检查器自动捕获所有通过 http 包 和 Dio 发送的 HTTP 请求,无需任何配置。

    @@ -330,4 +330,4 @@

    Create / edit / delete rules / 创建 / 编辑 / 删除规则
  • Enable / disable individual rules / 启用 / 禁用单条规则
  • View rule status indicators in the request list / 在请求列表中查看规则状态标识
  • -


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Route-Tracker/index.html b/docs/Route-Tracker/index.html index 89d5483..ad72d05 100644 --- a/docs/Route-Tracker/index.html +++ b/docs/Route-Tracker/index.html @@ -1,4 +1,4 @@ -
    🧭 Route Tracker

    Route Tracker / 路由追踪

    +
    🧭 Route Tracker

    Route Tracker / 路由追踪

    Overview / 概述

    The Route Tracker monitors navigation history, recording all route push, pop, and replacement events.

    路由追踪器监控导航历史,记录所有路由 push、pop 和替换事件。

    @@ -62,4 +62,4 @@

    MaterialApp(
       navigatorObservers: [InspectorRouteObserver()],
       home: MyHomePage(),
    -)


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +)

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Timeline/index.html b/docs/Timeline/index.html new file mode 100644 index 0000000..446652e --- /dev/null +++ b/docs/Timeline/index.html @@ -0,0 +1,44 @@ +
    🧵 Timeline

    Timeline / 统一会话时间线

    +

    Overview / 概述

    +

    The Timeline tab merges network, logs, errors, routes, and alerts into a single time-ordered stream, so you can see the full causal chain of a session at a glance — e.g. “route push → two requests → error log → 5xx alert”. Each source keeps its own data; the Timeline view only merges them for display.

    +

    统一会话时间线把网络、日志、异常、路由、告警归并为一条按时间排序的流,让你一眼看清一次会话的完整因果链——例如「路由跳转 → 两个请求 → error 日志 → 5xx 告警」。各来源的数据仍归属各自服务,时间线视图只做归并显示。

    +
    +

    Available since v1.12.0

    +

    v1.12.0 起可用

    +
    +

    Where to find it / 入口

    +

    Tap the floating inspector button → the Timeline tab (located after Routes and before Widgets in the panel).

    +

    点击悬浮检查器按钮 → Timeline 标签页(位于面板中 Routes 之后、Widgets 之前)。

    +

    Features / 功能

    +

    1. Merged stream / 归并流

    +
      +
    • Network / Logs / Errors / Routes / Alerts appear interleaved by timestamp / 网络 / 日志 / 异常 / 路由 / 告警按时间戳交错排列
    • +
    • Each entry shows its source icon, a colored left accent bar, timestamp, and a concise summary / 每条显示来源图标、左侧彩色强调条、时间戳与精炼摘要
    • +
    • Tap any entry to expand details (e.g. request URL/status, log level/message, route action/name) / 点击任意条目展开详情(如请求 URL/状态、日志级别/内容、路由操作/名称)
    • +
    +

    把五类数据按时间归并,每条带来源图标、彩色强调条、时间戳与摘要,可点击展开。

    +

    2. Per-source filtering / 逐来源筛选

    +
      +
    • Toggle each source on/off to focus on the ones you care about / 逐来源开关,只看关心的来源
    • +
    • Filtering only hides entries from the merged view; underlying data is untouched / 筛选只隐藏归并视图中的条目,底层数据不受影响
    • +
    +

    顶部可按来源开关自由筛选,归并显示内容随之变化,底层数据不变。

    +

    3. Focus ±N seconds / ±N 秒聚焦

    +
      +
    • Tap any event to enter “focus” mode: the view keeps only events within ±N seconds of the anchor / 点击任意事件进入「聚焦」模式,仅保留锚点前后 ±N 秒内的事件
    • +
    • Window selector: 5s / 10s (default) / 30s / 窗口可选:5s / 10s(默认)/ 30s
    • +
    • Great for isolating the exact cause-effect around a failure (e.g. what happened right before a 5xx alert) / 适合隔离某次失败前后的精确因果(如 5xx 告警前到底发生了什么)
    • +
    +

    点击任意事件即可「聚焦它前后 ±N 秒」,把噪音过滤掉,只留因果相关事件。

    +

    How It Works / 工作原理

    +
      +
    • View-only merge — TimelineService.build() reads from InspectorService (network / logs / errors), RouteTrackerService, and AlertService, sorts them by time, and tags each with its TimelineEventKind. It does not copy or own any data. / 仅视图归并:TimelineService.build() 从各服务读取数据、按时间排序并打上来源标签,不复制、不持有数据。
    • +
    • Zero cost when closed — no timers, no listeners, no polling while the panel is not open; the merge runs on demand when you open the Timeline tab. / 面板关闭时零开销:未打开面板时不跑定时器、不监听、不轮询,仅在打开 Timeline 时按需归并。
    • +
    • contextAround(anchor, window) — given an anchor event and a Duration window, returns the slice of events whose timestamp falls within [anchor - window, anchor + window], oldest-first. / 给定锚点事件与窗口时长,返回落在 [锚点 - 窗口, 锚点 + 窗口] 内的事件,按时间升序。
    • +
    + +
      +
    • Usage — General usage guide / 通用使用指南
    • +
    • FPS Viewer — Includes the main-thread blocking watchdog / 含主线程阻塞看门狗
    • +
    • Route Tracker — Route tracking details / 路由追踪详情
    • +

    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Usage/index.html b/docs/Usage/index.html index eee28a4..710da14 100644 --- a/docs/Usage/index.html +++ b/docs/Usage/index.html @@ -1,4 +1,4 @@ -
    📖 Usage

    Usage / 使用指南

    +
    📖 Usage

    Usage / 使用指南

    Integration Methods / 集成方式

    void main() {
    @@ -17,8 +17,8 @@ 

    runApp(ZeroInspectorKit.wrapApp(const MyApp())); }

    Inspector Panel / 检查器面板

    -

    The inspector panel contains 9 tabs (as of v1.9.0):

    -

    检查器面板包含 9 个标签页(v1.9.0 起):

    +

    The inspector panel contains 10 tabs (as of v1.12.0):

    +

    检查器面板包含 10 个标签页(v1.12.0 起):

    @@ -74,7 +74,12 @@

    TabIconFeatureNetwork🌐HTTP request viewing + interceptor rules / 网络请求查看 + 拦截修改Logs📝Log viewing with level filter / 日志查看Errors🚨Aggregated & deduped crash viewing / 去重聚合的异常查看Database💾Database and table inspection / 数据库查看Memory📊Memory trend, Dart Heap, Native memory, leak detection / 内存趋势、Dart Heap、Native 内存、泄漏检测FPS🎯Real-time FPS, jank rate, trend chart / 实时 FPS、卡顿率、趋势图Routes🧭Route navigation tracking / 路由追踪Widgets🔍Widget tree snapshot for the current route / 当前路由的 Widget 树快照Alerts🔔Rule-based alerts with unread badge / 基于规则的告警与未读角标 + + + + + +
    TabIconFeature
    Network🌐HTTP request viewing + interceptor rules / 网络请求查看 + 拦截修改
    Logs📝Log viewing with level filter / 日志查看
    Errors🚨Aggregated & deduped crash viewing / 去重聚合的异常查看
    Database💾Database and table inspection / 数据库查看
    Memory📊Memory trend, Dart Heap, Native memory, leak detection / 内存趋势、Dart Heap、Native 内存、泄漏检测
    FPS🎯Real-time FPS, jank rate, trend chart, plus main-thread blocking watchdog / 实时 FPS、卡顿率、趋势图,以及主线程阻塞看门狗
    Routes🧭Route navigation tracking / 路由追踪
    Timeline🧵Unified session timeline: network / logs / errors / routes / alerts merged into one stream, with ±N-second focus / 统一会话时间线:网络/日志/异常/路由/告警按时间归并,支持 ±N 秒聚焦
    Widgets🔍Widget tree snapshot for the current route / 当前路由的 Widget 树快照
    Alerts🔔Rule-based alerts with unread badge / 基于规则的告警与未读角标

    The Memory and FPS monitors are off by default to avoid performance overhead. Toggle the switch at the top of each panel to start collecting data.

    Memory 与 FPS 监控默认关闭以避免性能开销。在各自面板顶部打开开关才会开始采集数据。

    @@ -202,4 +207,4 @@

    Session Persistence / 会话持久化

    Logs, network requests, and aggregated errors are asynchronously flushed to a local SQLite ring buffer. On the next launch, logs and aggregated errors replay into their tabs; network requests stay archived on disk for later export. Data therefore survives app restarts even if the inspector panel was never opened. Tap the storage icon in the panel header to open the Persisted data manager: view row counts per category, export the full session archive, or clear the disk. See Configuration (PersistenceService section) for the API and tuning parameters.

    -

    日志、网络请求与聚合异常会被异步写入本地 SQLite 环形缓冲。下次启动时,日志与聚合异常会回放入各自标签页;网络请求保留在磁盘存档,供之后导出。因此即使从未打开过检查器面板,数据也能跨重启保留。点击面板头部的存储图标可打开 Persisted data 管理弹层:查看各类别行数、导出完整会话存档或清空磁盘。API 与调参详见 Configuration(PersistenceService 一节)。


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +

    日志、网络请求与聚合异常会被异步写入本地 SQLite 环形缓冲。下次启动时,日志与聚合异常会回放入各自标签页;网络请求保留在磁盘存档,供之后导出。因此即使从未打开过检查器面板,数据也能跨重启保留。点击面板头部的存储图标可打开 Persisted data 管理弹层:查看各类别行数、导出完整会话存档或清空磁盘。API 与调参详见 Configuration(PersistenceService 一节)。


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/_next/static/SozSnA7VxY0DQRMRZIWM2/_buildManifest.js b/docs/_next/static/SozSnA7VxY0DQRMRZIWM2/_buildManifest.js new file mode 100644 index 0000000..48ec39a --- /dev/null +++ b/docs/_next/static/SozSnA7VxY0DQRMRZIWM2/_buildManifest.js @@ -0,0 +1 @@ +self.__BUILD_MANIFEST=function(e){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[e,"static/chunks/pages/index-09093d7a1b2e97fb.js"],"/Alerts":[e,"static/chunks/pages/Alerts-a070cfa208c3ff82.js"],"/Configuration":[e,"static/chunks/pages/Configuration-59d63292bf84e213.js"],"/Custom-Database-Provider":[e,"static/chunks/pages/Custom-Database-Provider-bbe4c3db5859578c.js"],"/Database-Viewer":[e,"static/chunks/pages/Database-Viewer-bae8bb7c99f3f2a2.js"],"/Errors":[e,"static/chunks/pages/Errors-3214b059d0e33517.js"],"/FAQ":[e,"static/chunks/pages/FAQ-09aca322a5a031c7.js"],"/FPS-Viewer":[e,"static/chunks/pages/FPS-Viewer-954be0ba8ec208c8.js"],"/Getting-Started":[e,"static/chunks/pages/Getting-Started-8e9a8ab5ed31839b.js"],"/Installation":[e,"static/chunks/pages/Installation-ef9aacdf09d13218.js"],"/Log-Viewer":[e,"static/chunks/pages/Log-Viewer-458840c22be40d80.js"],"/Memory-Viewer":[e,"static/chunks/pages/Memory-Viewer-dd3d33cfe929fd44.js"],"/Network-Inspector":[e,"static/chunks/pages/Network-Inspector-b7d8a61d2d542ffd.js"],"/Route-Tracker":[e,"static/chunks/pages/Route-Tracker-8d1b310180aa3bf2.js"],"/Timeline":[e,"static/chunks/pages/Timeline-a6d10ad811492520.js"],"/Usage":[e,"static/chunks/pages/Usage-405dadea5015323f.js"],"/_error":["static/chunks/pages/_error-7a92967bea80186d.js"],"/_meta":["static/chunks/pages/_meta-b47e7a01bb6b7929.js"],sortedPages:["/","/Alerts","/Configuration","/Custom-Database-Provider","/Database-Viewer","/Errors","/FAQ","/FPS-Viewer","/Getting-Started","/Installation","/Log-Viewer","/Memory-Viewer","/Network-Inspector","/Route-Tracker","/Timeline","/Usage","/_app","/_error","/_meta"]}}("static/chunks/812-3fcedf6f3c3b1908.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB(); \ No newline at end of file diff --git a/docs/_next/static/b7ivpfeuFIodlQxmXYCzo/_ssgManifest.js b/docs/_next/static/SozSnA7VxY0DQRMRZIWM2/_ssgManifest.js similarity index 100% rename from docs/_next/static/b7ivpfeuFIodlQxmXYCzo/_ssgManifest.js rename to docs/_next/static/SozSnA7VxY0DQRMRZIWM2/_ssgManifest.js diff --git a/docs/_next/static/b7ivpfeuFIodlQxmXYCzo/_buildManifest.js b/docs/_next/static/b7ivpfeuFIodlQxmXYCzo/_buildManifest.js deleted file mode 100644 index 1ea382d..0000000 --- a/docs/_next/static/b7ivpfeuFIodlQxmXYCzo/_buildManifest.js +++ /dev/null @@ -1 +0,0 @@ -self.__BUILD_MANIFEST=function(e){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[e,"static/chunks/pages/index-2690f32ccd636754.js"],"/Alerts":[e,"static/chunks/pages/Alerts-a0f5430c00bb2cc2.js"],"/Configuration":[e,"static/chunks/pages/Configuration-c728c0430f76605b.js"],"/Custom-Database-Provider":[e,"static/chunks/pages/Custom-Database-Provider-838fb9c0a6151981.js"],"/Database-Viewer":[e,"static/chunks/pages/Database-Viewer-5b3fb1bd8161d7e8.js"],"/Errors":[e,"static/chunks/pages/Errors-7cf1ee698701533d.js"],"/FAQ":[e,"static/chunks/pages/FAQ-3f5854cf7971f23b.js"],"/FPS-Viewer":[e,"static/chunks/pages/FPS-Viewer-b850e87e45780b99.js"],"/Getting-Started":[e,"static/chunks/pages/Getting-Started-cb7caccb4a04217f.js"],"/Installation":[e,"static/chunks/pages/Installation-5edd28387f9ec116.js"],"/Log-Viewer":[e,"static/chunks/pages/Log-Viewer-f91ed29d9fff37fb.js"],"/Memory-Viewer":[e,"static/chunks/pages/Memory-Viewer-5f8bed1aa6f7d6b5.js"],"/Network-Inspector":[e,"static/chunks/pages/Network-Inspector-e55229099cd437f6.js"],"/Route-Tracker":[e,"static/chunks/pages/Route-Tracker-8de8d462ef1f1c4e.js"],"/Usage":[e,"static/chunks/pages/Usage-d81feeeea27e1c63.js"],"/_error":["static/chunks/pages/_error-7a92967bea80186d.js"],"/_meta":["static/chunks/pages/_meta-b47e7a01bb6b7929.js"],sortedPages:["/","/Alerts","/Configuration","/Custom-Database-Provider","/Database-Viewer","/Errors","/FAQ","/FPS-Viewer","/Getting-Started","/Installation","/Log-Viewer","/Memory-Viewer","/Network-Inspector","/Route-Tracker","/Usage","/_app","/_error","/_meta"]}}("static/chunks/812-3fcedf6f3c3b1908.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB(); \ No newline at end of file diff --git a/docs/_next/static/chunks/nextra-data-en-US.json b/docs/_next/static/chunks/nextra-data-en-US.json index 39817a8..aa2ddaf 100644 --- a/docs/_next/static/chunks/nextra-data-en-US.json +++ b/docs/_next/static/chunks/nextra-data-en-US.json @@ -1 +1 @@ -{"/Alerts":{"title":"Alerts / 告警系统","data":{"overview--概述#Overview / 概述":"Available since v1.3.0v1.3.0 起可用\nThe alert system lets you define rules that proactively surface problems across network requests, logs, memory, and FPS — without manually scanning the panels.告警系统允许你定义规则,主动暴露网络请求、日志、内存、FPS 方面的问题,无需手动逐个面板排查。","how-it-works--工作原理#How It Works / 工作原理":"Rules are evaluated by AlertService whenever a relevant event occurs / 每当相关事件发生时,AlertService 会评估规则\nMatched rules produce alerts, and the unread count is exposed via a ValueNotifier / 命中的规则会生成告警,未读数通过 ValueNotifier 暴露\nThe floating button shows an unread-count badge; opening the panel clears it / 悬浮球显示未读数量角标,打开面板即清零\nAn Alerts tab in the inspector panel lists all triggered alerts / 检查器面板的 Alerts 标签页列出所有已触发的告警","rule-types--规则类型#Rule Types / 规则类型":"Type\tTrigger / 触发条件\tExample / 示例\tNetwork\tResponse status code / 响应状态码\tAlert when status ≥ 400 / 状态码 ≥ 400 时告警\tLog\tLog level or message / 日志级别或内容\tAlert on error logs / 出现 error 日志时告警\tMemory\tDart heap / Native memory threshold / 内存阈值\tAlert when Dart heap > 100 MB / Dart 堆 > 100 MB 时告警\tFPS\tFrame rate / 帧率\tAlert when FPS < 50 / FPS < 50 时告警","ui-features--ui-功能#UI Features / UI 功能":"","floating-button-badge--悬浮球角标#Floating Button Badge / 悬浮球角标":"A red badge shows the current unread alert count (clamped 0–99) / 红色角标显示当前未读告警数(0–99)\nThe number is drawn inside the button so it is never clipped at screen edges / 数字直接绘制在按钮内部,吸附屏幕边缘时不会被裁切\nOpening the inspector panel clears the unread count / 打开检查器面板即清零未读数","alerts-tab--alerts-标签页#Alerts Tab / Alerts 标签页":"Lists triggered alerts with type, condition, and timestamp / 列出已触发告警的类型、条件与时间\nTap an alert to jump to the related panel (network / log / memory / FPS) / 点击告警可跳转到相关面板(网络 / 日志 / 内存 / FPS)","notes--备注#Notes / 备注":"Alerts are evaluated in-process and shown in the developer console only; they do not send data off-device / 告警仅在本机开发者控制台内评估与展示,不会将数据发送到设备外\nPersisted to disk (since v1.11.0): triggered alerts are flushed to the SQLite ring buffer and replay into the Alerts tab after an app restart, and are included in the session archive export / 已落盘持久化(v1.11.0 起):已触发的告警会写入 SQLite 环形缓冲,重启后回放至 Alerts 标签页,并包含在会话存档导出中\nThe alert system is tree-shaken out in release builds, like the rest of the inspector / 与检查器其余部分一样,告警系统在 release 构建中被 tree-shake 移除"}},"/Configuration":{"title":"Configuration / 配置说明","data":{"zeroinspectorkitinit-parameters--初始化参数#ZeroInspectorKit.init() Parameters / 初始化参数":"Parameter\tType\tDefault\tDescription\tenable\tbool\ttrue\tEnable inspector (auto false in release mode) / 启用检查器\tenableLogCapture\tbool\ttrue\tEnable log capture / 启用日志捕获\tenableNetworkCapture\tbool\ttrue\tEnable network interception / 启用网络拦截\tenableErrorCapture\tbool\ttrue\tEnable error aggregation (Errors tab) / 启用异常聚合(Errors 标签页)\tenablePersistence\tbool\ttrue\tPersist logs/network/errors to a SQLite ring buffer; logs & errors replay on launch / 将日志/网络/异常落盘到 SQLite 环形缓冲,启动时回放日志与异常\tenableFlutterLeakTracker\tbool\ttrue\tBridge Flutter's official MemoryAllocations as a second leak-detection source / 桥接 Flutter 官方 MemoryAllocations 作为泄漏检测第二来源\tenableDatabaseScan\tbool\ttrue\tEnable database scan / 启用数据库扫描\tenableRouteTracking\tbool\ttrue\tEnable route tracking / 启用路由追踪\tenableWidgetInspector\tbool\ttrue\tEnable Widget tree snapshot / 启用 Widget 树快照\tenableNetworkTimeline\tbool\ttrue\tPrefer the network timeline (waterfall) in request details / 网络详情页默认展示时间轴(瀑布图)\tcustomButton\tWidget?\tnull\tCustom floating button widget / 自定义悬浮按钮\tonLogCaptured\tvoid Function(LogEntry)?\tnull\tLog capture callback for third-party integration / 日志捕获回调\tmaxNetworkItems\tint?\t100\tNetwork request cache cap / 网络请求缓存上限\tmaxLogItems\tint?\t500\tLog entry cache cap / 日志条目缓存上限\tmaxRouteItems\tint?\t200\tRoute record cache cap / 路由记录缓存上限\tmaxBodyPreviewBytes\tint?\t32KB\tBody preview cap, longer bodies truncated / body 预览字节上限,超出截断\t\nenableWidgetInspector / enableNetworkTimeline are also exposed on runAppWithInspector(). (wrapApp() takes only enable.)enableWidgetInspector / enableNetworkTimeline 也可在 runAppWithInspector() 上设置(wrapApp() 仅接受 enable)。","usage-examples--使用示例#Usage Examples / 使用示例":"","disable-specific-features--禁用特定功能#Disable Specific Features / 禁用特定功能":"ZeroInspectorKit.init(\n enableLogCapture: true,\n enableNetworkCapture: false, // Disable network monitoring / 禁用网络监控\n enableDatabaseScan: true,\n enableRouteTracking: false, // Disable route tracking / 禁用路由追踪\n);","with-log-callback--带日志回调#With Log Callback / 带日志回调":"ZeroInspectorKit.init(\n onLogCaptured: (entry) {\n // Forward to your logging service / 转发到你的日志服务\n myLogger.log(entry.message);\n },\n);","conditionalinspector--条件检查器组件#ConditionalInspector / 条件检查器组件":"A convenience widget that automatically shows/hides the inspector based on build mode.根据构建模式自动显示/隐藏检查器的便利组件。\nConditionalInspector(\n child: YourAppWidget(),\n)\nParameter\tType\tDefault\tDescription\tchild\tWidget\trequired\tChild widget / 子组件\tenabled\tbool\ttrue\tEnable inspector / 启用检查器","floatinginspectorbutton--悬浮检查器按钮#FloatingInspectorButton / 悬浮检查器按钮":"Parameter\tType\tDefault\tDescription\tenabled\tbool\ttrue\tEnable button (auto false in release mode) / 启用按钮","inspectorloginterceptor--日志拦截器#InspectorLogInterceptor / 日志拦截器":"Method\tDescription\tstart()\tStart capturing logs / 开始捕获日志\tstop()\tStop capturing logs / 停止捕获日志\tlog(level, message, tag)\tAdd a log entry / 添加日志条目\tverbose(message, tag)\tAdd verbose log / 添加详细日志\tdebug(message, tag)\tAdd debug log / 添加调试日志\tinfo(message, tag)\tAdd info log / 添加信息日志\twarning(message, tag)\tAdd warning log / 添加警告日志\terror(message, tag)\tAdd error log / 添加错误日志\t\nProperty\tType\tDescription\tonLogCaptured\tvoid Function(LogEntry)?\tCallback when a log is captured / 日志捕获回调","inspectorrouteobserver--路由观察者#InspectorRouteObserver / 路由观察者":"Navigator observer for tracking route changes. Auto-injected when using runAppWithInspector() or wrapApp().用于追踪路由变化的 Navigator 观察者。使用 runAppWithInspector() 或 wrapApp() 时自动注入。\nMaterialApp(\n navigatorObservers: [InspectorRouteObserver()],\n home: MyHomePage(),\n)","databaseregistry--数据库注册表#DatabaseRegistry / 数据库注册表":"Register custom database providers:注册自定义数据库提供者:\nDatabaseRegistry.instance.registerProvider(SqliteDatabaseProvider());\nSee Custom Database Provider for more details.详见 自定义数据库提供者。","inspectorlog--简化日志-api#InspectorLog / 简化日志 API":"Available since v1.1.2 / v1.1.2 起可用\nA static wrapper around InspectorLogInterceptor.instance for shorter log calls.InspectorLogInterceptor.instance 的静态包装,用于更简短的日志调用。\nInspectorLog.v('Verbose log');\nInspectorLog.d('Debug log');\nInspectorLog.i('Info log', tag: 'Auth');\nInspectorLog.w('Warning log');\nInspectorLog.e('Error log');\nMethod\tDescription\tstart()\tStart capturing logs / 开始捕获日志\tstop()\tStop capturing logs / 停止捕获日志\tlog(level, message, {tag})\tAdd a log entry / 添加日志条目\tv(message, {tag})\tAdd verbose log / 添加详细日志\td(message, {tag})\tAdd debug log / 添加调试日志\ti(message, {tag})\tAdd info log / 添加信息日志\tw(message, {tag})\tAdd warning log / 添加警告日志\te(message, {tag})\tAdd error log / 添加错误日志\t\nProperty\tType\tDescription\tisRunning\tbool\tWhether log capture is currently active / 日志捕获是否正在运行","memoryinspectorservice--内存监控服务#MemoryInspectorService / 内存监控服务":"Available since v1.1.0 / v1.1.0 起可用\nSingleton service for memory monitoring and leak detection, extends ChangeNotifier.内存监控与泄漏检测单例服务,继承 ChangeNotifier。","full-api--完整接口#Full API / 完整接口":"// Leak tracking (full form) / 泄漏追踪(完整写法)\nMemoryInspectorService.instance.trackObject(\n myController,\n tag: 'HomeController_textController',\n expectedReleaseAfter: const Duration(seconds: 30),\n);\nMemoryInspectorService.instance.untrackObject(myController);\nMemoryInspectorService.instance.clearLeakRecords();","simplified-api--简化接口#Simplified API / 简化接口":"Available since v1.1.2 / v1.1.2 起可用\n// Extension method on Object / Object 上的扩展方法\nmyBloc.trackMemoryLeak(tag: 'HomePage_myBloc');\n// Top-level function / 顶层函数\ntrackMemoryLeak(myBloc, tag: 'HomePage_myBloc');\n// Cancel tracking / 取消追踪\nmyBloc.untrackMemoryLeak();\nSee Memory Viewer for full feature details.完整功能详情见 Memory Viewer。","flutter-memoryallocations-bridge--官方泄漏追踪桥接#Flutter MemoryAllocations Bridge / 官方泄漏追踪桥接":"Available since v1.9.0 / v1.9.0 起可用\nThe leak detector's own WeakReference state machine has a blind spot: an object whose dispose() ran but which has not been GC'd yet still resolves through the weak reference and is flagged as a suspected leak (false positive). LeakTrackerBridge subscribes to Flutter's official FlutterMemoryAllocations (the data source behind leak_tracker) as a second source: once the official stream reports disposed, the object is treated as released (awaiting GC) even if the weak reference still resolves — cutting false positives.自研泄漏检测的 WeakReference 状态机有一个盲点:对象已调用 dispose() 但尚未被 GC 时,弱引用仍存活,会被判定为\"疑似泄漏\"(误报)。LeakTrackerBridge 订阅 Flutter 官方的 FlutterMemoryAllocations(leak_tracker 背后的数据源)作为第二来源:只要官方上报了 disposed,即便弱引用仍存活也判定为已释放(等待 GC),从而显著降低误报。\nEnabled by default via ZeroInspectorKit.init()'s enableFlutterLeakTracker / 通过 init() 的 enableFlutterLeakTracker 默认启用\nOfficial events only cover framework types that report to FlutterMemoryAllocations (Image / Picture / Layer …) — it supplements, not replaces, the custom detector / 官方事件只覆盖会上报给 FlutterMemoryAllocations 的框架类型(如 Image / Picture / Layer),因此是补充而非替代自研方案","errorservice--异常聚合服务#ErrorService / 异常聚合服务":"Available since v1.9.0 / v1.9.0 起可用\nSingleton service for aggregated error capture, extends ChangeNotifier. Hooks FlutterError.onError (keeping the default red error behavior) and dedups each exception by type + stack signature. See Errors for full feature details.异常聚合单例服务,继承 ChangeNotifier。接管 FlutterError.onError(保留默认红色报错行为),按类型 + 堆栈签名去重聚合。完整功能见 Errors。\n// Report an exception manually (gRPC / custom protocols) / 手动上报异常\nErrorService.instance.report(error, stackTrace, 'myModule');\n// Read aggregated records (newest first) / 读取聚合记录(最新在前)\nfinal ErrorRecord r = ErrorService.instance.errors.first;\n// Toggle capture / 切换抓取开关\nErrorService.instance.isEnabled = false;\n// Clear all records / 清空全部记录\nErrorService.instance.clear();\nMember\tDescription\terrors\tList — aggregated records, newest first / 聚合记录,最新在前\terrorCount\tint — aggregated record count / 聚合记录条数\tisEnabled\tbool — capture toggle (programmatic) / 抓取开关(代码控制)\treport(exception, stack, [context])\tManually report an exception / 手动上报异常\trestore(records)\tRestore persisted records (replay on launch) / 恢复持久化记录(启动回放)\tclear()\tClear all aggregated records / 清空所有聚合记录\tinstall() / uninstall()\tHook / restore FlutterError.onError / 接管 / 还原 FlutterError.onError","persistenceservice--持久化服务#PersistenceService / 持久化服务":"Available since v1.9.0 / v1.9.0 起可用\nSingleton service that async-flushes logs, network requests, and aggregated errors to a local SQLite ring buffer (zero_inspector_kit.db) so data survives app restarts. On launch, logs and aggregated errors replay into their tabs; network requests stay archived on disk for later export. Everything is guarded and degrades gracefully: if the DB is unavailable (e.g. desktop without sqflite FFI) isEnabled is false and writes become no-ops, never affecting the host app.单例服务,将日志、网络请求与聚合异常异步落盘到本地 SQLite 环形缓冲(zero_inspector_kit.db),使数据跨重启不丢。启动时日志与聚合异常回放入各自标签页;网络请求保留在磁盘存档,供之后导出。所有操作都有保护并优雅降级:数据库不可用(如桌面端未配置 sqflite FFI)时 isEnabled 为 false,写入变为空操作,绝不影响宿主应用。","tuning--调参#Tuning / 调参":"await PersistenceService.instance.init(\n maxRowsPerTable: 5000, // rows kept per table / 每表保留行数\n retention: const Duration(days: 7), // retention window / 保留时长\n flushInterval: const Duration(seconds: 2), // disk flush interval / 刷盘间隔\n);\nEnabled by default via ZeroInspectorKit.init()'s enablePersistence — the ring buffer flushes automatically, so no manual init() call is needed in normal use.通过 init() 的 enablePersistence 默认启用——环形缓冲会自动刷盘,常规使用无需手动调用 init()。\nMember\tDescription\tisEnabled\tbool — whether the DB is available / 数据库是否可用\tmaxRowsPerTable\tint — row cap per table (ring-buffer trim line) / 每表行数上限\tenqueueLog(e) / enqueueNetwork(r) / enqueueError(e)\tEnqueue an item for async flush / 入队待异步落盘\tflush()\tFlush the buffer to disk and trim / 刷盘并按环形缓冲裁剪\tloadLogs() / loadErrors() / loadNetworkJson()\tLoad persisted data, newest first / 读取已持久化数据(最新在前)\tbuildSessionArchiveJson()\tBuild the full-session archive JSON (logs + errors + network) / 构建完整会话存档 JSON\texportSessionArchiveAndShare()\tExport & share the full-session archive via the system share sheet / 导出并分享完整会话存档\tclearAll()\tClear persisted data on disk / 清空磁盘上的持久化数据\tdispose()\tFlush then close / 先刷盘再关闭","persisted-data-manager--持久化数据管理#Persisted Data Manager / 持久化数据管理":"Tap the storage icon in the panel header to open the Persisted data sheet: it shows the current row counts per category (logs / network / errors), the per-table cap, and whether the data will be replayed on the next launch. Actions:点击面板头部的存储图标打开 Persisted data 管理弹层:展示各类别当前行数(logs / network / errors)、每表上限,以及下次启动是否会回放。支持的操作:\nExport & share session archive — share a JSON snapshot of the whole session / 导出并分享会话存档——分享本次会话完整 JSON 快照\nClear disk — wipe persisted rows so the next launch replays nothing / 清空磁盘——清空持久化数据,下次启动干净\nClear disk & lists — wipe disk and the in-memory lists together / 同时清空磁盘与列表——连同内存列表一并清空","fpsservice--fps-监控服务#FpsService / FPS 监控服务":"Available since v1.2.0 / v1.2.0 起可用\nSingleton service for FPS monitoring, extends ChangeNotifier.FPS 监控单例服务,继承 ChangeNotifier。\nFpsService.instance.start();\nFpsService.instance.stop();\nFpsService.instance.clear();\nfinal fps = FpsService.instance.currentFps;\nfinal jankRate = FpsService.instance.jankRate;\nMethod\tDescription\tstart()\tStart FPS monitoring / 开始 FPS 监控\tstop()\tStop FPS monitoring / 停止 FPS 监控\tclear()\tClear all historical data and counters / 清空所有历史数据和计数器\t\nProperty\tType\tDescription\tisRunning\tbool\tWhether monitoring is currently active / 是否正在监控\tcurrentFps\tdouble\tCurrent FPS (updated every 500ms) / 当前 FPS(每 500ms 更新)\tjankRate\tdouble\tJank rate as percentage / 卡顿率(百分比)\ttotalFrameCount\tint\tTotal frames captured / 总帧数\ttotalJankyCount\tint\tTotal janky frames (>16ms) / 总卡顿帧数(>16ms)\tlastFrameJanky\tbool\tWhether the most recent frame was janky / 最近一帧是否卡顿\tfpsHistory\tList\tRecent 60 FPS values (unmodifiable) / 最近 60 个 FPS 值(不可变)\tframeRecords\tList\tRecent frame records (unmodifiable, up to 3600) / 最近帧记录(不可变,最多 3600 条)\t\nSee FPS Viewer for full feature details.完整功能详情见 FPS Viewer。"}},"/Custom-Database-Provider":{"title":"Custom Database Provider / 自定义数据库提供者","data":{"overview--概述#Overview / 概述":"Zero Inspector Kit supports extending database inspection beyond SQLite through the DatabaseProvider interface.Zero Inspector Kit 通过 DatabaseProvider 接口支持扩展数据库检查功能到 SQLite 以外的数据库。","databaseprovider-interface--接口定义#DatabaseProvider Interface / 接口定义":"abstract class DatabaseProvider {\n /// Provider name / 提供者名称\n String get name;\n /// Get list of databases / 获取数据库列表\n Future> getDatabases();\n /// Query table data / 查询表数据\n Future queryTable(String dbPath, String tableName, {int limit = 50});\n}","data-models--数据模型#Data Models / 数据模型":"","databaseinfo#DatabaseInfo":"Field\tType\tDescription\tname\tString\tDatabase name / 数据库名称\tpath\tString\tDatabase file path / 数据库文件路径\ttables\tList\tList of tables / 表列表","tableinfo#TableInfo":"Field\tType\tDescription\tname\tString\tTable name / 表名\trowCount\tint\tRow count / 行数","queryresult#QueryResult":"Field\tType\tDescription\tcolumns\tList\tColumn names / 列名\trows\tList>\tRow data / 行数据","example-custom-provider--示例自定义提供者#Example: Custom Provider / 示例:自定义提供者":"class MyCustomDatabaseProvider implements DatabaseProvider {\n @override\n String get name => 'CustomDB';\n @override\n Future> getDatabases() async {\n // Return your custom databases / 返回你的自定义数据库列表\n return [\n DatabaseInfo(\n name: 'my_database',\n path: '/path/to/my_database',\n tables: [\n TableInfo(name: 'users', rowCount: 100),\n TableInfo(name: 'orders', rowCount: 500),\n ],\n ),\n ];\n }\n @override\n Future queryTable(\n String dbPath,\n String tableName, {\n int limit = 50,\n }) async {\n // Execute query and return results / 执行查询并返回结果\n return QueryResult(\n columns: ['id', 'name', 'value'],\n rows: [\n {'id': 1, 'name': 'item1', 'value': '100'},\n {'id': 2, 'name': 'item2', 'value': '200'},\n ],\n );\n }\n}","register-provider--注册提供者#Register Provider / 注册提供者":"// Register your custom provider / 注册自定义提供者\nDatabaseRegistry.instance.registerProvider(MyCustomDatabaseProvider());","built-in-provider--内置提供者#Built-in Provider / 内置提供者":"The plugin includes SqliteDatabaseProvider which is auto-registered when enableDatabaseScan is true (default).插件内置 SqliteDatabaseProvider,当 enableDatabaseScan 为 true(默认)时自动注册。You can also register it manually:也可以手动注册:\nDatabaseRegistry.instance.registerProvider(SqliteDatabaseProvider());"}},"/Database-Viewer":{"title":"Database Viewer / 数据库查看器","data":{"overview--概述#Overview / 概述":"The Database Viewer inspects SQLite databases in your app with a two-level navigation system.数据库查看器通过双层导航系统检查应用中的 SQLite 数据库。","auto-scan--自动扫描#Auto-Scan / 自动扫描":"The inspector automatically scans the following directories for .db and .sqlite files:检查器自动扫描以下目录中的 .db 和 .sqlite 文件:\ngetApplicationDocumentsDirectory() / 应用文档目录\ngetDatabasesPath() / 数据库目录","two-level-navigation--双层导航#Two-Level Navigation / 双层导航":"","level-1-database-list-global--第一层数据库列表全局#Level 1: Database List (Global) / 第一层:数据库列表(全局)":"Shows all discovered databases / 显示所有发现的数据库\nEach database shows name and table count / 每个数据库显示名称和表数量\nGlobal search: Search database names and table names / 全局搜索:搜索数据库名和表名","level-2-database-detail--第二层数据库详情#Level 2: Database Detail / 第二层:数据库详情":"Click a database to enter its detail view / 点击数据库进入详情视图\nLeft panel: table list with row counts / 左侧面板:表列表(含行数)\nRight panel: table data in DataTable format / 右侧面板:DataTable 格式的表数据\nBack button to return to database list / 返回按钮返回数据库列表\nIn-database search: Search table names AND all column data / 数据库内搜索:搜索表名和所有列数据","search--搜索#Search / 搜索":"Level\tSearch Scope\tGlobal (database list)\tDatabase names, table names / 数据库名、表名\tIn-database\tTable names, all column values in all tables / 表名、所有表的所有列值","search-highlights--搜索高亮#Search Highlights / 搜索高亮":"When searching within a database, matching cell values are highlighted in accent color.在数据库内搜索时,匹配的单元格值以强调色高亮显示。","ui-features--ui-功能#UI Features / UI 功能":"Color-coded table icons / 带颜色的表图标\nRow count badges for each table / 每个表的行数徽章\nSelected table highlighted / 选中表高亮\nHorizontal and vertical scrollable data table / 水平和垂直可滚动的数据表\nRow count display (filtered / total) when searching / 搜索时显示行数(过滤/总数)","custom-database-provider--自定义数据库提供者#Custom Database Provider / 自定义数据库提供者":"For non-SQLite databases, see Custom Database Provider.对于非 SQLite 数据库,请参阅 自定义数据库提供者。"}},"/Errors":{"title":"Errors / 异常聚合查看器","data":{"overview--概述#Overview / 概述":"Available since v1.9.0v1.9.0 起可用\nThe Errors tab surfaces \"the same crash happening repeatedly\". When the inspector is running, ErrorService hooks both FlutterError.onError and PlatformDispatcher.onError (keeping the default red-screen / console behavior for both) and aggregates every exception by type + stack signature. Repeated instances of the same crash merge into one record that shows how many times it occurred and when it was first/last seen — instead of flooding the log with hundreds of identical stack traces.Errors 标签页用来快速发现\"同一处崩溃反复出现\"的问题。检查器运行时,ErrorService 会接管 FlutterError.onError(保留默认的红色报错与控制台行为),把每次异常按类型 + 堆栈签名去重聚合:同一处崩溃的多次发生会合并为一条记录,展示累计次数与首末次时间——而不是用成百上千条相同的堆栈刷屏日志。","what-gets-captured--捕获来源#What Gets Captured / 捕获来源":"Source / 来源\tHow / 方式\tFlutter framework errors / Flutter 框架异常\tHooks FlutterError.onError (default handler kept) / 接管 FlutterError.onError(保留默认处理)\tFramework-boundary & platform-channel errors / 框架边界外与平台通道异常\tPlatformDispatcher.onError (with save/restore) / PlatformDispatcher.onError(接管 + 还原)\tUncaught async errors / 未捕获异步异常\trunZonedGuarded inside runAppWithInspector() / runAppWithInspector() 内部的 runZonedGuarded\tManual reports / 手动上报\tErrorService.instance.report(exception, stackTrace) — for gRPC / custom protocols / your own error paths / 用于 gRPC / 自定义协议或你自己的错误通道\t\nCaptured records are also persisted to disk (see Session Persistence below), so aggregated errors from previous sessions are replayed on the next launch.捕获到的记录也会落盘持久化(见下文会话持久化),下次启动时会回放上次会话的聚合异常。","deduplication--去重规则#Deduplication / 去重规则":"Records are keyed by exception type plus a stack signature (first frames with line/address noise stripped) / 按异常类型 + 堆栈签名(取前若干帧并去掉行号/地址噪声)去重\nA new occurrence of an existing signature bumps its count and updates lastSeen instead of adding a new row / 同签名的再次发生只累加 count 并更新 lastSeen,不新增行\nRing buffer capped at 200 aggregated records (oldest dropped) / 环形缓冲上限 200 条聚合记录(超出丢弃最旧)","ui-features--ui-功能#UI Features / UI 功能":"","errors-tab--errors-标签页#Errors Tab / Errors 标签页":"Lists aggregated errors newest-first, showing the exception type, message, first seen / last seen time, and a ×N badge when the same crash recurred / 按最新在前列出聚合异常,展示异常类型、消息、首次/末次出现时间,同崩溃复发时显示 ×N 徽章\nTap a row to expand the full stack sample (truncated at 8000 chars); tap again to collapse / 点击行展开完整堆栈样本(8000 字符截断);再点收起\nCopy stack per row / 行内复制堆栈\nSearch filters by exception type or message / 搜索按异常类型或消息过滤\nClear empties the aggregated list / 清除清空聚合列表","tab-badge--标签页红点#Tab Badge / 标签页红点":"The Errors tab icon shows a red count badge while there are aggregated errors (capped at 99+), so recurring crashes are visible at a glance / Errors 标签页图标在有聚合异常时显示红色计数(99+ 封顶),一眼可见反复崩溃\nOpening the Errors tab is not required to see the badge — it reflects ErrorService.instance.errorCount whenever the panel is open / 只要面板打开,标签红点即反映 ErrorService.instance.errorCount,无需先进入 Errors 页\nNote: the floating ball itself stays clean — its red count (if any) is the Alerts unread badge (AlertService), not error aggregation.注意:悬浮球本身保持纯粹——球上的红色数字(如有)来自告警未读数(AlertService),与异常聚合无关。","api--接口#API / 接口":"The full service is re-exported from the package root — no need to import lib/src/.完整服务已从包根导出,无需 import lib/src/。\nimport 'package:zero_inspector_kit/zero_inspector_kit.dart';\n// Report an exception manually (gRPC/custom protocol/your own error paths) / 手动上报异常\nErrorService.instance.report(error, stackTrace, 'myModule');\n// Read the aggregated view (newest first) / 读取聚合视图(最新在前)\nfinal ErrorRecord latest = ErrorService.instance.errors.first;\nprint('${latest.type} x${latest.count} — first ${latest.firstSeen} / last ${latest.lastSeen}');\n// Clear all records / 清空所有记录\nErrorService.instance.clear();\nMember\tDescription\terrors\tList — aggregated records, newest first / 聚合记录,最新在前\terrorCount\tint — number of aggregated records / 聚合记录条数\tisEnabled\tbool — capture toggle (programmatic) / 抓取开关(代码控制)\treport(exception, stack, [context])\tManually report an exception / 手动上报异常\trestore(records)\tRestore persisted records (replay on launch; existing dedup ids are skipped) / 恢复持久化记录(启动回放;已存在的去重 id 跳过)\tclear()\tClear all aggregated records / 清空全部记录\tinstall() / uninstall()\tHook / restore FlutterError.onError and PlatformDispatcher.onError / 接管 / 还原 FlutterError.onError 与 PlatformDispatcher.onError","errorrecord--异常记录#ErrorRecord / 异常记录":"Field\tDescription\ttype\tException type name (e.g. _TypeError) / 异常类型名\tmessage\tException message / 异常消息\tcount\tTotal occurrences / 累计出现次数\tfirstSeen / lastSeen\tFirst / last occurrence time / 首次 / 末次出现时间\tsampleStack\tOne full stack sample (may be truncated) / 一条完整堆栈样本(可能截断)","session-persistence--会话持久化#Session Persistence / 会话持久化":"Errors — together with logs, network requests and alerts — are asynchronously flushed to a local SQLite ring buffer (zero_inspector_kit.db). On the next launch, logs, aggregated errors and alerts replay into their tabs, so a crash you saw yesterday is still inspectable today even though the panel was never opened; network requests stay archived on disk for later export. Use the storage icon in the panel header to open the Persisted data manager: see the current row counts, export the full session archive as JSON, or clear the disk. See Configuration (PersistenceService section) for details and the tuning parameters.异常与日志、网络请求、告警一起被异步写入本地 SQLite 环形缓冲(zero_inspector_kit.db)。下次启动时,日志、聚合异常与告警会回放入各自标签页——即使昨天从没打开过面板,今天依然能复盘当时的崩溃现场;网络请求保留在磁盘存档,供之后导出。点击面板头部的存储图标可打开 Persisted data 管理弹层:查看当前行数、导出完整会话存档 JSON,或清空磁盘。详见 Configuration(PersistenceService 一节)的参数说明。","enable--disable--开关#Enable / Disable / 开关":"enableErrorCapture (default true) in ZeroInspectorKit.init() controls error aggregation / init() 的 enableErrorCapture(默认 true)控制异常聚合\nenablePersistence (default true) controls the disk ring buffer / enablePersistence(默认 true)控制磁盘环形缓冲\nSet both to false to keep everything in memory only / 两者都设为 false 时数据仅保留在内存\nZeroInspectorKit.init(\n enableErrorCapture: true,\n enablePersistence: true, // false → memory-only / 关闭后仅内存\n);","related--相关#Related / 相关":"Log Viewer — raw log stream including error lines / 原始日志流(含错误行)\nConfiguration — init parameters and full service APIs / 初始化参数与完整服务接口\nUsage — general usage guide / 使用指南"}},"/FAQ":{"title":"FAQ / 常见问题","data":{"general--通用#General / 通用":"","q-does-the-inspector-affect-production-builds--检查器会影响生产构建吗#Q: Does the inspector affect production builds? / 检查器会影响生产构建吗?":"A: No. The inspector is automatically disabled in release mode via kReleaseMode. Flutter's tree-shaking removes all inspector-related code from production builds. You don't need to remove any code.不会。 检查器在 release 模式下通过 kReleaseMode 自动禁用。Flutter 的 tree-shaking 会移除所有检查器相关代码,无需手动移除。","q-what-platforms-are-supported--支持哪些平台#Q: What platforms are supported? / 支持哪些平台?":"A: Android and iOS.支持 Android 和 iOS。","q-what-flutterdart-versions-are-required--需要什么-flutterdart-版本#Q: What Flutter/Dart versions are required? / 需要什么 Flutter/Dart 版本?":"A: Flutter >= 3.3.0, Dart SDK >= 3.11.0 < 4.0.0.Flutter >= 3.3.0,Dart SDK >= 3.11.0 < 4.0.0。","network--网络#Network / 网络":"","q-do-i-need-to-manually-add-interceptors-for-dio--需要为-dio-手动添加拦截器吗#Q: Do I need to manually add interceptors for Dio? / 需要为 Dio 手动添加拦截器吗?":"A: No. Dio uses IOHttpClientAdapter internally, which uses dart:io's HttpClient. The inspector captures all requests via HttpOverrides automatically, making it truly zero-invasion for both http package and Dio.不需要。 Dio 内部使用 IOHttpClientAdapter,底层使用 dart:io 的 HttpClient。检查器通过 HttpOverrides 自动捕获所有请求,对 http 包和 Dio 都是真正的零侵入。","q-why-do-i-see-duplicate-dio-requests--为什么看到重复的-dio-请求#Q: Why do I see duplicate Dio requests? / 为什么看到重复的 Dio 请求?":"A: If you use both InspectorDioInterceptor and the auto-capture (HttpOverrides), Dio requests will be recorded twice. Simply remove the manual InspectorDioInterceptor — auto-capture handles everything.如果同时使用了 InspectorDioInterceptor 和自动捕获(HttpOverrides),Dio 请求会被记录两次。移除手动 InspectorDioInterceptor 即可,自动捕获会处理一切。","q-can-i-modify-requests-during-testing--测试时可以修改请求吗#Q: Can I modify requests during testing? / 测试时可以修改请求吗?":"A: Yes (since v1.0.7). The inspector supports intercepting and modifying requests via rules. Open a request detail and tap the Interceptor icon to configure a rule. You can modify the request body and request headers only — response fields (status code, response body) are read-only (since v1.0.8). GET requests cannot be modified (no request body). The master toggle is on the Network list page; rules only apply when modification mode is enabled.可以(v1.0.7 起)。检查器支持通过规则拦截并修改请求。打开请求详情,点击拦截器图标配置规则。仅可修改请求体和请求头——响应字段(状态码、响应体)只读(v1.0.8 起)。GET 请求不可修改(无请求体)。总开关在 Network 列表页,规则仅在启用修改模式时生效。See Network Inspector > Request Interceptor for details.详见 Network Inspector > 请求拦截修改。","logging--日志#Logging / 日志":"","q-can-i-use-my-existing-logging-library--可以使用现有的日志库吗#Q: Can I use my existing logging library? / 可以使用现有的日志库吗?":"A: Yes! The inspector automatically captures logs from any library that uses print() or debugPrint(). No configuration needed. Third-party logs are categorized as Info level.可以! 检查器会自动捕获任何使用 print() 或 debugPrint() 的日志库的日志,无需配置。第三方日志统一归类为 Info 级别。","q-how-to-sync-inspector-logs-to-my-logger--如何将检查器日志同步到我的日志库#Q: How to sync inspector logs to my logger? / 如何将检查器日志同步到我的日志库?":"A: Use the onLogCaptured callback:使用 onLogCaptured 回调:\nInspectorLogInterceptor.instance.onLogCaptured = (entry) {\n yourLogger.log(entry.message);\n};\nWarning: Do NOT call print() or logging methods inside this callback, as it will cause infinite recursion.\n警告:不要在此回调中调用 print() 或日志方法,否则会导致无限递归。","database--数据库#Database / 数据库":"","q-why-dont-i-see-any-databases--为什么看不到数据库#Q: Why don't I see any databases? / 为什么看不到数据库?":"A: The inspector scans getApplicationDocumentsDirectory() and getDatabasesPath() for .db and .sqlite files. If your database is stored elsewhere, you can implement a custom DatabaseProvider.检查器扫描 getApplicationDocumentsDirectory() 和 getDatabasesPath() 目录中的 .db 和 .sqlite 文件。如果你的数据库存储在其他位置,可以实现自定义 DatabaseProvider。","memory--内存#Memory / 内存":"","q-why-does-dart-heap-show-na-when-debugging-via-pc--通过-pc-调试时为什么-dart-heap-显示-na#Q: Why does Dart Heap show \"N/A\" when debugging via PC? / 通过 PC 调试时为什么 Dart Heap 显示 \"N/A\"?":"A: This is an expected behavior. When using flutter run to debug via PC, the flutter tool sets up port forwarding between PC and device via adb reverse, allowing PC-side DevTools to access the device's VM Service. However, Service.getInfo() returns a serverUri from the PC's perspective; when the app process internally accesses 127.0.0.1:PC_port, the device doesn't have that port listening locally, resulting in Connection refused and VM Service showing OFF.这是预期行为。 使用 flutter run 连接 PC 调试时,flutter tool 会通过 adb reverse 在 PC 和设备之间做端口转发,让 PC 上的 DevTools 能访问设备的 VM Service。但应用进程内部 Service.getInfo() 返回的 serverUri 是 PC 视角的端口,应用进程访问 127.0.0.1:PC端口 时设备本地并没有监听该端口,导致 Connection refused,VM Service 显示 OFF。Workarounds / 解决方案:\nOpen the debug app directly without PC connection — VM Service works normally / 直接打开 debug 应用(不连 PC),VM Service 正常工作\nUse Native memory data (Android PSS / iOS physicalFootprint) as fallback — always available / 使用 Native 内存数据(Android PSS / iOS physicalFootprint)作为降级,始终可用\nProcess RSS is always available regardless of VM Service / 进程 RSS 无论 VM Service 状态都可用\nSee Memory Viewer > VM Service Availability for details.详见 Memory Viewer > VM Service 可用性。","q-does-memory-monitoring-affect-performance--内存监控会影响性能吗#Q: Does memory monitoring affect performance? / 内存监控会影响性能吗?":"A: Memory monitoring is off by default (since v1.1.0). When disabled, all timers are stopped and VM Service connection is cleared, leaving zero overhead. When enabled, refresh intervals are optimized:内存监控默认关闭(v1.1.0 起)。关闭时所有定时器停止、VM Service 连接清空,零开销。开启时刷新间隔已优化:\nProcess RSS / Dart Heap: 500ms\nNative Memory: 3000ms\nStorage Stats: 3000ms\nLeak Detection: 2000ms\nYou can toggle the switch at the top of the Memory panel anytime.可随时在 Memory 面板顶部切换开关。","q-does-leak-detection-require-code-modification--泄漏检测需要侵入代码吗#Q: Does leak detection require code modification? / 泄漏检测需要侵入代码吗?":"A: The current implementation uses WeakReference + Finalizer, which requires calling trackObject() to register objects for tracking. This is a mild invasion (one line of code per tracked object).当前实现使用 WeakReference + Finalizer,需要调用 trackObject() 注册要追踪的对象。这是轻度侵入(每个追踪对象一行代码)。Trade-offs / 取舍:\nPros: 100% reliable, doesn't depend on VM Service, works in release mode / 100% 可靠,不依赖 VM Service,release 模式也可用\nCons: Requires user to know which objects to track / 需要用户知道要追踪哪些对象\nA zero-invasion Heap Snapshot comparison feature (based on VM Service) is technically possible but would strongly depend on VM Service availability (unavailable when debugging via PC), with significant performance overhead. Not currently implemented.零侵入的 Heap Snapshot 对比功能(基于 VM Service)技术上可行,但会强依赖 VM Service 可用性(PC 调试时不可用),且有显著性能开销。目前未实现。See Memory Viewer > Memory Leak Detection for API details.详见 Memory Viewer > 内存泄漏检测 了解 API 详情。","fps--帧率#FPS / 帧率":"","q-why-does-fps-show-a-very-low-value-eg-9-10-fps--为什么-fps-显示很低如-9-10#Q: Why does FPS show a very low value (e.g. 9-10 FPS)? / 为什么 FPS 显示很低(如 9-10)?":"A: This was a bug fixed in v1.2.0. The root cause was that _recentFrameTimestamps.add(now) was placed outside the for-loop in _onFrameTimings; since Flutter engine's addTimingsCallback is batched (may return multiple frames per call), only one timestamp was recorded per batch, undercounting FPS by 6-10x. Upgrade to ^1.2.0 to fix this.这是 v1.2.0 已修复的 bug。根因是 _onFrameTimings 中 _recentFrameTimestamps.add(now) 在 for 循环外;Flutter 引擎的 addTimingsCallback 是批量回调(一次可能返回多帧),但每批只记录 1 个时间戳,导致 FPS 计算偏低 6-10 倍。升级到 ^1.2.0 即可修复。See FPS Viewer for details.详见 FPS Viewer。","q-does-fps-monitoring-affect-performance--fps-监控会影响性能吗#Q: Does FPS monitoring affect performance? / FPS 监控会影响性能吗?":"A: FPS monitoring is off by default (since v1.2.0). When disabled, no frame timings callbacks and no timers — zero overhead. When enabled, it only processes lightweight frame timing data with bounded history (60 trend points, up to 3600 frame records). You can toggle the switch at the top of the FPS panel anytime.FPS 监控默认关闭(v1.2.0 起)。关闭时无帧回调、无定时器——零开销。开启时仅处理轻量帧时序数据,历史有界(趋势 60 个点,帧记录最多 3600 条)。可随时在 FPS 面板顶部切换开关。","ui--界面#UI / 界面":"","q-the-inspector-panel-gets-pushed-up-when-keyboard-appears--键盘弹出时检查器面板被顶起来了#Q: The inspector panel gets pushed up when keyboard appears. / 键盘弹出时检查器面板被顶起来了。":"A: This was fixed in v1.0.6. The floating button and panel are rendered via Overlay, which is independent of the page layout and not affected by keyboard.此问题已在 v1.0.6 修复。悬浮按钮和面板通过 Overlay 渲染,独立于页面布局,不受键盘影响。","q-how-to-use-search--如何使用搜索#Q: How to use search? / 如何使用搜索?":"A: Each viewer has its own search bar at the top. Database viewer has two-level search: global search (database list) and in-database search (table names + all column data).每个查看器顶部都有搜索栏。数据库查看器有双层搜索:全局搜索(数据库列表)和数据库内搜索(表名 + 所有列数据)。","q-the-floating-button-disappeared--cant-be-opened-after-enabling-fps--开启-fps-后悬浮按钮消失无法打开#Q: The floating button disappeared / can't be opened after enabling FPS. / 开启 FPS 后悬浮按钮消失/无法打开。":"A: This was a bug fixed in v1.2.0. The root cause was that FpsService.notifyListeners() triggered Overlay rebuild; combined with the original Overlay lookup failure (using Overlay.of(context, rootOverlay: true) which returned null), the button State was destroyed and could not recover. Upgrade to ^1.2.0 which uses navigatorState.overlay instead.这是 v1.2.0 已修复的 bug。根因是 FpsService.notifyListeners() 触发 Overlay 重建,叠加原 Overlay 查找失败(使用 Overlay.of(context, rootOverlay: true) 返回 null),导致按钮 State 被销毁且无法恢复。升级到 ^1.2.0,改用 navigatorState.overlay 即可。","q-how-does-the-floating-button-edge-docking-work--悬浮按钮的边缘吸附怎么用#Q: How does the floating button edge docking work? / 悬浮按钮的边缘吸附怎么用?":"A: Since v1.2.0, when you drag the button near a screen edge and release, it auto-docks and tucks into the edge, leaving only a 24px peek visible. The icon becomes a directional chevron hinting you can tap to pull it out:\nTap the peek → smoothly pulls out to fully visible (panel NOT opened, avoids accidental open) / 平滑拉出到完全可见(不打开面板,避免误触)\nTap when fully visible → opens the inspector panel / 打开检查器面板\nThis design avoids conflicts with system back gestures (Android/iOS edge swipe to go back) when pulling out from the docked state.v1.2.0 起,拖动按钮靠近屏幕边缘松手即自动吸附并\"收入\"边缘,仅露 24px。图标变为方向箭头,提示可点击拉出:\n点击露出部分 → 平滑拉出到完全可见(不打开面板,避免误触)\n完全可见时点击 → 打开检查器面板\n此设计避免了从吸附态拖出时与系统返回手势(Android/iOS 边缘右滑退出)的冲突。See Usage > Edge Docking for details.详见 Usage > 边缘吸附。","license--许可证#License / 许可证":"","q-can-i-use-this-in-a-commercial-project--可以在商业项目中使用吗#Q: Can I use this in a commercial project? / 可以在商业项目中使用吗?":"A: Yes. This project is licensed under MPL-2.0. You can use it in closed-source commercial apps — if you use it unmodified, no source disclosure is required. If you modify the plugin's source files, those modified files must be published in source form under MPL-2.0 (your own app still does not need to be open-sourced).可以。本项目采用 MPL-2.0 许可证,可用于闭源商业 App——未修改直接使用时无需公开任何源码;若修改了插件源文件,被修改的文件必须以 MPL-2.0 公开源码(你自己的 App 仍无需开源)。","q-are-you-liable-for-issues-in-modified-versions--修改版出问题你们负责吗#Q: Are you liable for issues in modified versions? / 修改版出问题你们负责吗?":"A: This plugin is provided \"as is\", without warranty of any kind. The author assumes no responsibility or liability for the functionality, security, or any consequences arising from the use of modified versions or derivative projects.本插件按\"原样\"提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及任何使用后果承担责任。"}},"/FPS-Viewer":{"title":"FPS Viewer / FPS 监控面板","data":{"":"The FPS Viewer provides real-time frame performance analysis, including current FPS, jank rate, frame duration stats, and an FPS trend chart.FPS 监控面板提供实时帧性能分析,包括当前 FPS、卡顿率、帧耗时统计和 FPS 趋势图。\nAvailable since v1.2.0v1.2.0 起可用","overview--概览#Overview / 概览":"The FPS Viewer is integrated into the inspector panel. Tap the floating inspector button → \"FPS\" tab to access.FPS 监控面板集成在检查器面板中。点击悬浮检查器按钮 → \"FPS\" 标签即可访问。⚠️ Important: FPS monitoring is off by default. You must toggle on the switch at the top of the panel to start collecting data.⚠️ 重要:FPS 监控默认关闭。必须在面板顶部打开开关才会开始采集数据。","master-switch--总开关#Master Switch / 总开关":"A switch at the top of the FPS panel controls whether monitoring is enabled.FPS 面板顶部有一个开关,控制是否启用监控。\nState\tBehavior\tOFF (default)\tNo frame timings callbacks, no timer, zero overhead / 无帧回调、无定时器、零开销\tON\tStarts collecting frame data via WidgetsBinding.instance.addTimingsCallback / 通过 addTimingsCallback 开始采集帧数据\t\nWhen turned off, all callbacks and timers are cancelled, leaving no residual overhead.关闭时所有回调和定时器会被取消,不会残留开销。","features--功能#Features / 功能":"","1-current-stats--当前统计#1. Current Stats / 当前统计":"Current FPS — Updated every 500ms / 每 500ms 更新\nJank Rate — Percentage of janky frames (duration exceeds the per-frame budget, derived from the display refresh rate) / 卡顿帧占比(帧耗时超过由屏幕刷新率换算的逐帧预算)\nTotal Frame Count — All frames captured since start / 自启动以来的总帧数\nTotal Janky Count — All janky frames captured / 自启动以来的总卡顿帧数\nLast Frame Janky — Whether the most recent frame was janky / 最近一帧是否卡顿\n显示当前 FPS、卡顿率、总帧数、总卡顿帧数、最近一帧是否卡顿。","2-fps-trend-chart--fps-趋势图#2. FPS Trend Chart / FPS 趋势图":"Real-time line chart with 30-second history window (60 data points × 500ms)\nY-axis dynamically scales to max(60, maxFps) to avoid overflow when FPS spikes\nJanky frame region marked in red\nTime axis labels: -30s / -15s / Now\n实时折线图,30 秒历史窗口(60 个数据点 × 500ms)。Y 轴动态缩放为 max(60, maxFps) 避免 FPS 飙升时溢出。卡顿区域以红色标记。","3-janky-frame-list--卡顿帧列表#3. Janky Frame List / 卡顿帧列表":"Lists janky frames — duration exceeds the per-frame budget, derived from the display refresh rate (~16.7ms at 60Hz, ~8.3ms at 120Hz) / 列出卡顿帧——帧耗时超过由屏幕刷新率换算的逐帧预算(60Hz≈16.7ms、120Hz≈8.3ms)\nEach item shows frame duration and timestamp, plus the build / raster split (buildDurationUs + rasterDurationUs) so you can tell whether build or GPU raster is the bottleneck / 每项显示帧耗时、时间戳,以及 build / raster 分项(buildDurationUs + rasterDurationUs),便于判断是构建还是 GPU 光栅化卡顿\nHelps identify specific jank spikes / 帮助定位具体卡顿点\n列出卡顿帧(帧耗时超过由屏幕刷新率换算的逐帧预算,60Hz≈16.7ms、120Hz≈8.3ms),每项显示帧耗时、时间戳与 build / raster 分项,帮助定位具体卡顿点。","4-reset--重置#4. Reset / 重置":"\"Reset\" button clears all statistics and historical data / \"Reset\" 按钮清空所有统计和历史数据\nUseful for measuring a specific interaction scenario / 适合测量特定交互场景\n\"Reset\" 按钮清空所有统计和历史数据,适合测量特定交互场景。","how-it-works--工作原理#How It Works / 工作原理":"FPS monitoring uses Flutter's WidgetsBinding.instance.addTimingsCallback to receive frame timing information from the engine. The callback is batched — it may return multiple FrameTiming objects per call, so each frame is recorded individually inside the loop to ensure accurate FPS calculation.FPS 监控使用 Flutter 的 WidgetsBinding.instance.addTimingsCallback 接收引擎的帧时序信息。该回调是批量的——每次调用可能返回多个 FrameTiming 对象,因此在循环内部为每帧单独记录时间戳,确保 FPS 计算准确。Jank threshold / 卡顿阈值: A frame is janky when its total duration (build + raster) exceeds the per-frame budget — 1000ms ÷ refreshRate (≈16.7ms at 60Hz, ≈8.3ms at 120Hz). Since v1.11.0 the budget is derived from the device's refresh rate, so high-refresh (120Hz) screens are held to a tight ≈8.3ms instead of being forgiven by a fixed 16ms.卡顿阈值:帧的总耗时(build + raster)超过逐帧预算即视为卡顿——1000ms ÷ 刷新率(60Hz≈16.7ms、120Hz≈8.3ms)。自 v1.11.0 起预算按设备刷新率换算,高刷(120Hz)屏会被严格约束在 ≈8.3ms,而非被固定 16ms 放过。Phased timing / 分阶段耗时: Each FrameRecord carries buildDurationUs and rasterDurationUs (GPU raster); the janky-frame list shows the build / raster split per row.分阶段耗时:每条 FrameRecord 都带有 buildDurationUs 与 rasterDurationUs(GPU 光栅化),掉帧列表项会展示 build / raster 分项。","programmatic-control-optional--编程式控制可选#Programmatic Control (Optional) / 编程式控制(可选)":"If you need to start/stop monitoring from code (e.g., for automated testing), use FpsService:如需从代码控制监控(如自动化测试),使用 FpsService:\n// Start / Stop FPS monitoring / 开始 / 停止 FPS 监控\nFpsService.instance.start();\nFpsService.instance.stop();\n// Read current stats / 读取当前统计\nfinal fps = FpsService.instance.currentFps;\nfinal jankRate = FpsService.instance.jankRate;\nfinal totalFrames = FpsService.instance.totalFrameCount;\nfinal jankyFrames = FpsService.instance.totalJankyCount;\n// Clear historical data / 清空历史数据\nFpsService.instance.clear();\n// Listen to updates / 监听更新\nFpsService.instance.addListener(() {\n // Update your own UI / 更新你自己的 UI\n});\n// Historical data access / 历史数据访问\nfinal history = FpsService.instance.fpsHistory; // List, 60 entries / 60 条\nfinal records = FpsService.instance.frameRecords; // List, unmodifiable / 不可变,最多 3600 条","fpsservice-api--fpsservice-接口#FpsService API / FpsService 接口":"Singleton service extending ChangeNotifier.继承 ChangeNotifier 的单例服务。\nMethod\tDescription\tstart()\tStart FPS monitoring / 开始 FPS 监控\tstop()\tStop FPS monitoring / 停止 FPS 监控\tclear()\tClear all historical data and counters / 清空所有历史数据和计数器\t\nProperty\tType\tDescription\tisRunning\tbool\tWhether monitoring is currently active / 是否正在监控\tcurrentFps\tdouble\tCurrent FPS (updated every 500ms) / 当前 FPS(每 500ms 更新)\tjankRate\tdouble\tJank rate as percentage / 卡顿率(百分比)\ttotalFrameCount\tint\tTotal frames captured / 总帧数\ttotalJankyCount\tint\tTotal janky frames captured (per-frame budget) / 总卡顿帧数(按逐帧预算)\tlastFrameJanky\tbool\tWhether the most recent frame was janky / 最近一帧是否卡顿\tfpsHistory\tList\tRecent 60 FPS values (unmodifiable) / 最近 60 个 FPS 值(不可变)\tframeRecords\tList\tRecent frame records (unmodifiable, up to 3600) / 最近帧记录(不可变,最多 3600 条)","performance-considerations--性能说明#Performance Considerations / 性能说明":"Off by default / 默认关闭: Zero overhead when disabled / 关闭时零开销\nLightweight callbacks / 轻量回调: Only processes frame timing data, no heavy computation / 仅处理帧时序数据,无重计算\nBounded history / 有界历史: Trend chart keeps only 60 points, frame records capped at 3600 / 趋势图仅保留 60 个点,帧记录上限 3600 条\nSafe with other features / 与其他功能兼容: Can run alongside Memory Viewer monitoring / 可与 Memory Viewer 监控同时运行","platform-support--平台支持#Platform Support / 平台支持":"Feature\tAndroid\tiOS\tFPS Monitoring\t✅\t✅\tJank Detection\t✅\t✅\tTrend Chart\t✅\t✅\t\nFPS monitoring uses Flutter engine APIs and works on Android and iOS (the two platforms this plugin targets).FPS 监控使用 Flutter 引擎 API,在本插件支持的两个平台 Android 与 iOS 上均可用。","demo--演示#Demo / 演示":"The Example App includes an FPS demo module with three scenarios:Example App 包含 FPS 演示模块,提供三种场景:\nDemo\tDescription\tTrigger Jank\tBlocks the main thread for 100-500ms to simulate jank / 阻塞主线程 100-500ms 模拟卡顿\tHeavy Animations\t80 simultaneously rotating+scaling widgets to intentionally trigger jank / 80 个同时旋转+缩放的 widget 故意触发卡顿\tSmooth Animation\tSingle lightweight compound animation, demonstrates stable 60 FPS / 单个轻量复合动画,演示稳定 60 FPS","related--相关#Related / 相关":"Usage — General usage guide / 通用使用指南\nFAQ — Common questions / 常见问题\nConfiguration — Configuration options / 配置选项"}},"/Getting-Started":{"title":"Getting Started / 快速开始","data":{"quick-start--快速开始#Quick Start / 快速开始":"Integrate with just 1 line of code:仅需 1 行代码 即可完成集成:\nimport 'package:flutter/material.dart';\nimport 'package:zero_inspector_kit/zero_inspector_kit.dart';\nvoid main() {\n // Single line: Initialize inspector, capture print() via Zone, and display floating button\n // 一行代码:初始化检查器、通过 Zone 捕获 print()、自动显示悬浮按钮\n ZeroInspectorKit.runAppWithInspector(const MyApp());\n}\nclass MyApp extends StatelessWidget {\n const MyApp({super.key});\n @override\n Widget build(BuildContext context) {\n return MaterialApp(\n home: Scaffold(\n appBar: AppBar(title: const Text('App')),\n body: const Center(child: Text('Hello World')),\n ),\n );\n }\n}","what-happens-automatically--自动完成的工作#What Happens Automatically / 自动完成的工作":"After integration, the inspector automatically does the following without modifying any other project code:集成后,检查器会自动完成以下工作,无需修改项目其他代码:\nFeature\tDescription\t✅ Log Capture\tAuto-capture all print(), debugPrint() via Zone / 通过 Zone 自动捕获所有日志\t✅ Network Interception\tAuto-intercept all http and Dio requests via HttpOverrides / 自动拦截所有网络请求\t✅ Error Capture\tAggregate Flutter framework errors & unhandled exceptions, deduped by type + stack (Errors tab) / 聚合 Flutter 框架异常与未捕获异常,按类型+堆栈去重\t✅ Session Persistence\tFlush logs/network/errors to a disk ring buffer; logs & errors replay on next launch / 日志/网络/异常落盘环形缓冲,启动时回放日志与异常\t✅ Database Scan\tAuto-scan and register SQLite databases / 自动扫描注册数据库\t✅ Floating Button\tAuto-displayed via Overlay, not affected by keyboard / 通过 Overlay 自动显示\t✅ Route Tracking\tAuto-inject InspectorRouteObserver into MaterialApp / 自动注入路由观察者\t\nNote / 说明: The Memory and FPS panels are available as tabs but are off by default to avoid performance overhead. Toggle the switch at the top of each panel to start collecting data. See Memory Viewer and FPS Viewer for details.Memory 和 FPS 面板作为标签页可用,但默认关闭以避免性能开销。在各自面板顶部打开开关才会开始采集数据。详见 Memory Viewer 和 FPS Viewer。","production-build--生产构建#Production Build / 生产构建":"The inspector is automatically disabled in release mode. You don't need to remove any code — Flutter's tree-shaking will remove all inspector-related code from production builds.检查器在 release 模式下会自动禁用,无需移除任何代码,Flutter 的 tree-shaking 会自动移除所有检查器相关代码。","alternative-integration--替代集成方式#Alternative Integration / 替代集成方式":"If you prefer more control, use the two-line approach:如果需要更多控制权,可以使用两行代码方式:\nvoid main() {\n ZeroInspectorKit.init();\n runApp(ZeroInspectorKit.wrapApp(const MyApp()));\n}","next-steps--下一步#Next Steps / 下一步":"Installation — Detailed installation methods / 详细安装方式\nUsage — Full usage guide / 完整使用指南\nConfiguration — Configuration options / 配置选项"}},"/Installation":{"title":"Installation / 安装","data":{"from-pubdev-recommended--从-pubdev-安装推荐#From pub.dev (Recommended) / 从 pub.dev 安装(推荐)":"Add the following to your pubspec.yaml:在 pubspec.yaml 中添加以下依赖:\ndependencies:\n zero_inspector_kit: ^1.11.1\nThen run:然后运行:\nflutter pub get","from-github--从-github-安装#From GitHub / 从 GitHub 安装":"Alternatively, install from GitHub:或者从 GitHub 安装:\ndependencies:\n zero_inspector_kit:\n git:\n url: https://github.com/zero-labsco/zero_inspector_kit.git\n ref: release/v1.11.1","platform-setup--平台配置#Platform Setup / 平台配置":"","android#Android":"No additional configuration needed.无需额外配置。","ios#iOS":"No additional configuration needed.无需额外配置。","import--导入#Import / 导入":"import 'package:zero_inspector_kit/zero_inspector_kit.dart';","requirements--环境要求#Requirements / 环境要求":"Requirement\tVersion\tFlutter\t>= 3.3.0\tDart SDK\t>= 3.11.0 < 4.0.0","next-steps--下一步#Next Steps / 下一步":"Getting Started — Quick start guide / 快速开始\nUsage — Full usage guide / 完整使用指南"}},"/Log-Viewer":{"title":"Log Viewer / 日志查看器","data":{"overview--概述#Overview / 概述":"The Log Viewer automatically captures logs from multiple sources with zero configuration.日志查看器自动从多个来源捕获日志,无需配置。","log-sources--日志来源#Log Sources / 日志来源":"Source\tCapture Method\tprint()\tZone specification override / Zone 规范覆盖\tdebugPrint()\tdebugPrint override / debugPrint 覆盖\tFlutter errors\tFlutterError.onError hook / 接管 FlutterError.onError\tUnhandled exceptions\trunZonedGuarded / runZonedGuarded 捕获\tThird-party libraries\tVia print() capture / 通过 print() 捕获\t\nSince v1.9.0, Flutter framework errors and unhandled exceptions are also aggregated & deduplicated in the dedicated Errors tab (keyed by type + stack signature) — while this Log Viewer keeps showing them as error-level lines in the chronological stream. Use Errors for \"is this crash repeating?\"; use Log Viewer for the raw timeline.自 v1.9.0 起,Flutter 框架异常与未捕获异常会同时进入独立的 Errors 标签页去重聚合(按类型 + 堆栈签名归并);本 Log Viewer 仍会在原始时间流中把它们显示为错误级日志。回答\"该崩溃是否反复出现\"请用 Errors;查看原始时间线请用 Log Viewer。","log-levels--日志级别#Log Levels / 日志级别":"Level\tAbbreviation\tColor\tDescription\tVerbose\tV\tGray\tDetailed information / 详细信息\tDebug\tD\tBlue\tDebug information / 调试信息\tInfo\tI\tGreen\tGeneral information / 一般信息\tWarning\tW\tOrange\tWarning messages / 警告信息\tError\tE\tRed\tError messages / 错误信息\t\nThird-party library logs are categorized as Info level.\n第三方日志库的日志统一归类为 Info 级别。","ui-features--ui-功能#UI Features / UI 功能":"","filter-bar--过滤栏#Filter Bar / 过滤栏":"All: Show all logs / 使用 All 显示所有日志\nV / D / I / W / E: Filter by level / 按级别过滤\nTag dropdown: Filter by any captured tag / 按任意已捕获标签过滤\nSingle-select mode / 单选模式","toolbar--工具栏#Toolbar / 工具栏":"Auto-scroll toggle (default on): Jump to the newest log as new entries arrive; pause to keep history still for inspection / 自动滚动开关(默认开启):新日志到达时自动跳到最新;暂停可稳定查看历史\nCopy as JSON: Copy all currently filtered logs as JSON / 复制为 JSON:将当前过滤后的全部日志复制为 JSON\nShare as Text: Share filtered logs as plain text / 分享为文本:将过滤后的日志以纯文本分享\nClear: Clear all logs / 清除:清空全部日志","log-list--日志列表#Log List / 日志列表":"Level badge with color / 带颜色的级别徽章\nTimestamp display / 时间戳显示\nTag display (if available) / 标签显示(如有)\nError/warning rows have subtle background tint / 错误/警告行有淡色背景\nLeft border color indicates level / 左侧边框颜色表示级别\nTap a row to open the in-view detail page with full message and copy; use the back button to return / 点击行进入详情页(主视图内切换),可查看完整消息并复制,点返回按钮回到列表\nPer-row copy button copies that single log as JSON / 行内复制按钮将该条日志以 JSON 复制","search--搜索#Search / 搜索":"Fuzzy search by message content or tag / 按消息内容或标签模糊搜索\nToggle regex mode (. * button) to search with RegExp (case-insensitive); invalid patterns degrade gracefully instead of crashing / 点击 正则模式(. * 按钮)使用 RegExp 搜索(不区分大小写);非法图案优雅降级,不会崩溃\nCombined with level and tag filters / 可与级别、标签过滤组合使用","manual-logging--手动记录日志#Manual Logging / 手动记录日志":"Quick shorthand (recommended) / 简化写法(推荐) — available since v1.1.2 / v1.1.2 起可用:\nInspectorLog.v('Verbose message / 详细消息');\nInspectorLog.d('Debug message / 调试消息');\nInspectorLog.i('Info message / 信息消息');\nInspectorLog.w('Warning message / 警告消息');\nInspectorLog.e('Error message / 错误消息');\n// With tag / 带标签\nInspectorLog.i('User logged in', tag: 'Auth');\nFull form / 完整写法:\nInspectorLogInterceptor.instance.verbose('Verbose message / 详细消息');\nInspectorLogInterceptor.instance.debug('Debug message / 调试消息');\nInspectorLogInterceptor.instance.info('Info message / 信息消息');\nInspectorLogInterceptor.instance.warning('Warning message / 警告消息');\nInspectorLogInterceptor.instance.error('Error message / 错误消息');\n// With tag / 带标签\nInspectorLogInterceptor.instance.info('User logged in', tag: 'Auth');\nInspectorLog is a static wrapper around InspectorLogInterceptor.instance for shorter log calls.InspectorLog 是 InspectorLogInterceptor.instance 的静态包装,用于更简短的日志调用。","third-party-library-integration--第三方日志库集成#Third-Party Library Integration / 第三方日志库集成":"","auto-capture-inbound--自动捕获入站#Auto-Capture (Inbound) / 自动捕获(入站)":"No configuration needed. Any library using print() or debugPrint() is automatically captured.无需配置。任何使用 print() 或 debugPrint() 的库都会被自动捕获。","bidirectional-sync-optional--双向同步可选#Bidirectional Sync (Optional) / 双向同步(可选)":"To forward inspector logs to your third-party logger:将检查器日志转发到第三方日志库:\nimport 'package:logger/logger.dart';\nfinal logger = Logger();\nInspectorLogInterceptor.instance.onLogCaptured = (entry) {\n logger.log(\n _mapLogLevel(entry.level),\n '${entry.tag != null ? '[${entry.tag}] ' : ''}${entry.message}',\n );\n};\nNote: Do NOT call logging methods inside onLogCaptured, as this will cause infinite recursion.\n注意:不要在 onLogCaptured 内部调用日志方法,否则会导致无限递归。","starting-the-log-interceptor--启动日志拦截器#Starting the Log Interceptor / 启动日志拦截器":"If using runAppWithInspector(), the log interceptor starts automatically. Otherwise:如果使用 runAppWithInspector(),日志拦截器会自动启动。否则:\nInspectorLogInterceptor.instance.start();"}},"/Memory-Viewer":{"title":"Memory Viewer / 内存监控面板","data":{"":"The Memory Viewer provides comprehensive in-app memory analysis, including trend charts, Dart Heap details, Native memory breakdown, memory leak detection, image cache monitoring, and storage statistics.内存监控面板提供应用内全面内存分析,包括趋势图、Dart Heap 详情、Native 内存分项、内存泄漏检测、图片缓存监控和存储统计。\nAvailable since v1.1.0v1.1.1 起可用","overview--概览#Overview / 概览":"The Memory Viewer is integrated into the inspector panel. Tap the floating inspector button → \"Memory\" tab to access.内存监控面板集成在检查器面板中。点击悬浮检查器按钮 → \"Memory\" 标签即可访问。⚠️ Important: Memory monitoring is off by default. You must toggle on the switch at the top of the panel to start collecting data.⚠️ 重要:内存监控默认关闭。必须在面板顶部打开开关才会开始采集数据。","master-switch--总开关#Master Switch / 总开关":"A switch at the top of the Memory panel controls whether monitoring is enabled.Memory 面板顶部有一个开关,控制是否启用监控。\nState\tBehavior\tOFF (default)\tNo timers, no VM Service connection, zero overhead / 无定时器、无 VM Service 连接、零开销\tON\tStarts RSS collection (500ms), Native memory (3s), storage stats (3s), leak detection (2s), and VM Service connection attempt / 启动 RSS 采集(500ms)、Native 内存(3s)、存储统计(3s)、泄漏检测(2s),并尝试连接 VM Service\t\nWhen turned off, all timers are cancelled and the VM Service connection state is cleared, leaving no residual WebSocket overhead.关闭时所有定时器会被取消,VM Service 连接状态会被清空,不会残留 WebSocket 开销。","features--功能#Features / 功能":"","1-memory-trend-chart--内存趋势图#1. Memory Trend Chart / 内存趋势图":"Real-time line chart with 2-minute history window (240 snapshots × 500ms)\nSwitchable between 4 metrics via the metric selector chips:\nProcess RSS — Process-level RSS (always available)\nDart Heap — Total Dart Heap usage (requires VM Service)\nNew Space — New generation usage (requires VM Service)\nOld Space — Old generation usage (requires VM Service)\n实时折线图,2 分钟历史窗口(240 条快照 × 500ms)。可通过指标选择芯片切换 4 种指标。","2-native-memory--native-内存真机-100-可用#2. Native Memory / Native 内存(真机 100% 可用)":"Does not depend on VM Service. Data is collected via Platform Channel:不依赖 VM Service,通过 Platform Channel 采集:Android (Debug.MemoryInfo + /proc/self/status):\nTotal PSS / Dalvik PSS / Native PSS / Native Private Dirty\nProcess RSS (from /proc/self/status)\nDevice memory status (availMem, threshold, lowMemory)\niOS (mach task_info):\nPhysical Footprint (Apple's recommended metric)\nInternal / Compressed / Resident Size\nProcess RSS\nDevice available memory","3-dart-heap-overview--dart-heap-概览需要-vm-service#3. Dart Heap Overview / Dart Heap 概览(需要 VM Service)":"Heap Usage / Capacity / external usage\nProgress bar showing heap usage ratio\nThree core metrics with bilingual labels\n显示 Heap Usage / Capacity / External 三个核心指标 + 进度条。","4-heap-generations--堆分代详情需要-vm-service#4. Heap Generations / 堆分代详情(需要 VM Service)":"New Space: usage / capacity / external\nOld Space: usage / capacity / external\nHelps identify allocation patterns (frequent new-space churn vs old-space growth)\n显示新生代/老生代的 Usage / Capacity / External 详细数据。","5-manual-gc--手动触发-gc需要-vm-service#5. Manual GC / 手动触发 GC(需要 VM Service)":"\"Trigger GC\" button forces a full garbage collection\nDisabled (grayed out) when VM Service is unavailable\n\"Trigger GC\" 按钮强制触发完整垃圾回收。VM Service 不可用时按钮变灰禁用。","6-memory-leak-detection--内存泄漏检测#6. Memory Leak Detection / 内存泄漏检测":"Based on Dart 2.17+ WeakReference and Finalizer. Does NOT depend on VM Service.基于 Dart 2.17+ 的 WeakReference 和 Finalizer,不依赖 VM Service。Four-state transition / 四状态流转:\nState\tMeaning\ttracking\tObject registered, waiting for expected release time / 对象已注册,等待预期释放时间\tverifying\tExceeded expected release time, GC verification triggered (if VM Service available) / 超过预期释放时间,触发 GC 验证(VM Service 可用时)\tleaked\tObject still exists after GC — suspected leak / GC 后对象仍存在——疑似泄漏\treleased\tObject has been garbage collected / 对象已被垃圾回收\t\nAPI / 接口:Quick shorthand (recommended) / 简化写法(推荐) — available since v1.1.2 / v1.1.2 起可用:\n// Extension method on Object / Object 上的扩展方法\nmyBloc.trackMemoryLeak(tag: 'HomePage_myBloc');\n// Or top-level function / 或使用顶层函数\ntrackMemoryLeak(myBloc, tag: 'HomePage_myBloc');\n// Cancel tracking / 取消追踪\nmyBloc.untrackMemoryLeak();\nFull form / 完整写法:\n// Register an object for leak tracking / 注册对象进行泄漏追踪\nMemoryInspectorService.instance.trackObject(\n myController,\n tag: 'HomeController_textController', // optional identifier\n expectedReleaseAfter: const Duration(seconds: 30), // expected release time\n);\n// Stop tracking a specific object / 停止追踪特定对象\nMemoryInspectorService.instance.untrackObject(myController);\n// Clear all records / 清空所有记录\nMemoryInspectorService.instance.clearLeakRecords();\nLimits / 限制:\nMax 500 tracked objects (LRU eviction)\nDetection interval: 2 seconds\ntrackObject() requires user code modification (mild invasion)\nFlutter MemoryAllocations bridge (since v1.9.0) / 官方泄漏追踪桥接(v1.9.0 起)The WeakReference state machine has a blind spot: an object whose dispose() ran but that has not been GC'd yet still resolves through the weak reference, producing a false \"leaked\" verdict. Since v1.9.0, MemoryInspectorService also subscribes to Flutter's official FlutterMemoryAllocations (the data source behind leak_tracker) via LeakTrackerBridge: once the official stream reports disposed, the object is treated as released (awaiting GC) even if the weak reference still resolves — cutting false positives. Enabled by default through enableFlutterLeakTracker in init(), and toggled in code via MemoryInspectorService.instance.flutterLeakTrackerEnabled.WeakReference 状态机有一个盲点:对象已调用 dispose() 但尚未被 GC 时弱引用仍存活,会产生\"疑似泄漏\"的误报。v1.9.0 起,MemoryInspectorService 通过 LeakTrackerBridge 额外订阅 Flutter 官方的 FlutterMemoryAllocations(leak_tracker 背后的数据源):只要官方上报 disposed,即便弱引用仍存活也判定为已释放(等待 GC),从而显著降低误报。由 init() 的 enableFlutterLeakTracker 默认启用,也可用 MemoryInspectorService.instance.flutterLeakTrackerEnabled 在代码中开关。","7-image-cache--图片缓存#7. Image Cache / 图片缓存":"Real-time Flutter image cache size and count\nPending (loading) / Live (in use) image counts\nOne-click clear all image cache\n实时显示 Flutter 图片缓存大小、数量、加载中/使用中状态。一键清理图片缓存。","8-app-storage--应用存储#8. App Storage / 应用存储":"Documents directory size\nTemp cache directory size\nTotal database file size\nOne-click clear temp cache\n显示文档目录、临时缓存、数据库文件大小。一键清理临时缓存。","️-vm-service-availability--vm-service-可用性#⚠️ VM Service Availability / VM Service 可用性":"Dart Heap data and manual GC require VM Service connection. When VM Service is unavailable, these features gracefully degrade to show \"N/A\".Dart Heap 数据和手动 GC 需要 VM Service 连接。 VM Service 不可用时,这些功能优雅降级显示 \"N/A\"。","when-debugging-via-pc-with-flutter-run--通过-pc-用-flutter-run-调试时#When debugging via PC with flutter run / 通过 PC 用 flutter run 调试时":"Dart VM Heap data may be unavailable (VM: OFF).Dart VM Heap 数据可能不可用(VM: OFF)。Reason / 原因: When using flutter run to debug via PC, the flutter tool sets up port forwarding between PC and device via adb reverse, allowing PC-side DevTools to access the device's VM Service. However, Service.getInfo() returns a serverUri from the PC's perspective; when the app process internally accesses 127.0.0.1:PC_port, the device doesn't have that port listening locally, resulting in Connection refused and VM Service showing OFF.原因:使用 flutter run 连接 PC 调试时,flutter tool 会通过 adb reverse 在 PC 和设备之间做端口转发,让 PC 上的 DevTools 能访问设备的 VM Service。但应用进程内部 Service.getInfo() 返回的 serverUri 是 PC 视角的端口,应用进程访问 127.0.0.1:PC端口 时设备本地并没有监听该端口,导致 Connection refused,VM Service 显示 OFF。","when-opening-debug-app-directly-no-pc--直接打开-debug-应用不连-pc#When opening debug app directly (no PC) / 直接打开 debug 应用(不连 PC)":"VM Service works normally. No flutter tool is involved, VM Service listens directly on the device's local port, the app can connect normally, and Dart Heap data displays correctly.VM Service 正常工作。 没有 flutter tool 介入,VM Service 直接监听设备本地端口,应用能正常连接,Dart Heap 数据正常显示。","fallback--降级方案#Fallback / 降级方案":"When VM Service is unavailable:\n✅ Native memory (Android PSS / iOS physicalFootprint) — still available\n✅ Process RSS — always available\n✅ Image cache / storage stats — still available\n✅ Leak detection — still available (doesn't depend on VM Service)\n❌ Dart Heap details — shows N/A\n❌ Manual GC — button disabled","platform-support--平台支持#Platform Support / 平台支持":"Feature\tAndroid\tiOS\tProcess RSS\t✅\t✅\tNative Memory\t✅\t✅\tDart Heap (VM Service)\t✅ (no PC)\t✅ (no PC)\tManual GC\t✅ (no PC)\t✅ (no PC)\tLeak Detection\t✅\t✅\tImage Cache\t✅\t✅\tStorage Stats\t✅\t✅","refresh-intervals--刷新间隔#Refresh Intervals / 刷新间隔":"Data Source\tInterval\tProcess RSS / Dart Heap\t500ms\tNative Memory (Android/iOS)\t3000ms\tStorage Stats\t3000ms\tLeak Detection\t2000ms","related--相关#Related / 相关":"Usage — General usage guide\nFAQ — Common questions\nConfiguration — Configuration options"}},"/Network-Inspector":{"title":"Network Inspector / 网络检查器","data":{"overview--概述#Overview / 概述":"The Network Inspector automatically captures all HTTP requests made via the http package and Dio, with zero configuration needed.网络检查器自动捕获所有通过 http 包 和 Dio 发送的 HTTP 请求,无需任何配置。","how-it-works--工作原理#How It Works / 工作原理":"The inspector uses Flutter's HttpOverrides to intercept all HTTP traffic at the dart:io level. This means:检查器通过 Flutter 的 HttpOverrides 在 dart:io 层面拦截所有 HTTP 流量。这意味着:\nhttp package: Auto-captured ✅ / 自动捕获 ✅\nDio: Auto-captured (uses IOHttpClientAdapter → HttpClient) ✅ / 自动捕获 ✅\nNo manual interceptor setup needed / 无需手动添加拦截器","websocket--grpc-capture--websocket-与-grpc-抓取#WebSocket & gRPC Capture / WebSocket 与 gRPC 抓取":"Available since v1.7.0 (opt-in, off by default)v1.7.0 起可用(可选开启,默认关闭)\nHTTP/HTTPS traffic is captured automatically, but the HttpOverrides interceptor does not cover streaming protocols like WebSocket and gRPC. For those, enable the opt-in capture so frames and calls show up as WS / gRPC rows in the Network tab.HTTP/HTTPS 流量会自动捕获,但 HttpOverrides 拦截器无法覆盖 WebSocket、gRPC 这类流式协议。针对它们需开启可选的抓取,开启后收发帧/调用会以 WS / gRPC 行的形式出现在 Network 标签页。","enable--开启方式#Enable / 开启方式":"Toggle the WS switch in the Network tab toolbar, or / 在 Network 标签页工具栏点击 WS 开关,或\nSet it programmatically: / 通过代码开启:\n// on / 开启\nWsInspectorService.instance.enable();\n// off / 关闭\nWsInspectorService.instance.disable();\nCapture is off by default and only records while enabled — apps that don't use these protocols pay nothing.抓取默认关闭,且只在开启时记录;不使用这类协议的应用零开销。","two-usage-modes--两种使用方式#Two Usage Modes / 两种使用方式":"1. Transparent wrapper — InspectorWebSocketReplace WebSocket.connect with InspectorWebSocket.connect. Frames are auto-recorded (→ out, ← in) when capture is on; when off it passes through with zero overhead.将 WebSocket.connect 替换为 InspectorWebSocket.connect。开启抓取时会自动记录收发帧(→ 出站、← 入站);关闭时零开销透传。\nfinal ws = await InspectorWebSocket.connect('wss://echo.websocket.events');\nws.listen((msg) => print('received: $msg'));\nws.add('hello'); // recorded as an outgoing frame / 记录为出站帧\n2. Manual hook — recordCallFor stacks not transparently interceptable by dart:io (gRPC, web_socket_channel, custom protocols), call recordCall to log a request/response pair. No-ops when capture is disabled.对于无法被 dart:io 透明拦截的栈(gRPC、web_socket_channel、自定义协议),调用 recordCall 记录一次请求/响应。关闭抓取时为空操作。\nWsInspectorService.instance.recordCall(\n name: 'user.UserService/GetUser',\n request: '{ \"id\": 1 }',\n response: '{ \"name\": \"Ada\" }',\n protocol: 'gRPC', // appears in the method column / 显示在 method 列\n);","what-you-see--查看方式#What You See / 查看方式":"Network tab lists a WS (or gRPC) entry per connection/call / Network 标签页按连接/调用列出 WS(或 gRPC)记录\nOpen the detail view to see the frame log (outgoing → / incoming ←), accumulated in the response body / 进入详情页查看帧日志(出站 → / 入站 ←),累积显示在响应体中\nA [connection closed] marker is appended when the socket closes / 连接关闭后会追加 [connection closed] 标记","captured-information--捕获的信息#Captured Information / 捕获的信息":"Field\tDescription\tMethod\tGET, POST, PUT, DELETE, PATCH\tURL\tFull request URL\tStatus Code\tHTTP response status code\tDuration\tRequest duration\tRequest Headers\tAll request headers\tRequest Body\tRequest payload (JSON formatted)\tResponse Body\tResponse payload (JSON formatted)\tHost\tParsed from URL","ui-features--ui-功能#UI Features / UI 功能":"","request-list--请求列表#Request List / 请求列表":"Color-coded by HTTP method / 按 HTTP 方法着色\nStatus code badge / 状态码徽章\nDuration display / 耗时显示\nLeft border color indicates status / 左侧边框颜色表示状态","request-detail--请求详情#Request Detail / 请求详情":"Click a request to enter detail view / 点击请求进入详情视图\nBack button to return to list / 返回按钮返回列表\nRequest and response sections / 请求和响应分段显示\nJSON formatted body / JSON 格式化显示","search--搜索#Search / 搜索":"Fuzzy search by URL or method / 按 URL 或方法模糊搜索\nSearch bar hidden in detail view / 详情视图隐藏搜索栏","batch-operations--批量操作#Batch Operations / 批量操作":"Available since v1.3.0v1.3.0 起可用\nThe request list supports a selection mode for operating on multiple requests at once.请求列表支持选择模式,可一次性操作多条请求。\nTap the selection icon in the toolbar to enter selection mode / 点击工具栏的选择图标进入选择模式\nCheck/uncheck items; a top batch bar shows Select all / Cancel / 勾选/取消勾选;顶部批量条提供全选 / 取消\nBatch \"Copy as cURL\": copies all selected requests as cURL commands / 批量「Copy as cURL」:将所有选中请求复制为 cURL 命令\nBatch delete: removes selected requests from the list / 批量删除:从列表中移除选中请求","export--sensitive-field-masking--导出与敏感字段遮蔽#Export & Sensitive-field Masking / 导出与敏感字段遮蔽":"Available since v1.3.0v1.3.0 起可用\nThe toolbar includes an eye toggle that controls whether sensitive headers are masked on export.工具栏包含眼睛开关,控制导出时是否遮蔽敏感请求头。\nDefault: off (fully visible) — cURL / JSON / HAR reflect the real request verbatim / 默认关闭(完整可见):cURL / JSON / HAR 原样反映真实请求\nOn: toCurl / netToJson / netToHar / copyNet / exportNetToFile mask these headers / 开启后:以下请求头被遮蔽:\nAuthorization, Cookie, Set-Cookie, Proxy-Authorization, X-Auth-Token, X-CSRF-Token, X-XSRF-Token\nThe detail page \"Copy as cURL\" / \"Copy as JSON\" follow the same toggle / 详情页「Copy as cURL」「Copy as JSON」跟随同一开关\nWhen masking is active, the snackbar notes (sensitive hidden) / 遮蔽开启时,snackbar 提示 (sensitive hidden)\nTip: keep masking on before pasting cURL/JSON into terminals, chat, or issue trackers to avoid leaking credentials.提示:将 cURL/JSON 粘贴到终端、聊天或 issue 前,建议开启遮蔽,避免凭据泄露。","copy-as-curl--复制为-curl#Copy as cURL / 复制为 cURL":"Available since v1.3.0v1.3.0 起可用\nFrom the request detail view, tap Copy as cURL to copy the request as a ready-to-run cURL command (method, URL, headers, body). Respects the sensitive-field masking toggle above.在请求详情页点击 Copy as cURL,即可将请求复制为可直接运行的 cURL 命令(方法、URL、请求头、请求体),并遵循上方的敏感字段遮蔽开关。","replay-editor--重放编辑器#Replay Editor / 重放编辑器":"Available since v1.11.0v1.11.0 起可用\nEvery request detail view has a replay action (↻ icon). It opens an editable sheet that lets you re-issue the captured request in-app and preview the response — handy for quickly re-running an endpoint without leaving the app.每个请求详情页都有重放操作(↻ 图标)。它会打开一个可编辑弹层,让你在 App 内重新发出该请求并预览响应——无需离开应用即可快速复跑某个接口。\nOnly the URL query parameters are editable — add / edit / remove key=value rows; the URL is rebuilt from them on send / 仅 URL 查询参数可编辑——可增删改 key=value 行,发送时用其重建 URL\nURL, request headers and request body are read-only by design (the captured values are re-sent verbatim) / URL、请求头与请求体按设计只读(以捕获到的原值原样重发)\nOn Send, the request is re-issued with the original method / headers / body and the edited query string; the sheet shows the returned status code, elapsed time, and a response preview / 点击 Send 后用原始方法 / 请求头 / 请求体加上修改后的查询串重新发出,弹层展示返回状态码、耗时与响应预览\nThis is distinct from the interceptor (which builds a durable modification rule applied to future matching requests). The replay editor makes a one-off call with editable query params — including GET requests, whose params can't be changed by the interceptor.它与拦截器不同(拦截器生成作用于后续匹配请求的持久规则)。重放编辑器发起的是一次性调用,仅查询参数可改——包括 GET 请求(其参数拦截器无法修改)。","status-code-colors--状态码颜色#Status Code Colors / 状态码颜色":"Range\tColor\tDescription\t2xx\tGreen\tSuccess / 成功\t3xx\tBlue\tRedirect / 重定向\t4xx\tOrange\tClient error / 客户端错误\t5xx\tRed\tServer error / 服务器错误","http-method-colors--http-方法颜色#HTTP Method Colors / HTTP 方法颜色":"Method\tColor\tGET\tBlue / 蓝色\tPOST\tGreen / 绿色\tPUT\tOrange / 橙色\tDELETE\tRed / 红色\tPATCH\tPurple / 紫色","usage-example--使用示例#Usage Example / 使用示例":"// http package - auto-captured / http 包 - 自动捕获\nfinal response = await http.get(\n Uri.parse('https://api.example.com/data'),\n);\n// Dio - auto-captured / Dio - 自动捕获\nfinal response = await dio.post(\n 'https://api.example.com/data',\n data: {'key': 'value'},\n);\nNo additional setup required! All requests will appear in the Network tab.无需额外配置!所有请求都会出现在 Network 标签页中。","request-interceptor--请求拦截修改#Request Interceptor / 请求拦截修改":"Available since v1.0.7 (response fields locked to read-only since v1.0.8)v1.0.7 起可用(v1.0.8 起响应字段锁定为只读)\nThe inspector supports intercepting and modifying network requests via rules. This is useful for testing different request parameters without modifying app code.检查器支持通过规则拦截并修改网络请求,适合在不修改应用代码的情况下测试不同的请求参数。","workflow--工作流程#Workflow / 工作流程":"Send a request normally (it will be captured in the Network panel) / 正常发送请求(会被捕获到 Network 面板)\nOpen the request detail and tap the Interceptor icon / 打开请求详情,点击拦截器图标\nConfigure the modification rule (URL pattern, HTTP method, request modifications) / 配置修改规则(URL 模式、HTTP 方法、请求修改)\nSave the rule — subsequent matching requests will use the modified parameters / 保存规则——后续匹配的请求将使用修改后的参数","supported-modifications--支持的修改#Supported Modifications / 支持的修改":"Field\tEditable\tNotes\tRequest Body\t✅\tOnly for requests with body (POST, PUT, PATCH, etc.) / 仅适用于有 body 的请求\tRequest Headers\t✅\tAdd / modify / remove headers / 新增 / 修改 / 删除请求头\tURL\t❌\tGrayed out, read-only / 灰色不可编辑\tResponse Status Code\t❌\tRead-only since v1.0.8 / v1.0.8 起只读\tResponse Body\t❌\tRead-only since v1.0.8 / v1.0.8 起只读\t\nThe interception edit panel only allows modifying the request body and request headers. Response fields (status code, response body) are grayed out and uneditable.拦截编辑面板仅允许修改请求体和请求头。响应字段(状态码、响应体)灰色不可编辑。","rule-matching--规则匹配#Rule Matching / 规则匹配":"URL pattern matching: exact match or regex / URL 模式匹配:精确匹配或正则匹配\nHTTP method filtering: GET, POST, PUT, DELETE, PATCH, HEAD, or Any / HTTP 方法过滤","why-get-requests-cannot-be-modified--为什么-get-请求不能修改#Why GET Requests Cannot Be Modified / 为什么 GET 请求不能修改":"The interceptor currently supports modifying request body and headers only / 拦截器目前仅支持修改请求体和请求头\nGET requests don't have a request body / GET 请求没有请求体\nModifying GET request parameters would require URL modification / 修改 GET 请求参数需要修改 URL\nURL modification may cause unexpected issues with request routing and parameter encoding / 修改 URL 可能导致请求路由和参数编码的意外问题\nGET request detail pages do not display interception edit buttons, making them unmodifiable.GET 请求详情页不显示拦截编辑按钮,因此无法修改。","master-toggle--拦截总开关#Master Toggle / 拦截总开关":"The interception master toggle is displayed only on the Network list page, not in the detail page / 拦截总开关只显示在 Network 列表页,不在详情页\nRules are only applied when modification mode is enabled / 规则仅在启用修改模式时生效\nWhen no rules are configured or rules are disabled, all requests are sent normally without any modification / 未配置规则或禁用规则时,所有请求正常发送,不做任何修改","rule-management--规则管理#Rule Management / 规则管理":"The network panel includes an interceptor rule editor where you can:网络面板包含拦截规则编辑器,可以:\nCreate / edit / delete rules / 创建 / 编辑 / 删除规则\nEnable / disable individual rules / 启用 / 禁用单条规则\nView rule status indicators in the request list / 在请求列表中查看规则状态标识"}},"/Route-Tracker":{"title":"Route Tracker / 路由追踪","data":{"overview--概述#Overview / 概述":"The Route Tracker monitors navigation history, recording all route push, pop, and replacement events.路由追踪器监控导航历史,记录所有路由 push、pop 和替换事件。","auto-injection--自动注入#Auto-Injection / 自动注入":"When using runAppWithInspector() or wrapApp(), the InspectorRouteObserver is automatically injected into MaterialApp's navigatorObservers.使用 runAppWithInspector() 或 wrapApp() 时,InspectorRouteObserver 会自动注入到 MaterialApp 的 navigatorObservers 中。","tracked-route-actions--追踪的路由操作#Tracked Route Actions / 追踪的路由操作":"Action\tColor\tDescription\tpush\tBlue\tNavigator.push() / 推入新路由\tpushNamed\tBlue\tNavigator.pushNamed() / 按名称推入\tpop\tOrange\tNavigator.pop() / 弹出路由\tpopUntil\tOrange\tNavigator.popUntil() / 弹出直到\tpushReplacement\tPurple\tNavigator.pushReplacement() / 替换路由","ui-features--ui-功能#UI Features / UI 功能":"","route-list--路由列表#Route List / 路由列表":"Action badge with color / 带颜色的操作徽章\nRoute name display / 路由名称显示\nTimestamp display / 时间戳显示\nLeft border color indicates action type / 左侧边框颜色表示操作类型","route-detail--路由详情#Route Detail / 路由详情":"Click a route to view details / 点击路由查看详情\nShows: Action, Route Name, Timestamp, Arguments / 显示:操作、路由名、时间戳、参数\nArguments displayed as formatted JSON / 参数以 JSON 格式显示","manual-setup-optional--手动设置可选#Manual Setup (Optional) / 手动设置(可选)":"If you use a custom Navigator or don't use MaterialApp, add the observer manually:如果使用自定义 Navigator 或不使用 MaterialApp,请手动添加观察者:\nMaterialApp(\n navigatorObservers: [InspectorRouteObserver()],\n home: MyHomePage(),\n)"}},"/":{"title":"Zero Inspector Kit","data":{"":"A powerful Flutter plugin for in-app developer console, providing real-time debugging tools including network request inspection, logging, error aggregation, database viewing, and route tracking.一个功能强大的 Flutter 插件,提供应用内开发者控制台,包括网络请求检查、日志记录、异常聚合、数据库查看和路由追踪。","-features--功能特性#✨ Features / 功能特性":"Feature\tDescription\tZero Invasion\tIntegrate with 1 line of code / 一行代码集成\tNetwork Inspector\tReal-time HTTP request viewing (http + Dio) / 实时网络请求查看\tLog Viewer\tAuto-capture print()/debugPrint() and third-party logs / 自动捕获日志\tErrors\tAggregate & dedupe crashes by type + stack, with count and first/last seen / 按类型+堆栈去重聚合崩溃,记录次数与首末次时间\tDatabase Viewer\tSQLite inspection with table data / 数据库查看\tMemory Viewer\tTrend chart, Dart Heap, Native memory, leak detection (incl. Flutter MemoryAllocations bridge) / 内存趋势图、Dart Heap、Native 内存、泄漏检测(含官方 MemoryAllocations 桥接)\tFPS Monitor\tReal-time FPS, jank rate, trend chart / 实时 FPS、卡顿率、趋势图\tRoute Tracker\tNavigation history tracking / 路由追踪\tSession Persistence\tLogs/network/errors survive restarts via a disk ring buffer; logs & errors replay on launch; one-tap session archive export / 日志/网络/异常通过磁盘环形缓冲跨重启保留,启动时回放日志与异常;一键导出会话存档\tAlerts\tRule-based alerts (network/log/memory/FPS) with unread badge / 基于规则的告警(网络/日志/内存/FPS)与未读角标\tSensitive Masking & cURL\tMask secrets on export; one-click cURL copy; batch ops / 导出遮蔽敏感字段、一键复制 cURL、批量操作\tFuzzy Search\tSearch in all viewers / 各查看器模糊搜索\tCross-platform\tAndroid, iOS / 跨平台支持","-table-of-contents--目录#📚 Table of Contents / 目录":"Page\tDescription\tGetting Started\tQuick start guide / 快速开始\tInstallation\tHow to install / 安装方式\tUsage\tDetailed usage / 详细使用\tNetwork Inspector\tNetwork request viewing / 网络检查器\tLog Viewer\tLog capturing and viewing / 日志查看器\tErrors\tAggregated error viewing / 异常聚合查看\tDatabase Viewer\tDatabase inspection / 数据库查看器\tRoute Tracker\tRoute tracking / 路由追踪\tMemory Viewer\tMemory monitoring & leak detection / 内存监控与泄漏检测\tFPS Viewer\tFPS monitoring & jank detection / FPS 监控与卡顿检测\tAlerts\tRule-based alerting / 基于规则的告警\tConfiguration\tConfiguration options / 配置说明\tCustom Database Provider\tExtend database support / 自定义数据库提供者\tFAQ\tFrequently asked questions / 常见问题","-links--链接#🔗 Links / 链接":"GitHub\nOfficial Website\npub.dev","-license--许可证#📄 License / 许可证":"This project is licensed under the GNU General Public License v3.0.本项目采用 GNU General Public License v3.0 许可证。This plugin is provided \"as is\", without warranty of any kind. The author assumes no responsibility or liability for the functionality, security, or any consequences arising from the use of modified versions or derivative projects.本插件按\"原样\"提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及任何使用后果承担责任。"}},"/Usage":{"title":"Usage / 使用指南","data":{"integration-methods--集成方式#Integration Methods / 集成方式":"","1-one-line-integration-recommended--一行代码集成推荐#1. One-Line Integration (Recommended) / 一行代码集成(推荐)":"void main() {\n ZeroInspectorKit.runAppWithInspector(const MyApp());\n}\nThis method:\nAuto-initializes inspector / 自动初始化检查器\nCaptures print() via Zone / 通过 Zone 捕获 print()\nDisplays floating button via Overlay / 通过 Overlay 显示悬浮按钮\nAuto-injects route observer / 自动注入路由观察者","2-two-line-integration--两行代码集成#2. Two-Line Integration / 两行代码集成":"void main() {\n ZeroInspectorKit.init();\n runApp(ZeroInspectorKit.wrapApp(const MyApp()));\n}","inspector-panel--检查器面板#Inspector Panel / 检查器面板":"The inspector panel contains 9 tabs (as of v1.9.0):检查器面板包含 9 个标签页(v1.9.0 起):\nTab\tIcon\tFeature\tNetwork\t🌐\tHTTP request viewing + interceptor rules / 网络请求查看 + 拦截修改\tLogs\t📝\tLog viewing with level filter / 日志查看\tErrors\t🚨\tAggregated & deduped crash viewing / 去重聚合的异常查看\tDatabase\t💾\tDatabase and table inspection / 数据库查看\tMemory\t📊\tMemory trend, Dart Heap, Native memory, leak detection / 内存趋势、Dart Heap、Native 内存、泄漏检测\tFPS\t🎯\tReal-time FPS, jank rate, trend chart / 实时 FPS、卡顿率、趋势图\tRoutes\t🧭\tRoute navigation tracking / 路由追踪\tWidgets\t🔍\tWidget tree snapshot for the current route / 当前路由的 Widget 树快照\tAlerts\t🔔\tRule-based alerts with unread badge / 基于规则的告警与未读角标\t\nThe Memory and FPS monitors are off by default to avoid performance overhead. Toggle the switch at the top of each panel to start collecting data.Memory 与 FPS 监控默认关闭以避免性能开销。在各自面板顶部打开开关才会开始采集数据。","one-click-bug-report--一键-bug-报告#One-Click Bug Report / 一键 Bug 报告":"Tap the bug icon in the inspector panel header to generate and share a bug report in one tap — ideal for QA to attach environment context when filing issues.点击检查器面板头部的虫子图标,即可一键生成并分享一份 Bug 报告——非常适合 QA 在提 issue 时附上环境上下文。The shared text snapshot includes / 分享的文本快照包含:\nDevice / 设备: real model (e.g. Pixel 8 Pro / iPhone (iPhone16,1)), OS & version, locale, Dart runtime, CPU cores.\nMemory / 内存: current heap usage and whether Native memory is supported.\nRecent logs / 最近日志: the latest captured log entries.\nRecent network / 最近网络: the latest captured requests.\nSensitive headers are masked the same way as in the Network tab. No data leaves the device except through the share target you choose.敏感请求头会与网络标签页一样被遮蔽。除你选择的分享目标外,数据不会离开设备。\nRequires no extra setup — it works as soon as the inspector is running. / 无需额外配置——检查器运行后即可使用。","floating-button--悬浮按钮#Floating Button / 悬浮按钮":"The floating button appears after 1 second delay / 悬浮按钮延迟 1 秒出现\nDrag to move it along the screen edge / 拖动 可沿屏幕边缘移动\nTap (when fully visible) to open/close the inspector panel / 点击(完全可见时)打开/关闭检查器面板\nButton auto-snaps to the nearest screen edge / 按钮自动吸附到最近的屏幕边缘\nBreathing animation when idle / 空闲时有呼吸动画","edge-docking-since-v120--边缘吸附v120-起#Edge Docking (since v1.2.0) / 边缘吸附(v1.2.0 起)":"When released near a screen edge, the button auto-docks and tucks into the edge, leaving only a 24px peek visible:拖动松手后,按钮会自动吸附到最近边缘并\"收入\"边缘,仅露出 24px 小弧边:\nState\tBehavior / 行为\tDocked (tucked in)\tOnly 24px peek visible; icon becomes a directional chevron (left dock → ➡, right dock → ⬅) hinting at tap-to-pull-out / 仅露出 24px;图标变为方向箭头提示可点击拉出\tTap docked peek\tSmoothly pulls out to fully visible (panel NOT opened, avoids accidental open) / 平滑拉出到完全可见(不打开面板,避免误触)\tTap fully visible\tOpens the inspector panel / 打开检查器面板\t\nThis design avoids conflicts with system back gestures (Android/iOS edge swipe to go back) when pulling out from the docked state.此设计避免了从吸附态拖出时与系统返回手势(Android/iOS 边缘右滑退出)的冲突。","search--搜索#Search / 搜索":"The main viewers support fuzzy search:各查看器均支持模糊搜索:\nViewer\tSearch Scope\tNetwork\tURL, HTTP method / URL、请求方法\tLogs\tMessage, tag / 消息、标签\tErrors\tException type, message / 异常类型、消息\tDatabase (global)\tDatabase name, table name / 数据库名、表名\tDatabase (in-database)\tTable name, all column data / 表名、所有列数据","manual-logging-optional--手动记录日志可选#Manual Logging (Optional) / 手动记录日志(可选)":"The inspector auto-captures print() output. You can also use manual log methods for precise level control:检查器会自动捕获 print() 输出。也可以使用手动日志方法进行精确级别控制:Quick shorthand (recommended) / 简化写法(推荐):\nInspectorLog.v('Verbose log / 详细日志');\nInspectorLog.d('Debug log / 调试日志');\nInspectorLog.i('Info log / 信息日志');\nInspectorLog.w('Warning log / 警告日志');\nInspectorLog.e('Error log / 错误日志');\nFull form / 完整写法:\nInspectorLogInterceptor.instance.verbose('Verbose log / 详细日志');\nInspectorLogInterceptor.instance.debug('Debug log / 调试日志');\nInspectorLogInterceptor.instance.info('Info log / 信息日志');\nInspectorLogInterceptor.instance.warning('Warning log / 警告日志');\nInspectorLogInterceptor.instance.error('Error log / 错误日志');","third-party-log-integration--第三方日志库集成#Third-Party Log Integration / 第三方日志库集成":"No configuration needed! The plugin automatically captures logs from third-party logging libraries (e.g., logger, flutter_logger) that use print() or debugPrint().无需配置! 插件会自动捕获所有使用 print() 的第三方日志库的日志。These logs are categorized as INFO level.这些日志统一归类为 INFO 级别。","bidirectional-sync-optional--双向同步可选#Bidirectional Sync (Optional) / 双向同步(可选)":"To sync inspector-captured logs to your third-party logger:将检查器捕获的日志同步到第三方日志库:\nInspectorLogInterceptor.instance.onLogCaptured = (entry) {\n yourLogger.log(entry.message);\n};","feature-pages--功能详情#Feature Pages / 功能详情":"Network Inspector — Network request details + interceptor rules / 网络检查器详情 + 拦截修改\nLog Viewer — Log viewing details / 日志查看器详情\nErrors — Aggregated error viewing / 异常聚合查看\nDatabase Viewer — Database inspection details / 数据库查看器详情\nRoute Tracker — Route tracking details / 路由追踪详情\nMemory Viewer — Memory monitoring & leak detection / 内存监控与泄漏检测\nFPS Viewer — FPS monitoring & jank detection / FPS 监控与卡顿检测","session-persistence--会话持久化#Session Persistence / 会话持久化":"Logs, network requests, and aggregated errors are asynchronously flushed to a local SQLite ring buffer. On the next launch, logs and aggregated errors replay into their tabs; network requests stay archived on disk for later export. Data therefore survives app restarts even if the inspector panel was never opened. Tap the storage icon in the panel header to open the Persisted data manager: view row counts per category, export the full session archive, or clear the disk. See Configuration (PersistenceService section) for the API and tuning parameters.日志、网络请求与聚合异常会被异步写入本地 SQLite 环形缓冲。下次启动时,日志与聚合异常会回放入各自标签页;网络请求保留在磁盘存档,供之后导出。因此即使从未打开过检查器面板,数据也能跨重启保留。点击面板头部的存储图标可打开 Persisted data 管理弹层:查看各类别行数、导出完整会话存档或清空磁盘。API 与调参详见 Configuration(PersistenceService 一节)。"}}} \ No newline at end of file +{"/Alerts":{"title":"Alerts / 告警系统","data":{"overview--概述#Overview / 概述":"Available since v1.3.0v1.3.0 起可用\nThe alert system lets you define rules that proactively surface problems across network requests, logs, memory, and FPS — without manually scanning the panels.告警系统允许你定义规则,主动暴露网络请求、日志、内存、FPS 方面的问题,无需手动逐个面板排查。","how-it-works--工作原理#How It Works / 工作原理":"Rules are evaluated by AlertService whenever a relevant event occurs / 每当相关事件发生时,AlertService 会评估规则\nMatched rules produce alerts, and the unread count is exposed via a ValueNotifier / 命中的规则会生成告警,未读数通过 ValueNotifier 暴露\nThe floating button shows an unread-count badge; opening the panel clears it / 悬浮球显示未读数量角标,打开面板即清零\nAn Alerts tab in the inspector panel lists all triggered alerts / 检查器面板的 Alerts 标签页列出所有已触发的告警","rule-types--规则类型#Rule Types / 规则类型":"Type\tTrigger / 触发条件\tExample / 示例\tNetwork\tResponse status code / 响应状态码\tAlert when status ≥ 400 / 状态码 ≥ 400 时告警\tLog\tLog level or message / 日志级别或内容\tAlert on error logs / 出现 error 日志时告警\tMemory\tDart heap / Native memory threshold / 内存阈值\tAlert when Dart heap > 100 MB / Dart 堆 > 100 MB 时告警\tFPS\tFrame rate / 帧率\tAlert when FPS < 50 / FPS < 50 时告警","ui-features--ui-功能#UI Features / UI 功能":"","floating-button-badge--悬浮球角标#Floating Button Badge / 悬浮球角标":"A red badge shows the current unread alert count (clamped 0–99) / 红色角标显示当前未读告警数(0–99)\nThe number is drawn inside the button so it is never clipped at screen edges / 数字直接绘制在按钮内部,吸附屏幕边缘时不会被裁切\nOpening the inspector panel clears the unread count / 打开检查器面板即清零未读数","alerts-tab--alerts-标签页#Alerts Tab / Alerts 标签页":"Lists triggered alerts with type, condition, and timestamp / 列出已触发告警的类型、条件与时间\nTap an alert to jump to the related panel (network / log / memory / FPS) / 点击告警可跳转到相关面板(网络 / 日志 / 内存 / FPS)","notes--备注#Notes / 备注":"Alerts are evaluated in-process and shown in the developer console only; they do not send data off-device / 告警仅在本机开发者控制台内评估与展示,不会将数据发送到设备外\nPersisted to disk (since v1.11.0): triggered alerts are flushed to the SQLite ring buffer and replay into the Alerts tab after an app restart, and are included in the session archive export / 已落盘持久化(v1.11.0 起):已触发的告警会写入 SQLite 环形缓冲,重启后回放至 Alerts 标签页,并包含在会话存档导出中\nThe alert system is tree-shaken out in release builds, like the rest of the inspector / 与检查器其余部分一样,告警系统在 release 构建中被 tree-shake 移除"}},"/Configuration":{"title":"Configuration / 配置说明","data":{"zeroinspectorkitinit-parameters--初始化参数#ZeroInspectorKit.init() Parameters / 初始化参数":"Parameter\tType\tDefault\tDescription\tenable\tbool\ttrue\tEnable inspector (auto false in release mode) / 启用检查器\tenableLogCapture\tbool\ttrue\tEnable log capture / 启用日志捕获\tenableNetworkCapture\tbool\ttrue\tEnable network interception / 启用网络拦截\tenableErrorCapture\tbool\ttrue\tEnable error aggregation (Errors tab) / 启用异常聚合(Errors 标签页)\tenablePersistence\tbool\ttrue\tPersist logs/network/errors to a SQLite ring buffer; logs & errors replay on launch / 将日志/网络/异常落盘到 SQLite 环形缓冲,启动时回放日志与异常\tenableFlutterLeakTracker\tbool\ttrue\tBridge Flutter's official MemoryAllocations as a second leak-detection source / 桥接 Flutter 官方 MemoryAllocations 作为泄漏检测第二来源\tenableDatabaseScan\tbool\ttrue\tEnable database scan / 启用数据库扫描\tenableRouteTracking\tbool\ttrue\tEnable route tracking / 启用路由追踪\tenableWidgetInspector\tbool\ttrue\tEnable Widget tree snapshot / 启用 Widget 树快照\tenableNetworkTimeline\tbool\ttrue\tPrefer the network timeline (waterfall) in request details / 网络详情页默认展示时间轴(瀑布图)\tcustomButton\tWidget?\tnull\tCustom floating button widget / 自定义悬浮按钮\tonLogCaptured\tvoid Function(LogEntry)?\tnull\tLog capture callback for third-party integration / 日志捕获回调\tmaxNetworkItems\tint?\t100\tNetwork request cache cap / 网络请求缓存上限\tmaxLogItems\tint?\t500\tLog entry cache cap / 日志条目缓存上限\tmaxRouteItems\tint?\t200\tRoute record cache cap / 路由记录缓存上限\tmaxBodyPreviewBytes\tint?\t32KB\tBody preview cap, longer bodies truncated / body 预览字节上限,超出截断\t\nenableWidgetInspector / enableNetworkTimeline are also exposed on runAppWithInspector(). (wrapApp() takes only enable.)enableWidgetInspector / enableNetworkTimeline 也可在 runAppWithInspector() 上设置(wrapApp() 仅接受 enable)。","usage-examples--使用示例#Usage Examples / 使用示例":"","disable-specific-features--禁用特定功能#Disable Specific Features / 禁用特定功能":"ZeroInspectorKit.init(\n enableLogCapture: true,\n enableNetworkCapture: false, // Disable network monitoring / 禁用网络监控\n enableDatabaseScan: true,\n enableRouteTracking: false, // Disable route tracking / 禁用路由追踪\n);","with-log-callback--带日志回调#With Log Callback / 带日志回调":"ZeroInspectorKit.init(\n onLogCaptured: (entry) {\n // Forward to your logging service / 转发到你的日志服务\n myLogger.log(entry.message);\n },\n);","conditionalinspector--条件检查器组件#ConditionalInspector / 条件检查器组件":"A convenience widget that automatically shows/hides the inspector based on build mode.根据构建模式自动显示/隐藏检查器的便利组件。\nConditionalInspector(\n child: YourAppWidget(),\n)\nParameter\tType\tDefault\tDescription\tchild\tWidget\trequired\tChild widget / 子组件\tenabled\tbool\ttrue\tEnable inspector / 启用检查器","floatinginspectorbutton--悬浮检查器按钮#FloatingInspectorButton / 悬浮检查器按钮":"Parameter\tType\tDefault\tDescription\tenabled\tbool\ttrue\tEnable button (auto false in release mode) / 启用按钮","inspectorloginterceptor--日志拦截器#InspectorLogInterceptor / 日志拦截器":"Method\tDescription\tstart()\tStart capturing logs / 开始捕获日志\tstop()\tStop capturing logs / 停止捕获日志\tlog(level, message, tag)\tAdd a log entry / 添加日志条目\tverbose(message, tag)\tAdd verbose log / 添加详细日志\tdebug(message, tag)\tAdd debug log / 添加调试日志\tinfo(message, tag)\tAdd info log / 添加信息日志\twarning(message, tag)\tAdd warning log / 添加警告日志\terror(message, tag)\tAdd error log / 添加错误日志\t\nProperty\tType\tDescription\tonLogCaptured\tvoid Function(LogEntry)?\tCallback when a log is captured / 日志捕获回调","inspectorrouteobserver--路由观察者#InspectorRouteObserver / 路由观察者":"Navigator observer for tracking route changes. Auto-injected when using runAppWithInspector() or wrapApp().用于追踪路由变化的 Navigator 观察者。使用 runAppWithInspector() 或 wrapApp() 时自动注入。\nMaterialApp(\n navigatorObservers: [InspectorRouteObserver()],\n home: MyHomePage(),\n)","databaseregistry--数据库注册表#DatabaseRegistry / 数据库注册表":"Register custom database providers:注册自定义数据库提供者:\nDatabaseRegistry.instance.registerProvider(SqliteDatabaseProvider());\nSee Custom Database Provider for more details.详见 自定义数据库提供者。","inspectorlog--简化日志-api#InspectorLog / 简化日志 API":"Available since v1.1.2 / v1.1.2 起可用\nA static wrapper around InspectorLogInterceptor.instance for shorter log calls.InspectorLogInterceptor.instance 的静态包装,用于更简短的日志调用。\nInspectorLog.v('Verbose log');\nInspectorLog.d('Debug log');\nInspectorLog.i('Info log', tag: 'Auth');\nInspectorLog.w('Warning log');\nInspectorLog.e('Error log');\nMethod\tDescription\tstart()\tStart capturing logs / 开始捕获日志\tstop()\tStop capturing logs / 停止捕获日志\tlog(level, message, {tag})\tAdd a log entry / 添加日志条目\tv(message, {tag})\tAdd verbose log / 添加详细日志\td(message, {tag})\tAdd debug log / 添加调试日志\ti(message, {tag})\tAdd info log / 添加信息日志\tw(message, {tag})\tAdd warning log / 添加警告日志\te(message, {tag})\tAdd error log / 添加错误日志\t\nProperty\tType\tDescription\tisRunning\tbool\tWhether log capture is currently active / 日志捕获是否正在运行","memoryinspectorservice--内存监控服务#MemoryInspectorService / 内存监控服务":"Available since v1.1.0 / v1.1.0 起可用\nSingleton service for memory monitoring and leak detection, extends ChangeNotifier.内存监控与泄漏检测单例服务,继承 ChangeNotifier。","full-api--完整接口#Full API / 完整接口":"// Leak tracking (full form) / 泄漏追踪(完整写法)\nMemoryInspectorService.instance.trackObject(\n myController,\n tag: 'HomeController_textController',\n expectedReleaseAfter: const Duration(seconds: 30),\n);\nMemoryInspectorService.instance.untrackObject(myController);\nMemoryInspectorService.instance.clearLeakRecords();","simplified-api--简化接口#Simplified API / 简化接口":"Available since v1.1.2 / v1.1.2 起可用\n// Extension method on Object / Object 上的扩展方法\nmyBloc.trackMemoryLeak(tag: 'HomePage_myBloc');\n// Top-level function / 顶层函数\ntrackMemoryLeak(myBloc, tag: 'HomePage_myBloc');\n// Cancel tracking / 取消追踪\nmyBloc.untrackMemoryLeak();\nSee Memory Viewer for full feature details.完整功能详情见 Memory Viewer。","flutter-memoryallocations-bridge--官方泄漏追踪桥接#Flutter MemoryAllocations Bridge / 官方泄漏追踪桥接":"Available since v1.9.0 / v1.9.0 起可用\nThe leak detector's own WeakReference state machine has a blind spot: an object whose dispose() ran but which has not been GC'd yet still resolves through the weak reference and is flagged as a suspected leak (false positive). LeakTrackerBridge subscribes to Flutter's official FlutterMemoryAllocations (the data source behind leak_tracker) as a second source: once the official stream reports disposed, the object is treated as released (awaiting GC) even if the weak reference still resolves — cutting false positives.自研泄漏检测的 WeakReference 状态机有一个盲点:对象已调用 dispose() 但尚未被 GC 时,弱引用仍存活,会被判定为\"疑似泄漏\"(误报)。LeakTrackerBridge 订阅 Flutter 官方的 FlutterMemoryAllocations(leak_tracker 背后的数据源)作为第二来源:只要官方上报了 disposed,即便弱引用仍存活也判定为已释放(等待 GC),从而显著降低误报。\nEnabled by default via ZeroInspectorKit.init()'s enableFlutterLeakTracker / 通过 init() 的 enableFlutterLeakTracker 默认启用\nOfficial events only cover framework types that report to FlutterMemoryAllocations (Image / Picture / Layer …) — it supplements, not replaces, the custom detector / 官方事件只覆盖会上报给 FlutterMemoryAllocations 的框架类型(如 Image / Picture / Layer),因此是补充而非替代自研方案","errorservice--异常聚合服务#ErrorService / 异常聚合服务":"Available since v1.9.0 / v1.9.0 起可用\nSingleton service for aggregated error capture, extends ChangeNotifier. Hooks FlutterError.onError (keeping the default red error behavior) and dedups each exception by type + stack signature. See Errors for full feature details.异常聚合单例服务,继承 ChangeNotifier。接管 FlutterError.onError(保留默认红色报错行为),按类型 + 堆栈签名去重聚合。完整功能见 Errors。\n// Report an exception manually (gRPC / custom protocols) / 手动上报异常\nErrorService.instance.report(error, stackTrace, 'myModule');\n// Read aggregated records (newest first) / 读取聚合记录(最新在前)\nfinal ErrorRecord r = ErrorService.instance.errors.first;\n// Toggle capture / 切换抓取开关\nErrorService.instance.isEnabled = false;\n// Clear all records / 清空全部记录\nErrorService.instance.clear();\nMember\tDescription\terrors\tList — aggregated records, newest first / 聚合记录,最新在前\terrorCount\tint — aggregated record count / 聚合记录条数\tisEnabled\tbool — capture toggle (programmatic) / 抓取开关(代码控制)\treport(exception, stack, [context])\tManually report an exception / 手动上报异常\trestore(records)\tRestore persisted records (replay on launch) / 恢复持久化记录(启动回放)\tclear()\tClear all aggregated records / 清空所有聚合记录\tinstall() / uninstall()\tHook / restore FlutterError.onError / 接管 / 还原 FlutterError.onError","persistenceservice--持久化服务#PersistenceService / 持久化服务":"Available since v1.9.0 / v1.9.0 起可用\nSingleton service that async-flushes logs, network requests, and aggregated errors to a local SQLite ring buffer (zero_inspector_kit.db) so data survives app restarts. On launch, logs and aggregated errors replay into their tabs; network requests stay archived on disk for later export. Everything is guarded and degrades gracefully: if the DB is unavailable (e.g. desktop without sqflite FFI) isEnabled is false and writes become no-ops, never affecting the host app.单例服务,将日志、网络请求与聚合异常异步落盘到本地 SQLite 环形缓冲(zero_inspector_kit.db),使数据跨重启不丢。启动时日志与聚合异常回放入各自标签页;网络请求保留在磁盘存档,供之后导出。所有操作都有保护并优雅降级:数据库不可用(如桌面端未配置 sqflite FFI)时 isEnabled 为 false,写入变为空操作,绝不影响宿主应用。","tuning--调参#Tuning / 调参":"await PersistenceService.instance.init(\n maxRowsPerTable: 5000, // rows kept per table / 每表保留行数\n retention: const Duration(days: 7), // retention window / 保留时长\n flushInterval: const Duration(seconds: 2), // disk flush interval / 刷盘间隔\n);\nEnabled by default via ZeroInspectorKit.init()'s enablePersistence — the ring buffer flushes automatically, so no manual init() call is needed in normal use.通过 init() 的 enablePersistence 默认启用——环形缓冲会自动刷盘,常规使用无需手动调用 init()。\nMember\tDescription\tisEnabled\tbool — whether the DB is available / 数据库是否可用\tmaxRowsPerTable\tint — row cap per table (ring-buffer trim line) / 每表行数上限\tenqueueLog(e) / enqueueNetwork(r) / enqueueError(e)\tEnqueue an item for async flush / 入队待异步落盘\tflush()\tFlush the buffer to disk and trim / 刷盘并按环形缓冲裁剪\tloadLogs() / loadErrors() / loadNetworkJson()\tLoad persisted data, newest first / 读取已持久化数据(最新在前)\tbuildSessionArchiveJson()\tBuild the full-session archive JSON (logs + errors + network) / 构建完整会话存档 JSON\texportSessionArchiveAndShare()\tExport & share the full-session archive via the system share sheet / 导出并分享完整会话存档\tclearAll()\tClear persisted data on disk / 清空磁盘上的持久化数据\tdispose()\tFlush then close / 先刷盘再关闭","persisted-data-manager--持久化数据管理#Persisted Data Manager / 持久化数据管理":"Tap the storage icon in the panel header to open the Persisted data sheet: it shows the current row counts per category (logs / network / errors), the per-table cap, and whether the data will be replayed on the next launch. Actions:点击面板头部的存储图标打开 Persisted data 管理弹层:展示各类别当前行数(logs / network / errors)、每表上限,以及下次启动是否会回放。支持的操作:\nExport & share session archive — share a JSON snapshot of the whole session / 导出并分享会话存档——分享本次会话完整 JSON 快照\nClear disk — wipe persisted rows so the next launch replays nothing / 清空磁盘——清空持久化数据,下次启动干净\nClear disk & lists — wipe disk and the in-memory lists together / 同时清空磁盘与列表——连同内存列表一并清空","fpsservice--fps-监控服务#FpsService / FPS 监控服务":"Available since v1.2.0 / v1.2.0 起可用\nSingleton service for FPS monitoring, extends ChangeNotifier.FPS 监控单例服务,继承 ChangeNotifier。\nFpsService.instance.start();\nFpsService.instance.stop();\nFpsService.instance.clear();\nfinal fps = FpsService.instance.currentFps;\nfinal jankRate = FpsService.instance.jankRate;\nMethod\tDescription\tstart()\tStart FPS monitoring / 开始 FPS 监控\tstop()\tStop FPS monitoring / 停止 FPS 监控\tclear()\tClear all historical data and counters / 清空所有历史数据和计数器\t\nProperty\tType\tDescription\tisRunning\tbool\tWhether monitoring is currently active / 是否正在监控\tcurrentFps\tdouble\tCurrent FPS (updated every 500ms) / 当前 FPS(每 500ms 更新)\tjankRate\tdouble\tJank rate as percentage / 卡顿率(百分比)\ttotalFrameCount\tint\tTotal frames captured / 总帧数\ttotalJankyCount\tint\tTotal janky frames (>16ms) / 总卡顿帧数(>16ms)\tlastFrameJanky\tbool\tWhether the most recent frame was janky / 最近一帧是否卡顿\tfpsHistory\tList\tRecent 60 FPS values (unmodifiable) / 最近 60 个 FPS 值(不可变)\tframeRecords\tList\tRecent frame records (unmodifiable, up to 3600) / 最近帧记录(不可变,最多 3600 条)\t\nSee FPS Viewer for full feature details.完整功能详情见 FPS Viewer。"}},"/Custom-Database-Provider":{"title":"Custom Database Provider / 自定义数据库提供者","data":{"overview--概述#Overview / 概述":"Zero Inspector Kit supports extending database inspection beyond SQLite through the DatabaseProvider interface.Zero Inspector Kit 通过 DatabaseProvider 接口支持扩展数据库检查功能到 SQLite 以外的数据库。","databaseprovider-interface--接口定义#DatabaseProvider Interface / 接口定义":"abstract class DatabaseProvider {\n /// Provider name / 提供者名称\n String get name;\n /// Get list of databases / 获取数据库列表\n Future> getDatabases();\n /// Query table data / 查询表数据\n Future queryTable(String dbPath, String tableName, {int limit = 50});\n}","data-models--数据模型#Data Models / 数据模型":"","databaseinfo#DatabaseInfo":"Field\tType\tDescription\tname\tString\tDatabase name / 数据库名称\tpath\tString\tDatabase file path / 数据库文件路径\ttables\tList\tList of tables / 表列表","tableinfo#TableInfo":"Field\tType\tDescription\tname\tString\tTable name / 表名\trowCount\tint\tRow count / 行数","queryresult#QueryResult":"Field\tType\tDescription\tcolumns\tList\tColumn names / 列名\trows\tList>\tRow data / 行数据","example-custom-provider--示例自定义提供者#Example: Custom Provider / 示例:自定义提供者":"class MyCustomDatabaseProvider implements DatabaseProvider {\n @override\n String get name => 'CustomDB';\n @override\n Future> getDatabases() async {\n // Return your custom databases / 返回你的自定义数据库列表\n return [\n DatabaseInfo(\n name: 'my_database',\n path: '/path/to/my_database',\n tables: [\n TableInfo(name: 'users', rowCount: 100),\n TableInfo(name: 'orders', rowCount: 500),\n ],\n ),\n ];\n }\n @override\n Future queryTable(\n String dbPath,\n String tableName, {\n int limit = 50,\n }) async {\n // Execute query and return results / 执行查询并返回结果\n return QueryResult(\n columns: ['id', 'name', 'value'],\n rows: [\n {'id': 1, 'name': 'item1', 'value': '100'},\n {'id': 2, 'name': 'item2', 'value': '200'},\n ],\n );\n }\n}","register-provider--注册提供者#Register Provider / 注册提供者":"// Register your custom provider / 注册自定义提供者\nDatabaseRegistry.instance.registerProvider(MyCustomDatabaseProvider());","built-in-provider--内置提供者#Built-in Provider / 内置提供者":"The plugin includes SqliteDatabaseProvider which is auto-registered when enableDatabaseScan is true (default).插件内置 SqliteDatabaseProvider,当 enableDatabaseScan 为 true(默认)时自动注册。You can also register it manually:也可以手动注册:\nDatabaseRegistry.instance.registerProvider(SqliteDatabaseProvider());"}},"/Database-Viewer":{"title":"Database Viewer / 数据库查看器","data":{"overview--概述#Overview / 概述":"The Database Viewer inspects SQLite databases in your app with a two-level navigation system.数据库查看器通过双层导航系统检查应用中的 SQLite 数据库。","auto-scan--自动扫描#Auto-Scan / 自动扫描":"The inspector automatically scans the following directories for .db and .sqlite files:检查器自动扫描以下目录中的 .db 和 .sqlite 文件:\ngetApplicationDocumentsDirectory() / 应用文档目录\ngetDatabasesPath() / 数据库目录","two-level-navigation--双层导航#Two-Level Navigation / 双层导航":"","level-1-database-list-global--第一层数据库列表全局#Level 1: Database List (Global) / 第一层:数据库列表(全局)":"Shows all discovered databases / 显示所有发现的数据库\nEach database shows name and table count / 每个数据库显示名称和表数量\nGlobal search: Search database names and table names / 全局搜索:搜索数据库名和表名","level-2-database-detail--第二层数据库详情#Level 2: Database Detail / 第二层:数据库详情":"Click a database to enter its detail view / 点击数据库进入详情视图\nLeft panel: table list with row counts / 左侧面板:表列表(含行数)\nRight panel: table data in DataTable format / 右侧面板:DataTable 格式的表数据\nBack button to return to database list / 返回按钮返回数据库列表\nIn-database search: Search table names AND all column data / 数据库内搜索:搜索表名和所有列数据","search--搜索#Search / 搜索":"Level\tSearch Scope\tGlobal (database list)\tDatabase names, table names / 数据库名、表名\tIn-database\tTable names, all column values in all tables / 表名、所有表的所有列值","search-highlights--搜索高亮#Search Highlights / 搜索高亮":"When searching within a database, matching cell values are highlighted in accent color.在数据库内搜索时,匹配的单元格值以强调色高亮显示。","ui-features--ui-功能#UI Features / UI 功能":"Color-coded table icons / 带颜色的表图标\nRow count badges for each table / 每个表的行数徽章\nSelected table highlighted / 选中表高亮\nHorizontal and vertical scrollable data table / 水平和垂直可滚动的数据表\nRow count display (filtered / total) when searching / 搜索时显示行数(过滤/总数)","custom-database-provider--自定义数据库提供者#Custom Database Provider / 自定义数据库提供者":"For non-SQLite databases, see Custom Database Provider.对于非 SQLite 数据库,请参阅 自定义数据库提供者。"}},"/Errors":{"title":"Errors / 异常聚合查看器","data":{"overview--概述#Overview / 概述":"Available since v1.9.0v1.9.0 起可用\nThe Errors tab surfaces \"the same crash happening repeatedly\". When the inspector is running, ErrorService hooks both FlutterError.onError and PlatformDispatcher.onError (keeping the default red-screen / console behavior for both) and aggregates every exception by type + stack signature. Repeated instances of the same crash merge into one record that shows how many times it occurred and when it was first/last seen — instead of flooding the log with hundreds of identical stack traces.Errors 标签页用来快速发现\"同一处崩溃反复出现\"的问题。检查器运行时,ErrorService 会接管 FlutterError.onError(保留默认的红色报错与控制台行为),把每次异常按类型 + 堆栈签名去重聚合:同一处崩溃的多次发生会合并为一条记录,展示累计次数与首末次时间——而不是用成百上千条相同的堆栈刷屏日志。","what-gets-captured--捕获来源#What Gets Captured / 捕获来源":"Source / 来源\tHow / 方式\tFlutter framework errors / Flutter 框架异常\tHooks FlutterError.onError (default handler kept) / 接管 FlutterError.onError(保留默认处理)\tFramework-boundary & platform-channel errors / 框架边界外与平台通道异常\tPlatformDispatcher.onError (with save/restore) / PlatformDispatcher.onError(接管 + 还原)\tUncaught async errors / 未捕获异步异常\trunZonedGuarded inside runAppWithInspector() / runAppWithInspector() 内部的 runZonedGuarded\tManual reports / 手动上报\tErrorService.instance.report(exception, stackTrace) — for gRPC / custom protocols / your own error paths / 用于 gRPC / 自定义协议或你自己的错误通道\t\nCaptured records are also persisted to disk (see Session Persistence below), so aggregated errors from previous sessions are replayed on the next launch.捕获到的记录也会落盘持久化(见下文会话持久化),下次启动时会回放上次会话的聚合异常。","deduplication--去重规则#Deduplication / 去重规则":"Records are keyed by exception type plus a stack signature (first frames with line/address noise stripped) / 按异常类型 + 堆栈签名(取前若干帧并去掉行号/地址噪声)去重\nA new occurrence of an existing signature bumps its count and updates lastSeen instead of adding a new row / 同签名的再次发生只累加 count 并更新 lastSeen,不新增行\nRing buffer capped at 200 aggregated records (oldest dropped) / 环形缓冲上限 200 条聚合记录(超出丢弃最旧)","ui-features--ui-功能#UI Features / UI 功能":"","errors-tab--errors-标签页#Errors Tab / Errors 标签页":"Lists aggregated errors newest-first, showing the exception type, message, first seen / last seen time, and a ×N badge when the same crash recurred / 按最新在前列出聚合异常,展示异常类型、消息、首次/末次出现时间,同崩溃复发时显示 ×N 徽章\nTap a row to expand the full stack sample (truncated at 8000 chars); tap again to collapse / 点击行展开完整堆栈样本(8000 字符截断);再点收起\nCopy stack per row / 行内复制堆栈\nSearch filters by exception type or message / 搜索按异常类型或消息过滤\nClear empties the aggregated list / 清除清空聚合列表","tab-badge--标签页红点#Tab Badge / 标签页红点":"The Errors tab icon shows a red count badge while there are aggregated errors (capped at 99+), so recurring crashes are visible at a glance / Errors 标签页图标在有聚合异常时显示红色计数(99+ 封顶),一眼可见反复崩溃\nOpening the Errors tab is not required to see the badge — it reflects ErrorService.instance.errorCount whenever the panel is open / 只要面板打开,标签红点即反映 ErrorService.instance.errorCount,无需先进入 Errors 页\nNote: the floating ball itself stays clean — its red count (if any) is the Alerts unread badge (AlertService), not error aggregation.注意:悬浮球本身保持纯粹——球上的红色数字(如有)来自告警未读数(AlertService),与异常聚合无关。","api--接口#API / 接口":"The full service is re-exported from the package root — no need to import lib/src/.完整服务已从包根导出,无需 import lib/src/。\nimport 'package:zero_inspector_kit/zero_inspector_kit.dart';\n// Report an exception manually (gRPC/custom protocol/your own error paths) / 手动上报异常\nErrorService.instance.report(error, stackTrace, 'myModule');\n// Read the aggregated view (newest first) / 读取聚合视图(最新在前)\nfinal ErrorRecord latest = ErrorService.instance.errors.first;\nprint('${latest.type} x${latest.count} — first ${latest.firstSeen} / last ${latest.lastSeen}');\n// Clear all records / 清空所有记录\nErrorService.instance.clear();\nMember\tDescription\terrors\tList — aggregated records, newest first / 聚合记录,最新在前\terrorCount\tint — number of aggregated records / 聚合记录条数\tisEnabled\tbool — capture toggle (programmatic) / 抓取开关(代码控制)\treport(exception, stack, [context])\tManually report an exception / 手动上报异常\trestore(records)\tRestore persisted records (replay on launch; existing dedup ids are skipped) / 恢复持久化记录(启动回放;已存在的去重 id 跳过)\tclear()\tClear all aggregated records / 清空全部记录\tinstall() / uninstall()\tHook / restore FlutterError.onError and PlatformDispatcher.onError / 接管 / 还原 FlutterError.onError 与 PlatformDispatcher.onError","errorrecord--异常记录#ErrorRecord / 异常记录":"Field\tDescription\ttype\tException type name (e.g. _TypeError) / 异常类型名\tmessage\tException message / 异常消息\tcount\tTotal occurrences / 累计出现次数\tfirstSeen / lastSeen\tFirst / last occurrence time / 首次 / 末次出现时间\tsampleStack\tOne full stack sample (may be truncated) / 一条完整堆栈样本(可能截断)","session-persistence--会话持久化#Session Persistence / 会话持久化":"Errors — together with logs, network requests and alerts — are asynchronously flushed to a local SQLite ring buffer (zero_inspector_kit.db). On the next launch, logs, aggregated errors and alerts replay into their tabs, so a crash you saw yesterday is still inspectable today even though the panel was never opened; network requests stay archived on disk for later export. Use the storage icon in the panel header to open the Persisted data manager: see the current row counts, export the full session archive as JSON, or clear the disk. See Configuration (PersistenceService section) for details and the tuning parameters.异常与日志、网络请求、告警一起被异步写入本地 SQLite 环形缓冲(zero_inspector_kit.db)。下次启动时,日志、聚合异常与告警会回放入各自标签页——即使昨天从没打开过面板,今天依然能复盘当时的崩溃现场;网络请求保留在磁盘存档,供之后导出。点击面板头部的存储图标可打开 Persisted data 管理弹层:查看当前行数、导出完整会话存档 JSON,或清空磁盘。详见 Configuration(PersistenceService 一节)的参数说明。","enable--disable--开关#Enable / Disable / 开关":"enableErrorCapture (default true) in ZeroInspectorKit.init() controls error aggregation / init() 的 enableErrorCapture(默认 true)控制异常聚合\nenablePersistence (default true) controls the disk ring buffer / enablePersistence(默认 true)控制磁盘环形缓冲\nSet both to false to keep everything in memory only / 两者都设为 false 时数据仅保留在内存\nZeroInspectorKit.init(\n enableErrorCapture: true,\n enablePersistence: true, // false → memory-only / 关闭后仅内存\n);","related--相关#Related / 相关":"Log Viewer — raw log stream including error lines / 原始日志流(含错误行)\nConfiguration — init parameters and full service APIs / 初始化参数与完整服务接口\nUsage — general usage guide / 使用指南"}},"/FAQ":{"title":"FAQ / 常见问题","data":{"general--通用#General / 通用":"","q-does-the-inspector-affect-production-builds--检查器会影响生产构建吗#Q: Does the inspector affect production builds? / 检查器会影响生产构建吗?":"A: No. The inspector is automatically disabled in release mode via kReleaseMode. Flutter's tree-shaking removes all inspector-related code from production builds. You don't need to remove any code.不会。 检查器在 release 模式下通过 kReleaseMode 自动禁用。Flutter 的 tree-shaking 会移除所有检查器相关代码,无需手动移除。","q-what-platforms-are-supported--支持哪些平台#Q: What platforms are supported? / 支持哪些平台?":"A: Android and iOS.支持 Android 和 iOS。","q-what-flutterdart-versions-are-required--需要什么-flutterdart-版本#Q: What Flutter/Dart versions are required? / 需要什么 Flutter/Dart 版本?":"A: Flutter >= 3.3.0, Dart SDK >= 3.11.0 < 4.0.0.Flutter >= 3.3.0,Dart SDK >= 3.11.0 < 4.0.0。","network--网络#Network / 网络":"","q-do-i-need-to-manually-add-interceptors-for-dio--需要为-dio-手动添加拦截器吗#Q: Do I need to manually add interceptors for Dio? / 需要为 Dio 手动添加拦截器吗?":"A: No. Dio uses IOHttpClientAdapter internally, which uses dart:io's HttpClient. The inspector captures all requests via HttpOverrides automatically, making it truly zero-invasion for both http package and Dio.不需要。 Dio 内部使用 IOHttpClientAdapter,底层使用 dart:io 的 HttpClient。检查器通过 HttpOverrides 自动捕获所有请求,对 http 包和 Dio 都是真正的零侵入。","q-why-do-i-see-duplicate-dio-requests--为什么看到重复的-dio-请求#Q: Why do I see duplicate Dio requests? / 为什么看到重复的 Dio 请求?":"A: If you use both InspectorDioInterceptor and the auto-capture (HttpOverrides), Dio requests will be recorded twice. Simply remove the manual InspectorDioInterceptor — auto-capture handles everything.如果同时使用了 InspectorDioInterceptor 和自动捕获(HttpOverrides),Dio 请求会被记录两次。移除手动 InspectorDioInterceptor 即可,自动捕获会处理一切。","q-can-i-modify-requests-during-testing--测试时可以修改请求吗#Q: Can I modify requests during testing? / 测试时可以修改请求吗?":"A: Yes (since v1.0.7). The inspector supports intercepting and modifying requests via rules. Open a request detail and tap the Interceptor icon to configure a rule. You can modify the request body and request headers only — response fields (status code, response body) are read-only (since v1.0.8). GET requests cannot be modified (no request body). The master toggle is on the Network list page; rules only apply when modification mode is enabled.可以(v1.0.7 起)。检查器支持通过规则拦截并修改请求。打开请求详情,点击拦截器图标配置规则。仅可修改请求体和请求头——响应字段(状态码、响应体)只读(v1.0.8 起)。GET 请求不可修改(无请求体)。总开关在 Network 列表页,规则仅在启用修改模式时生效。See Network Inspector > Request Interceptor for details.详见 Network Inspector > 请求拦截修改。","logging--日志#Logging / 日志":"","q-can-i-use-my-existing-logging-library--可以使用现有的日志库吗#Q: Can I use my existing logging library? / 可以使用现有的日志库吗?":"A: Yes! The inspector automatically captures logs from any library that uses print() or debugPrint(). No configuration needed. Third-party logs are categorized as Info level.可以! 检查器会自动捕获任何使用 print() 或 debugPrint() 的日志库的日志,无需配置。第三方日志统一归类为 Info 级别。","q-how-to-sync-inspector-logs-to-my-logger--如何将检查器日志同步到我的日志库#Q: How to sync inspector logs to my logger? / 如何将检查器日志同步到我的日志库?":"A: Use the onLogCaptured callback:使用 onLogCaptured 回调:\nInspectorLogInterceptor.instance.onLogCaptured = (entry) {\n yourLogger.log(entry.message);\n};\nWarning: Do NOT call print() or logging methods inside this callback, as it will cause infinite recursion.\n警告:不要在此回调中调用 print() 或日志方法,否则会导致无限递归。","database--数据库#Database / 数据库":"","q-why-dont-i-see-any-databases--为什么看不到数据库#Q: Why don't I see any databases? / 为什么看不到数据库?":"A: The inspector scans getApplicationDocumentsDirectory() and getDatabasesPath() for .db and .sqlite files. If your database is stored elsewhere, you can implement a custom DatabaseProvider.检查器扫描 getApplicationDocumentsDirectory() 和 getDatabasesPath() 目录中的 .db 和 .sqlite 文件。如果你的数据库存储在其他位置,可以实现自定义 DatabaseProvider。","memory--内存#Memory / 内存":"","q-why-does-dart-heap-show-na-when-debugging-via-pc--通过-pc-调试时为什么-dart-heap-显示-na#Q: Why does Dart Heap show \"N/A\" when debugging via PC? / 通过 PC 调试时为什么 Dart Heap 显示 \"N/A\"?":"A: This is an expected behavior. When using flutter run to debug via PC, the flutter tool sets up port forwarding between PC and device via adb reverse, allowing PC-side DevTools to access the device's VM Service. However, Service.getInfo() returns a serverUri from the PC's perspective; when the app process internally accesses 127.0.0.1:PC_port, the device doesn't have that port listening locally, resulting in Connection refused and VM Service showing OFF.这是预期行为。 使用 flutter run 连接 PC 调试时,flutter tool 会通过 adb reverse 在 PC 和设备之间做端口转发,让 PC 上的 DevTools 能访问设备的 VM Service。但应用进程内部 Service.getInfo() 返回的 serverUri 是 PC 视角的端口,应用进程访问 127.0.0.1:PC端口 时设备本地并没有监听该端口,导致 Connection refused,VM Service 显示 OFF。Workarounds / 解决方案:\nOpen the debug app directly without PC connection — VM Service works normally / 直接打开 debug 应用(不连 PC),VM Service 正常工作\nUse Native memory data (Android PSS / iOS physicalFootprint) as fallback — always available / 使用 Native 内存数据(Android PSS / iOS physicalFootprint)作为降级,始终可用\nProcess RSS is always available regardless of VM Service / 进程 RSS 无论 VM Service 状态都可用\nSee Memory Viewer > VM Service Availability for details.详见 Memory Viewer > VM Service 可用性。","q-does-memory-monitoring-affect-performance--内存监控会影响性能吗#Q: Does memory monitoring affect performance? / 内存监控会影响性能吗?":"A: Memory monitoring is off by default (since v1.1.0). When disabled, all timers are stopped and VM Service connection is cleared, leaving zero overhead. When enabled, refresh intervals are optimized:内存监控默认关闭(v1.1.0 起)。关闭时所有定时器停止、VM Service 连接清空,零开销。开启时刷新间隔已优化:\nProcess RSS / Dart Heap: 500ms\nNative Memory: 3000ms\nStorage Stats: 3000ms\nLeak Detection: 2000ms\nYou can toggle the switch at the top of the Memory panel anytime.可随时在 Memory 面板顶部切换开关。","q-does-leak-detection-require-code-modification--泄漏检测需要侵入代码吗#Q: Does leak detection require code modification? / 泄漏检测需要侵入代码吗?":"A: The current implementation uses WeakReference + Finalizer, which requires calling trackObject() to register objects for tracking. This is a mild invasion (one line of code per tracked object).当前实现使用 WeakReference + Finalizer,需要调用 trackObject() 注册要追踪的对象。这是轻度侵入(每个追踪对象一行代码)。Trade-offs / 取舍:\nPros: 100% reliable, doesn't depend on VM Service, works in release mode / 100% 可靠,不依赖 VM Service,release 模式也可用\nCons: Requires user to know which objects to track / 需要用户知道要追踪哪些对象\nA zero-invasion Heap Snapshot comparison feature (based on VM Service) is technically possible but would strongly depend on VM Service availability (unavailable when debugging via PC), with significant performance overhead. Not currently implemented.零侵入的 Heap Snapshot 对比功能(基于 VM Service)技术上可行,但会强依赖 VM Service 可用性(PC 调试时不可用),且有显著性能开销。目前未实现。See Memory Viewer > Memory Leak Detection for API details.详见 Memory Viewer > 内存泄漏检测 了解 API 详情。","fps--帧率#FPS / 帧率":"","q-why-does-fps-show-a-very-low-value-eg-9-10-fps--为什么-fps-显示很低如-9-10#Q: Why does FPS show a very low value (e.g. 9-10 FPS)? / 为什么 FPS 显示很低(如 9-10)?":"A: This was a bug fixed in v1.2.0. The root cause was that _recentFrameTimestamps.add(now) was placed outside the for-loop in _onFrameTimings; since Flutter engine's addTimingsCallback is batched (may return multiple frames per call), only one timestamp was recorded per batch, undercounting FPS by 6-10x. Upgrade to ^1.2.0 to fix this.这是 v1.2.0 已修复的 bug。根因是 _onFrameTimings 中 _recentFrameTimestamps.add(now) 在 for 循环外;Flutter 引擎的 addTimingsCallback 是批量回调(一次可能返回多帧),但每批只记录 1 个时间戳,导致 FPS 计算偏低 6-10 倍。升级到 ^1.2.0 即可修复。See FPS Viewer for details.详见 FPS Viewer。","q-does-fps-monitoring-affect-performance--fps-监控会影响性能吗#Q: Does FPS monitoring affect performance? / FPS 监控会影响性能吗?":"A: FPS monitoring is off by default (since v1.2.0). When disabled, no frame timings callbacks and no timers — zero overhead. When enabled, it only processes lightweight frame timing data with bounded history (60 trend points, up to 3600 frame records). You can toggle the switch at the top of the FPS panel anytime.FPS 监控默认关闭(v1.2.0 起)。关闭时无帧回调、无定时器——零开销。开启时仅处理轻量帧时序数据,历史有界(趋势 60 个点,帧记录最多 3600 条)。可随时在 FPS 面板顶部切换开关。","ui--界面#UI / 界面":"","q-the-inspector-panel-gets-pushed-up-when-keyboard-appears--键盘弹出时检查器面板被顶起来了#Q: The inspector panel gets pushed up when keyboard appears. / 键盘弹出时检查器面板被顶起来了。":"A: This was fixed in v1.0.6. The floating button and panel are rendered via Overlay, which is independent of the page layout and not affected by keyboard.此问题已在 v1.0.6 修复。悬浮按钮和面板通过 Overlay 渲染,独立于页面布局,不受键盘影响。","q-how-to-use-search--如何使用搜索#Q: How to use search? / 如何使用搜索?":"A: Each viewer has its own search bar at the top. Database viewer has two-level search: global search (database list) and in-database search (table names + all column data).每个查看器顶部都有搜索栏。数据库查看器有双层搜索:全局搜索(数据库列表)和数据库内搜索(表名 + 所有列数据)。","q-the-floating-button-disappeared--cant-be-opened-after-enabling-fps--开启-fps-后悬浮按钮消失无法打开#Q: The floating button disappeared / can't be opened after enabling FPS. / 开启 FPS 后悬浮按钮消失/无法打开。":"A: This was a bug fixed in v1.2.0. The root cause was that FpsService.notifyListeners() triggered Overlay rebuild; combined with the original Overlay lookup failure (using Overlay.of(context, rootOverlay: true) which returned null), the button State was destroyed and could not recover. Upgrade to ^1.2.0 which uses navigatorState.overlay instead.这是 v1.2.0 已修复的 bug。根因是 FpsService.notifyListeners() 触发 Overlay 重建,叠加原 Overlay 查找失败(使用 Overlay.of(context, rootOverlay: true) 返回 null),导致按钮 State 被销毁且无法恢复。升级到 ^1.2.0,改用 navigatorState.overlay 即可。","q-how-does-the-floating-button-edge-docking-work--悬浮按钮的边缘吸附怎么用#Q: How does the floating button edge docking work? / 悬浮按钮的边缘吸附怎么用?":"A: Since v1.2.0, when you drag the button near a screen edge and release, it auto-docks and tucks into the edge, leaving only a 24px peek visible. The icon becomes a directional chevron hinting you can tap to pull it out:\nTap the peek → smoothly pulls out to fully visible (panel NOT opened, avoids accidental open) / 平滑拉出到完全可见(不打开面板,避免误触)\nTap when fully visible → opens the inspector panel / 打开检查器面板\nThis design avoids conflicts with system back gestures (Android/iOS edge swipe to go back) when pulling out from the docked state.v1.2.0 起,拖动按钮靠近屏幕边缘松手即自动吸附并\"收入\"边缘,仅露 24px。图标变为方向箭头,提示可点击拉出:\n点击露出部分 → 平滑拉出到完全可见(不打开面板,避免误触)\n完全可见时点击 → 打开检查器面板\n此设计避免了从吸附态拖出时与系统返回手势(Android/iOS 边缘右滑退出)的冲突。See Usage > Edge Docking for details.详见 Usage > 边缘吸附。","license--许可证#License / 许可证":"","q-can-i-use-this-in-a-commercial-project--可以在商业项目中使用吗#Q: Can I use this in a commercial project? / 可以在商业项目中使用吗?":"A: Yes. This project is licensed under MPL-2.0. You can use it in closed-source commercial apps — if you use it unmodified, no source disclosure is required. If you modify the plugin's source files, those modified files must be published in source form under MPL-2.0 (your own app still does not need to be open-sourced).可以。本项目采用 MPL-2.0 许可证,可用于闭源商业 App——未修改直接使用时无需公开任何源码;若修改了插件源文件,被修改的文件必须以 MPL-2.0 公开源码(你自己的 App 仍无需开源)。","q-are-you-liable-for-issues-in-modified-versions--修改版出问题你们负责吗#Q: Are you liable for issues in modified versions? / 修改版出问题你们负责吗?":"A: This plugin is provided \"as is\", without warranty of any kind. The author assumes no responsibility or liability for the functionality, security, or any consequences arising from the use of modified versions or derivative projects.本插件按\"原样\"提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及任何使用后果承担责任。"}},"/FPS-Viewer":{"title":"FPS Viewer / FPS 监控面板","data":{"":"The FPS Viewer provides real-time frame performance analysis, including current FPS, jank rate, frame duration stats, and an FPS trend chart.FPS 监控面板提供实时帧性能分析,包括当前 FPS、卡顿率、帧耗时统计和 FPS 趋势图。\nAvailable since v1.2.0v1.2.0 起可用","overview--概览#Overview / 概览":"The FPS Viewer is integrated into the inspector panel. Tap the floating inspector button → \"FPS\" tab to access.FPS 监控面板集成在检查器面板中。点击悬浮检查器按钮 → \"FPS\" 标签即可访问。⚠️ Important: FPS monitoring is off by default. You must toggle on the switch at the top of the panel to start collecting data.⚠️ 重要:FPS 监控默认关闭。必须在面板顶部打开开关才会开始采集数据。","master-switch--总开关#Master Switch / 总开关":"A switch at the top of the FPS panel controls whether monitoring is enabled.FPS 面板顶部有一个开关,控制是否启用监控。\nState\tBehavior\tOFF (default)\tNo frame timings callbacks, no timer, zero overhead / 无帧回调、无定时器、零开销\tON\tStarts collecting frame data via WidgetsBinding.instance.addTimingsCallback / 通过 addTimingsCallback 开始采集帧数据\t\nWhen turned off, all callbacks and timers are cancelled, leaving no residual overhead.关闭时所有回调和定时器会被取消,不会残留开销。","features--功能#Features / 功能":"","1-current-stats--当前统计#1. Current Stats / 当前统计":"Current FPS — Updated every 500ms / 每 500ms 更新\nJank Rate — Percentage of janky frames (duration exceeds the per-frame budget, derived from the display refresh rate) / 卡顿帧占比(帧耗时超过由屏幕刷新率换算的逐帧预算)\nTotal Frame Count — All frames captured since start / 自启动以来的总帧数\nTotal Janky Count — All janky frames captured / 自启动以来的总卡顿帧数\nLast Frame Janky — Whether the most recent frame was janky / 最近一帧是否卡顿\n显示当前 FPS、卡顿率、总帧数、总卡顿帧数、最近一帧是否卡顿。","2-fps-trend-chart--fps-趋势图#2. FPS Trend Chart / FPS 趋势图":"Real-time line chart with 30-second history window (60 data points × 500ms)\nY-axis dynamically scales to max(60, maxFps) to avoid overflow when FPS spikes\nJanky frame region marked in red\nTime axis labels: -30s / -15s / Now\n实时折线图,30 秒历史窗口(60 个数据点 × 500ms)。Y 轴动态缩放为 max(60, maxFps) 避免 FPS 飙升时溢出。卡顿区域以红色标记。","3-janky-frame-list--卡顿帧列表#3. Janky Frame List / 卡顿帧列表":"Lists janky frames — duration exceeds the per-frame budget, derived from the display refresh rate (~16.7ms at 60Hz, ~8.3ms at 120Hz) / 列出卡顿帧——帧耗时超过由屏幕刷新率换算的逐帧预算(60Hz≈16.7ms、120Hz≈8.3ms)\nEach item shows frame duration and timestamp, plus the build / raster split (buildDurationUs + rasterDurationUs) so you can tell whether build or GPU raster is the bottleneck / 每项显示帧耗时、时间戳,以及 build / raster 分项(buildDurationUs + rasterDurationUs),便于判断是构建还是 GPU 光栅化卡顿\nHelps identify specific jank spikes / 帮助定位具体卡顿点\n列出卡顿帧(帧耗时超过由屏幕刷新率换算的逐帧预算,60Hz≈16.7ms、120Hz≈8.3ms),每项显示帧耗时、时间戳与 build / raster 分项,帮助定位具体卡顿点。","4-reset--重置#4. Reset / 重置":"\"Reset\" button clears all statistics and historical data / \"Reset\" 按钮清空所有统计和历史数据\nUseful for measuring a specific interaction scenario / 适合测量特定交互场景\n\"Reset\" 按钮清空所有统计和历史数据,适合测量特定交互场景。","main-thread-blocking-watchdog--主线程阻塞看门狗#Main-thread Blocking Watchdog / 主线程阻塞看门狗":"The FPS panel also includes a main-thread blocking watchdog that catches stalls FPS cannot see.FPS 面板还内置主线程阻塞看门狗,专门捕捉 FPS 看不见的卡死。\nAvailable since v1.12.0v1.12.0 起可用\nWhy it's needed / 为什么需要: FPS only measures frames. When the UI isolate truly stalls (e.g. a synchronous heavy computation), it produces zero frames, so FPS reads as idle while the app is actually frozen. The watchdog closes that blind spot.FPS 只测帧。当 UI isolate 真正卡死(如同步重计算)时,它零帧产出,于是 FPS 显示空闲,而 App 其实已冻住。看门狗补上这个盲区。How it works / 工作原理:\nA low-frequency 100ms heartbeat measures the UI isolate's responsiveness directly / 用 100ms 低频心跳 直接测量 UI isolate 的响应间隔\nIf a gap exceeds 300ms with no response, it records a blocking event (duration / time / nearby logs) / 若超过 300ms 无响应,记录一条阻塞事件(时长 / 时间 / 附近日志)\nOff by default with its own switch (not tied to the FPS master switch) — zero overhead unless you opt in / 默认关闭、有独立开关(不随 FPS 总开关联动),不开启则零开销\n开启后,真正卡死(不产帧)也会被记成阻塞事件,配合 FPS 一起看因果。","how-it-works--工作原理#How It Works / 工作原理":"FPS monitoring uses Flutter's WidgetsBinding.instance.addTimingsCallback to receive frame timing information from the engine. The callback is batched — it may return multiple FrameTiming objects per call, so each frame is recorded individually inside the loop to ensure accurate FPS calculation.FPS 监控使用 Flutter 的 WidgetsBinding.instance.addTimingsCallback 接收引擎的帧时序信息。该回调是批量的——每次调用可能返回多个 FrameTiming 对象,因此在循环内部为每帧单独记录时间戳,确保 FPS 计算准确。Jank threshold / 卡顿阈值: A frame is janky when its total duration (build + raster) exceeds the per-frame budget — 1000ms ÷ refreshRate (≈16.7ms at 60Hz, ≈8.3ms at 120Hz). Since v1.11.0 the budget is derived from the device's refresh rate, so high-refresh (120Hz) screens are held to a tight ≈8.3ms instead of being forgiven by a fixed 16ms.卡顿阈值:帧的总耗时(build + raster)超过逐帧预算即视为卡顿——1000ms ÷ 刷新率(60Hz≈16.7ms、120Hz≈8.3ms)。自 v1.11.0 起预算按设备刷新率换算,高刷(120Hz)屏会被严格约束在 ≈8.3ms,而非被固定 16ms 放过。Phased timing / 分阶段耗时: Each FrameRecord carries buildDurationUs and rasterDurationUs (GPU raster); the janky-frame list shows the build / raster split per row.分阶段耗时:每条 FrameRecord 都带有 buildDurationUs 与 rasterDurationUs(GPU 光栅化),掉帧列表项会展示 build / raster 分项。","programmatic-control-optional--编程式控制可选#Programmatic Control (Optional) / 编程式控制(可选)":"If you need to start/stop monitoring from code (e.g., for automated testing), use FpsService:如需从代码控制监控(如自动化测试),使用 FpsService:\n// Start / Stop FPS monitoring / 开始 / 停止 FPS 监控\nFpsService.instance.start();\nFpsService.instance.stop();\n// Read current stats / 读取当前统计\nfinal fps = FpsService.instance.currentFps;\nfinal jankRate = FpsService.instance.jankRate;\nfinal totalFrames = FpsService.instance.totalFrameCount;\nfinal jankyFrames = FpsService.instance.totalJankyCount;\n// Clear historical data / 清空历史数据\nFpsService.instance.clear();\n// Listen to updates / 监听更新\nFpsService.instance.addListener(() {\n // Update your own UI / 更新你自己的 UI\n});\n// Historical data access / 历史数据访问\nfinal history = FpsService.instance.fpsHistory; // List, 60 entries / 60 条\nfinal records = FpsService.instance.frameRecords; // List, unmodifiable / 不可变,最多 3600 条","fpsservice-api--fpsservice-接口#FpsService API / FpsService 接口":"Singleton service extending ChangeNotifier.继承 ChangeNotifier 的单例服务。\nMethod\tDescription\tstart()\tStart FPS monitoring / 开始 FPS 监控\tstop()\tStop FPS monitoring / 停止 FPS 监控\tclear()\tClear all historical data and counters / 清空所有历史数据和计数器\t\nProperty\tType\tDescription\tisRunning\tbool\tWhether monitoring is currently active / 是否正在监控\tcurrentFps\tdouble\tCurrent FPS (updated every 500ms) / 当前 FPS(每 500ms 更新)\tjankRate\tdouble\tJank rate as percentage / 卡顿率(百分比)\ttotalFrameCount\tint\tTotal frames captured / 总帧数\ttotalJankyCount\tint\tTotal janky frames captured (per-frame budget) / 总卡顿帧数(按逐帧预算)\tlastFrameJanky\tbool\tWhether the most recent frame was janky / 最近一帧是否卡顿\tfpsHistory\tList\tRecent 60 FPS values (unmodifiable) / 最近 60 个 FPS 值(不可变)\tframeRecords\tList\tRecent frame records (unmodifiable, up to 3600) / 最近帧记录(不可变,最多 3600 条)","performance-considerations--性能说明#Performance Considerations / 性能说明":"Off by default / 默认关闭: Zero overhead when disabled / 关闭时零开销\nLightweight callbacks / 轻量回调: Only processes frame timing data, no heavy computation / 仅处理帧时序数据,无重计算\nBounded history / 有界历史: Trend chart keeps only 60 points, frame records capped at 3600 / 趋势图仅保留 60 个点,帧记录上限 3600 条\nSafe with other features / 与其他功能兼容: Can run alongside Memory Viewer monitoring / 可与 Memory Viewer 监控同时运行","platform-support--平台支持#Platform Support / 平台支持":"Feature\tAndroid\tiOS\tFPS Monitoring\t✅\t✅\tJank Detection\t✅\t✅\tTrend Chart\t✅\t✅\t\nFPS monitoring uses Flutter engine APIs and works on Android and iOS (the two platforms this plugin targets).FPS 监控使用 Flutter 引擎 API,在本插件支持的两个平台 Android 与 iOS 上均可用。","demo--演示#Demo / 演示":"The Example App includes an FPS demo module with three scenarios:Example App 包含 FPS 演示模块,提供三种场景:\nDemo\tDescription\tTrigger Jank\tBlocks the main thread for 100-500ms to simulate jank / 阻塞主线程 100-500ms 模拟卡顿\tHeavy Animations\t80 simultaneously rotating+scaling widgets to intentionally trigger jank / 80 个同时旋转+缩放的 widget 故意触发卡顿\tSmooth Animation\tSingle lightweight compound animation, demonstrates stable 60 FPS / 单个轻量复合动画,演示稳定 60 FPS","related--相关#Related / 相关":"Usage — General usage guide / 通用使用指南\nFAQ — Common questions / 常见问题\nConfiguration — Configuration options / 配置选项"}},"/Getting-Started":{"title":"Getting Started / 快速开始","data":{"quick-start--快速开始#Quick Start / 快速开始":"Integrate with just 1 line of code:仅需 1 行代码 即可完成集成:\nimport 'package:flutter/material.dart';\nimport 'package:zero_inspector_kit/zero_inspector_kit.dart';\nvoid main() {\n // Single line: Initialize inspector, capture print() via Zone, and display floating button\n // 一行代码:初始化检查器、通过 Zone 捕获 print()、自动显示悬浮按钮\n ZeroInspectorKit.runAppWithInspector(const MyApp());\n}\nclass MyApp extends StatelessWidget {\n const MyApp({super.key});\n @override\n Widget build(BuildContext context) {\n return MaterialApp(\n home: Scaffold(\n appBar: AppBar(title: const Text('App')),\n body: const Center(child: Text('Hello World')),\n ),\n );\n }\n}","what-happens-automatically--自动完成的工作#What Happens Automatically / 自动完成的工作":"After integration, the inspector automatically does the following without modifying any other project code:集成后,检查器会自动完成以下工作,无需修改项目其他代码:\nFeature\tDescription\t✅ Log Capture\tAuto-capture all print(), debugPrint() via Zone / 通过 Zone 自动捕获所有日志\t✅ Network Interception\tAuto-intercept all http and Dio requests via HttpOverrides / 自动拦截所有网络请求\t✅ Error Capture\tAggregate Flutter framework errors & unhandled exceptions, deduped by type + stack (Errors tab) / 聚合 Flutter 框架异常与未捕获异常,按类型+堆栈去重\t✅ Session Persistence\tFlush logs/network/errors to a disk ring buffer; logs & errors replay on next launch / 日志/网络/异常落盘环形缓冲,启动时回放日志与异常\t✅ Database Scan\tAuto-scan and register SQLite databases / 自动扫描注册数据库\t✅ Floating Button\tAuto-displayed via Overlay, not affected by keyboard / 通过 Overlay 自动显示\t✅ Route Tracking\tAuto-inject InspectorRouteObserver into MaterialApp / 自动注入路由观察者\t\nNote / 说明: The Memory and FPS panels are available as tabs but are off by default to avoid performance overhead. Toggle the switch at the top of each panel to start collecting data. See Memory Viewer and FPS Viewer for details.Memory 和 FPS 面板作为标签页可用,但默认关闭以避免性能开销。在各自面板顶部打开开关才会开始采集数据。详见 Memory Viewer 和 FPS Viewer。\nPre-initialized binding / 提前初始化的 binding: If you call WidgetsFlutterBinding.ensureInitialized() or await a platform-channel plugin (e.g. SharedPreferences) before runAppWithInspector(), the binding is already initialized. In that case runAppWithInspector() degrades gracefully to a plain runApp() (capturing debugPrint logs, but not raw print()) instead of throwing a \"Zone mismatch\" assertion. No extra setup is needed.binding 已提前初始化:如果你在 runAppWithInspector() 之前调用了 WidgetsFlutterBinding.ensureInitialized() 或 await 了会触发 platform channel 的插件(如 SharedPreferences),binding 已被初始化。此时 runAppWithInspector() 会优雅降级为普通 runApp()(仍捕获 debugPrint 日志,但不捕获原始 print()),而非抛出 \"Zone mismatch\" 断言。无需额外处理。","production-build--生产构建#Production Build / 生产构建":"The inspector is automatically disabled in release mode. You don't need to remove any code — Flutter's tree-shaking will remove all inspector-related code from production builds.检查器在 release 模式下会自动禁用,无需移除任何代码,Flutter 的 tree-shaking 会自动移除所有检查器相关代码。","alternative-integration--替代集成方式#Alternative Integration / 替代集成方式":"If you prefer more control, use the two-line approach:如果需要更多控制权,可以使用两行代码方式:\nvoid main() {\n ZeroInspectorKit.init();\n runApp(ZeroInspectorKit.wrapApp(const MyApp()));\n}","next-steps--下一步#Next Steps / 下一步":"Installation — Detailed installation methods / 详细安装方式\nUsage — Full usage guide / 完整使用指南\nConfiguration — Configuration options / 配置选项"}},"/Installation":{"title":"Installation / 安装","data":{"from-pubdev-recommended--从-pubdev-安装推荐#From pub.dev (Recommended) / 从 pub.dev 安装(推荐)":"Add the following to your pubspec.yaml:在 pubspec.yaml 中添加以下依赖:\ndependencies:\n zero_inspector_kit: ^1.12.0\nThen run:然后运行:\nflutter pub get","from-github--从-github-安装#From GitHub / 从 GitHub 安装":"Alternatively, install from GitHub:或者从 GitHub 安装:\ndependencies:\n zero_inspector_kit:\n git:\n url: https://github.com/zero-labsco/zero_inspector_kit.git\n ref: release/v1.12.0","platform-setup--平台配置#Platform Setup / 平台配置":"","android#Android":"No additional configuration needed.无需额外配置。","ios#iOS":"No additional configuration needed.无需额外配置。","import--导入#Import / 导入":"import 'package:zero_inspector_kit/zero_inspector_kit.dart';","requirements--环境要求#Requirements / 环境要求":"Requirement\tVersion\tFlutter\t>= 3.3.0\tDart SDK\t>= 3.11.0 < 4.0.0","next-steps--下一步#Next Steps / 下一步":"Getting Started — Quick start guide / 快速开始\nUsage — Full usage guide / 完整使用指南"}},"/Log-Viewer":{"title":"Log Viewer / 日志查看器","data":{"overview--概述#Overview / 概述":"The Log Viewer automatically captures logs from multiple sources with zero configuration.日志查看器自动从多个来源捕获日志,无需配置。","log-sources--日志来源#Log Sources / 日志来源":"Source\tCapture Method\tprint()\tZone specification override / Zone 规范覆盖\tdebugPrint()\tdebugPrint override / debugPrint 覆盖\tFlutter errors\tFlutterError.onError hook / 接管 FlutterError.onError\tUnhandled exceptions\trunZonedGuarded / runZonedGuarded 捕获\tThird-party libraries\tVia print() capture / 通过 print() 捕获\t\nSince v1.9.0, Flutter framework errors and unhandled exceptions are also aggregated & deduplicated in the dedicated Errors tab (keyed by type + stack signature) — while this Log Viewer keeps showing them as error-level lines in the chronological stream. Use Errors for \"is this crash repeating?\"; use Log Viewer for the raw timeline.自 v1.9.0 起,Flutter 框架异常与未捕获异常会同时进入独立的 Errors 标签页去重聚合(按类型 + 堆栈签名归并);本 Log Viewer 仍会在原始时间流中把它们显示为错误级日志。回答\"该崩溃是否反复出现\"请用 Errors;查看原始时间线请用 Log Viewer。","log-levels--日志级别#Log Levels / 日志级别":"Level\tAbbreviation\tColor\tDescription\tVerbose\tV\tGray\tDetailed information / 详细信息\tDebug\tD\tBlue\tDebug information / 调试信息\tInfo\tI\tGreen\tGeneral information / 一般信息\tWarning\tW\tOrange\tWarning messages / 警告信息\tError\tE\tRed\tError messages / 错误信息\t\nThird-party library logs are categorized as Info level.\n第三方日志库的日志统一归类为 Info 级别。","ui-features--ui-功能#UI Features / UI 功能":"","filter-bar--过滤栏#Filter Bar / 过滤栏":"All: Show all logs / 使用 All 显示所有日志\nV / D / I / W / E: Filter by level / 按级别过滤\nTag dropdown: Filter by any captured tag / 按任意已捕获标签过滤\nSingle-select mode / 单选模式","toolbar--工具栏#Toolbar / 工具栏":"Auto-scroll toggle (default on): Jump to the newest log as new entries arrive; pause to keep history still for inspection / 自动滚动开关(默认开启):新日志到达时自动跳到最新;暂停可稳定查看历史\nCopy as JSON: Copy all currently filtered logs as JSON / 复制为 JSON:将当前过滤后的全部日志复制为 JSON\nShare as Text: Share filtered logs as plain text / 分享为文本:将过滤后的日志以纯文本分享\nClear: Clear all logs / 清除:清空全部日志","log-list--日志列表#Log List / 日志列表":"Level badge with color / 带颜色的级别徽章\nTimestamp display / 时间戳显示\nTag display (if available) / 标签显示(如有)\nError/warning rows have subtle background tint / 错误/警告行有淡色背景\nLeft border color indicates level / 左侧边框颜色表示级别\nTap a row to open the in-view detail page with full message and copy; use the back button to return / 点击行进入详情页(主视图内切换),可查看完整消息并复制,点返回按钮回到列表\nPer-row copy button copies that single log as JSON / 行内复制按钮将该条日志以 JSON 复制","search--搜索#Search / 搜索":"Fuzzy search by message content or tag / 按消息内容或标签模糊搜索\nToggle regex mode (. * button) to search with RegExp (case-insensitive); invalid patterns degrade gracefully instead of crashing / 点击 正则模式(. * 按钮)使用 RegExp 搜索(不区分大小写);非法图案优雅降级,不会崩溃\nCombined with level and tag filters / 可与级别、标签过滤组合使用","manual-logging--手动记录日志#Manual Logging / 手动记录日志":"Quick shorthand (recommended) / 简化写法(推荐) — available since v1.1.2 / v1.1.2 起可用:\nInspectorLog.v('Verbose message / 详细消息');\nInspectorLog.d('Debug message / 调试消息');\nInspectorLog.i('Info message / 信息消息');\nInspectorLog.w('Warning message / 警告消息');\nInspectorLog.e('Error message / 错误消息');\n// With tag / 带标签\nInspectorLog.i('User logged in', tag: 'Auth');\nFull form / 完整写法:\nInspectorLogInterceptor.instance.verbose('Verbose message / 详细消息');\nInspectorLogInterceptor.instance.debug('Debug message / 调试消息');\nInspectorLogInterceptor.instance.info('Info message / 信息消息');\nInspectorLogInterceptor.instance.warning('Warning message / 警告消息');\nInspectorLogInterceptor.instance.error('Error message / 错误消息');\n// With tag / 带标签\nInspectorLogInterceptor.instance.info('User logged in', tag: 'Auth');\nInspectorLog is a static wrapper around InspectorLogInterceptor.instance for shorter log calls.InspectorLog 是 InspectorLogInterceptor.instance 的静态包装,用于更简短的日志调用。","third-party-library-integration--第三方日志库集成#Third-Party Library Integration / 第三方日志库集成":"","auto-capture-inbound--自动捕获入站#Auto-Capture (Inbound) / 自动捕获(入站)":"No configuration needed. Any library using print() or debugPrint() is automatically captured.无需配置。任何使用 print() 或 debugPrint() 的库都会被自动捕获。","bidirectional-sync-optional--双向同步可选#Bidirectional Sync (Optional) / 双向同步(可选)":"To forward inspector logs to your third-party logger:将检查器日志转发到第三方日志库:\nimport 'package:logger/logger.dart';\nfinal logger = Logger();\nInspectorLogInterceptor.instance.onLogCaptured = (entry) {\n logger.log(\n _mapLogLevel(entry.level),\n '${entry.tag != null ? '[${entry.tag}] ' : ''}${entry.message}',\n );\n};\nNote: Do NOT call logging methods inside onLogCaptured, as this will cause infinite recursion.\n注意:不要在 onLogCaptured 内部调用日志方法,否则会导致无限递归。","starting-the-log-interceptor--启动日志拦截器#Starting the Log Interceptor / 启动日志拦截器":"If using runAppWithInspector(), the log interceptor starts automatically. Otherwise:如果使用 runAppWithInspector(),日志拦截器会自动启动。否则:\nInspectorLogInterceptor.instance.start();"}},"/Memory-Viewer":{"title":"Memory Viewer / 内存监控面板","data":{"":"The Memory Viewer provides comprehensive in-app memory analysis, including trend charts, Dart Heap details, Native memory breakdown, memory leak detection, image cache monitoring, and storage statistics.内存监控面板提供应用内全面内存分析,包括趋势图、Dart Heap 详情、Native 内存分项、内存泄漏检测、图片缓存监控和存储统计。\nAvailable since v1.1.0v1.1.1 起可用","overview--概览#Overview / 概览":"The Memory Viewer is integrated into the inspector panel. Tap the floating inspector button → \"Memory\" tab to access.内存监控面板集成在检查器面板中。点击悬浮检查器按钮 → \"Memory\" 标签即可访问。⚠️ Important: Memory monitoring is off by default. You must toggle on the switch at the top of the panel to start collecting data.⚠️ 重要:内存监控默认关闭。必须在面板顶部打开开关才会开始采集数据。","master-switch--总开关#Master Switch / 总开关":"A switch at the top of the Memory panel controls whether monitoring is enabled.Memory 面板顶部有一个开关,控制是否启用监控。\nState\tBehavior\tOFF (default)\tNo timers, no VM Service connection, zero overhead / 无定时器、无 VM Service 连接、零开销\tON\tStarts RSS collection (500ms), Native memory (3s), storage stats (3s), leak detection (2s), and VM Service connection attempt / 启动 RSS 采集(500ms)、Native 内存(3s)、存储统计(3s)、泄漏检测(2s),并尝试连接 VM Service\t\nWhen turned off, all timers are cancelled and the VM Service connection state is cleared, leaving no residual WebSocket overhead.关闭时所有定时器会被取消,VM Service 连接状态会被清空,不会残留 WebSocket 开销。","features--功能#Features / 功能":"","1-memory-trend-chart--内存趋势图#1. Memory Trend Chart / 内存趋势图":"Real-time line chart with 2-minute history window (240 snapshots × 500ms)\nSwitchable between 4 metrics via the metric selector chips:\nProcess RSS — Process-level RSS (always available)\nDart Heap — Total Dart Heap usage (requires VM Service)\nNew Space — New generation usage (requires VM Service)\nOld Space — Old generation usage (requires VM Service)\n实时折线图,2 分钟历史窗口(240 条快照 × 500ms)。可通过指标选择芯片切换 4 种指标。","2-native-memory--native-内存真机-100-可用#2. Native Memory / Native 内存(真机 100% 可用)":"Does not depend on VM Service. Data is collected via Platform Channel:不依赖 VM Service,通过 Platform Channel 采集:Android (Debug.MemoryInfo + /proc/self/status):\nTotal PSS / Dalvik PSS / Native PSS / Native Private Dirty\nProcess RSS (from /proc/self/status)\nDevice memory status (availMem, threshold, lowMemory)\niOS (mach task_info):\nPhysical Footprint (Apple's recommended metric)\nInternal / Compressed / Resident Size\nProcess RSS\nDevice available memory","3-dart-heap-overview--dart-heap-概览需要-vm-service#3. Dart Heap Overview / Dart Heap 概览(需要 VM Service)":"Heap Usage / Capacity / external usage\nProgress bar showing heap usage ratio\nThree core metrics with bilingual labels\n显示 Heap Usage / Capacity / External 三个核心指标 + 进度条。","4-heap-generations--堆分代详情需要-vm-service#4. Heap Generations / 堆分代详情(需要 VM Service)":"New Space: usage / capacity / external\nOld Space: usage / capacity / external\nHelps identify allocation patterns (frequent new-space churn vs old-space growth)\n显示新生代/老生代的 Usage / Capacity / External 详细数据。","5-manual-gc--手动触发-gc需要-vm-service#5. Manual GC / 手动触发 GC(需要 VM Service)":"\"Trigger GC\" button forces a full garbage collection\nDisabled (grayed out) when VM Service is unavailable\n\"Trigger GC\" 按钮强制触发完整垃圾回收。VM Service 不可用时按钮变灰禁用。","6-memory-leak-detection--内存泄漏检测#6. Memory Leak Detection / 内存泄漏检测":"Based on Dart 2.17+ WeakReference and Finalizer. Does NOT depend on VM Service.基于 Dart 2.17+ 的 WeakReference 和 Finalizer,不依赖 VM Service。Four-state transition / 四状态流转:\nState\tMeaning\ttracking\tObject registered, waiting for expected release time / 对象已注册,等待预期释放时间\tverifying\tExceeded expected release time, GC verification triggered (if VM Service available) / 超过预期释放时间,触发 GC 验证(VM Service 可用时)\tleaked\tObject still exists after GC — suspected leak / GC 后对象仍存在——疑似泄漏\treleased\tObject has been garbage collected / 对象已被垃圾回收\t\nAPI / 接口:Quick shorthand (recommended) / 简化写法(推荐) — available since v1.1.2 / v1.1.2 起可用:\n// Extension method on Object / Object 上的扩展方法\nmyBloc.trackMemoryLeak(tag: 'HomePage_myBloc');\n// Or top-level function / 或使用顶层函数\ntrackMemoryLeak(myBloc, tag: 'HomePage_myBloc');\n// Cancel tracking / 取消追踪\nmyBloc.untrackMemoryLeak();\nFull form / 完整写法:\n// Register an object for leak tracking / 注册对象进行泄漏追踪\nMemoryInspectorService.instance.trackObject(\n myController,\n tag: 'HomeController_textController', // optional identifier\n expectedReleaseAfter: const Duration(seconds: 30), // expected release time\n);\n// Stop tracking a specific object / 停止追踪特定对象\nMemoryInspectorService.instance.untrackObject(myController);\n// Clear all records / 清空所有记录\nMemoryInspectorService.instance.clearLeakRecords();\nLimits / 限制:\nMax 500 tracked objects (LRU eviction)\nDetection interval: 2 seconds\ntrackObject() requires user code modification (mild invasion)\nFlutter MemoryAllocations bridge (since v1.9.0) / 官方泄漏追踪桥接(v1.9.0 起)The WeakReference state machine has a blind spot: an object whose dispose() ran but that has not been GC'd yet still resolves through the weak reference, producing a false \"leaked\" verdict. Since v1.9.0, MemoryInspectorService also subscribes to Flutter's official FlutterMemoryAllocations (the data source behind leak_tracker) via LeakTrackerBridge: once the official stream reports disposed, the object is treated as released (awaiting GC) even if the weak reference still resolves — cutting false positives. Enabled by default through enableFlutterLeakTracker in init(), and toggled in code via MemoryInspectorService.instance.flutterLeakTrackerEnabled.WeakReference 状态机有一个盲点:对象已调用 dispose() 但尚未被 GC 时弱引用仍存活,会产生\"疑似泄漏\"的误报。v1.9.0 起,MemoryInspectorService 通过 LeakTrackerBridge 额外订阅 Flutter 官方的 FlutterMemoryAllocations(leak_tracker 背后的数据源):只要官方上报 disposed,即便弱引用仍存活也判定为已释放(等待 GC),从而显著降低误报。由 init() 的 enableFlutterLeakTracker 默认启用,也可用 MemoryInspectorService.instance.flutterLeakTrackerEnabled 在代码中开关。","7-image-cache--图片缓存#7. Image Cache / 图片缓存":"Real-time Flutter image cache size and count\nPending (loading) / Live (in use) image counts\nOne-click clear all image cache\n实时显示 Flutter 图片缓存大小、数量、加载中/使用中状态。一键清理图片缓存。","8-app-storage--应用存储#8. App Storage / 应用存储":"Documents directory size\nTemp cache directory size\nTotal database file size\nOne-click clear temp cache\n显示文档目录、临时缓存、数据库文件大小。一键清理临时缓存。","️-vm-service-availability--vm-service-可用性#⚠️ VM Service Availability / VM Service 可用性":"Dart Heap data and manual GC require VM Service connection. When VM Service is unavailable, these features gracefully degrade to show \"N/A\".Dart Heap 数据和手动 GC 需要 VM Service 连接。 VM Service 不可用时,这些功能优雅降级显示 \"N/A\"。","when-debugging-via-pc-with-flutter-run--通过-pc-用-flutter-run-调试时#When debugging via PC with flutter run / 通过 PC 用 flutter run 调试时":"Dart VM Heap data may be unavailable (VM: OFF).Dart VM Heap 数据可能不可用(VM: OFF)。Reason / 原因: When using flutter run to debug via PC, the flutter tool sets up port forwarding between PC and device via adb reverse, allowing PC-side DevTools to access the device's VM Service. However, Service.getInfo() returns a serverUri from the PC's perspective; when the app process internally accesses 127.0.0.1:PC_port, the device doesn't have that port listening locally, resulting in Connection refused and VM Service showing OFF.原因:使用 flutter run 连接 PC 调试时,flutter tool 会通过 adb reverse 在 PC 和设备之间做端口转发,让 PC 上的 DevTools 能访问设备的 VM Service。但应用进程内部 Service.getInfo() 返回的 serverUri 是 PC 视角的端口,应用进程访问 127.0.0.1:PC端口 时设备本地并没有监听该端口,导致 Connection refused,VM Service 显示 OFF。","when-opening-debug-app-directly-no-pc--直接打开-debug-应用不连-pc#When opening debug app directly (no PC) / 直接打开 debug 应用(不连 PC)":"VM Service works normally. No flutter tool is involved, VM Service listens directly on the device's local port, the app can connect normally, and Dart Heap data displays correctly.VM Service 正常工作。 没有 flutter tool 介入,VM Service 直接监听设备本地端口,应用能正常连接,Dart Heap 数据正常显示。","fallback--降级方案#Fallback / 降级方案":"When VM Service is unavailable:\n✅ Native memory (Android PSS / iOS physicalFootprint) — still available\n✅ Process RSS — always available\n✅ Image cache / storage stats — still available\n✅ Leak detection — still available (doesn't depend on VM Service)\n❌ Dart Heap details — shows N/A\n❌ Manual GC — button disabled","platform-support--平台支持#Platform Support / 平台支持":"Feature\tAndroid\tiOS\tProcess RSS\t✅\t✅\tNative Memory\t✅\t✅\tDart Heap (VM Service)\t✅ (no PC)\t✅ (no PC)\tManual GC\t✅ (no PC)\t✅ (no PC)\tLeak Detection\t✅\t✅\tImage Cache\t✅\t✅\tStorage Stats\t✅\t✅","refresh-intervals--刷新间隔#Refresh Intervals / 刷新间隔":"Data Source\tInterval\tProcess RSS / Dart Heap\t500ms\tNative Memory (Android/iOS)\t3000ms\tStorage Stats\t3000ms\tLeak Detection\t2000ms","related--相关#Related / 相关":"Usage — General usage guide\nFAQ — Common questions\nConfiguration — Configuration options"}},"/Network-Inspector":{"title":"Network Inspector / 网络检查器","data":{"overview--概述#Overview / 概述":"The Network Inspector automatically captures all HTTP requests made via the http package and Dio, with zero configuration needed.网络检查器自动捕获所有通过 http 包 和 Dio 发送的 HTTP 请求,无需任何配置。","how-it-works--工作原理#How It Works / 工作原理":"The inspector uses Flutter's HttpOverrides to intercept all HTTP traffic at the dart:io level. This means:检查器通过 Flutter 的 HttpOverrides 在 dart:io 层面拦截所有 HTTP 流量。这意味着:\nhttp package: Auto-captured ✅ / 自动捕获 ✅\nDio: Auto-captured (uses IOHttpClientAdapter → HttpClient) ✅ / 自动捕获 ✅\nNo manual interceptor setup needed / 无需手动添加拦截器","websocket--grpc-capture--websocket-与-grpc-抓取#WebSocket & gRPC Capture / WebSocket 与 gRPC 抓取":"Available since v1.7.0 (opt-in, off by default)v1.7.0 起可用(可选开启,默认关闭)\nHTTP/HTTPS traffic is captured automatically, but the HttpOverrides interceptor does not cover streaming protocols like WebSocket and gRPC. For those, enable the opt-in capture so frames and calls show up as WS / gRPC rows in the Network tab.HTTP/HTTPS 流量会自动捕获,但 HttpOverrides 拦截器无法覆盖 WebSocket、gRPC 这类流式协议。针对它们需开启可选的抓取,开启后收发帧/调用会以 WS / gRPC 行的形式出现在 Network 标签页。","enable--开启方式#Enable / 开启方式":"Toggle the WS switch in the Network tab toolbar, or / 在 Network 标签页工具栏点击 WS 开关,或\nSet it programmatically: / 通过代码开启:\n// on / 开启\nWsInspectorService.instance.enable();\n// off / 关闭\nWsInspectorService.instance.disable();\nCapture is off by default and only records while enabled — apps that don't use these protocols pay nothing.抓取默认关闭,且只在开启时记录;不使用这类协议的应用零开销。","two-usage-modes--两种使用方式#Two Usage Modes / 两种使用方式":"1. Transparent wrapper — InspectorWebSocketReplace WebSocket.connect with InspectorWebSocket.connect. Frames are auto-recorded (→ out, ← in) when capture is on; when off it passes through with zero overhead.将 WebSocket.connect 替换为 InspectorWebSocket.connect。开启抓取时会自动记录收发帧(→ 出站、← 入站);关闭时零开销透传。\nfinal ws = await InspectorWebSocket.connect('wss://echo.websocket.events');\nws.listen((msg) => print('received: $msg'));\nws.add('hello'); // recorded as an outgoing frame / 记录为出站帧\n2. Manual hook — recordCallFor stacks not transparently interceptable by dart:io (gRPC, web_socket_channel, custom protocols), call recordCall to log a request/response pair. No-ops when capture is disabled.对于无法被 dart:io 透明拦截的栈(gRPC、web_socket_channel、自定义协议),调用 recordCall 记录一次请求/响应。关闭抓取时为空操作。\nWsInspectorService.instance.recordCall(\n name: 'user.UserService/GetUser',\n request: '{ \"id\": 1 }',\n response: '{ \"name\": \"Ada\" }',\n protocol: 'gRPC', // appears in the method column / 显示在 method 列\n);","what-you-see--查看方式#What You See / 查看方式":"Network tab lists a WS (or gRPC) entry per connection/call / Network 标签页按连接/调用列出 WS(或 gRPC)记录\nOpen the detail view to see the frame log (outgoing → / incoming ←), accumulated in the response body / 进入详情页查看帧日志(出站 → / 入站 ←),累积显示在响应体中\nA [connection closed] marker is appended when the socket closes / 连接关闭后会追加 [connection closed] 标记","captured-information--捕获的信息#Captured Information / 捕获的信息":"Field\tDescription\tMethod\tGET, POST, PUT, DELETE, PATCH\tURL\tFull request URL\tStatus Code\tHTTP response status code\tDuration\tRequest duration\tRequest Headers\tAll request headers\tRequest Body\tRequest payload (JSON formatted)\tResponse Body\tResponse payload (JSON formatted)\tHost\tParsed from URL","ui-features--ui-功能#UI Features / UI 功能":"","request-list--请求列表#Request List / 请求列表":"Color-coded by HTTP method / 按 HTTP 方法着色\nStatus code badge / 状态码徽章\nDuration display / 耗时显示\nLeft border color indicates status / 左侧边框颜色表示状态","request-detail--请求详情#Request Detail / 请求详情":"Click a request to enter detail view / 点击请求进入详情视图\nBack button to return to list / 返回按钮返回列表\nRequest and response sections / 请求和响应分段显示\nJSON formatted body / JSON 格式化显示","search--搜索#Search / 搜索":"Fuzzy search by URL or method / 按 URL 或方法模糊搜索\nSearch bar hidden in detail view / 详情视图隐藏搜索栏","batch-operations--批量操作#Batch Operations / 批量操作":"Available since v1.3.0v1.3.0 起可用\nThe request list supports a selection mode for operating on multiple requests at once.请求列表支持选择模式,可一次性操作多条请求。\nTap the selection icon in the toolbar to enter selection mode / 点击工具栏的选择图标进入选择模式\nCheck/uncheck items; a top batch bar shows Select all / Cancel / 勾选/取消勾选;顶部批量条提供全选 / 取消\nBatch \"Copy as cURL\": copies all selected requests as cURL commands / 批量「Copy as cURL」:将所有选中请求复制为 cURL 命令\nBatch delete: removes selected requests from the list / 批量删除:从列表中移除选中请求","export--sensitive-field-masking--导出与敏感字段遮蔽#Export & Sensitive-field Masking / 导出与敏感字段遮蔽":"Available since v1.3.0v1.3.0 起可用\nThe toolbar includes an eye toggle that controls whether sensitive headers are masked on export.工具栏包含眼睛开关,控制导出时是否遮蔽敏感请求头。\nDefault: off (fully visible) — cURL / JSON / HAR reflect the real request verbatim / 默认关闭(完整可见):cURL / JSON / HAR 原样反映真实请求\nOn: toCurl / netToJson / netToHar / copyNet / exportNetToFile mask these headers / 开启后:以下请求头被遮蔽:\nAuthorization, Cookie, Set-Cookie, Proxy-Authorization, X-Auth-Token, X-CSRF-Token, X-XSRF-Token\nThe detail page \"Copy as cURL\" / \"Copy as JSON\" follow the same toggle / 详情页「Copy as cURL」「Copy as JSON」跟随同一开关\nWhen masking is active, the snackbar notes (sensitive hidden) / 遮蔽开启时,snackbar 提示 (sensitive hidden)\nTip: keep masking on before pasting cURL/JSON into terminals, chat, or issue trackers to avoid leaking credentials.提示:将 cURL/JSON 粘贴到终端、聊天或 issue 前,建议开启遮蔽,避免凭据泄露。","copy-as-curl--复制为-curl#Copy as cURL / 复制为 cURL":"Available since v1.3.0v1.3.0 起可用\nFrom the request detail view, tap Copy as cURL to copy the request as a ready-to-run cURL command (method, URL, headers, body). Respects the sensitive-field masking toggle above.在请求详情页点击 Copy as cURL,即可将请求复制为可直接运行的 cURL 命令(方法、URL、请求头、请求体),并遵循上方的敏感字段遮蔽开关。","replay-editor--重放编辑器#Replay Editor / 重放编辑器":"Available since v1.11.0v1.11.0 起可用\nEvery request detail view has a replay action (↻ icon). It opens an editable sheet that lets you re-issue the captured request in-app and preview the response — handy for quickly re-running an endpoint without leaving the app.每个请求详情页都有重放操作(↻ 图标)。它会打开一个可编辑弹层,让你在 App 内重新发出该请求并预览响应——无需离开应用即可快速复跑某个接口。\nOnly the URL query parameters are editable — add / edit / remove key=value rows; the URL is rebuilt from them on send / 仅 URL 查询参数可编辑——可增删改 key=value 行,发送时用其重建 URL\nURL, request headers and request body are read-only by design (the captured values are re-sent verbatim) / URL、请求头与请求体按设计只读(以捕获到的原值原样重发)\nOn Send, the request is re-issued with the original method / headers / body and the edited query string; the sheet shows the returned status code, elapsed time, and a response preview / 点击 Send 后用原始方法 / 请求头 / 请求体加上修改后的查询串重新发出,弹层展示返回状态码、耗时与响应预览\nThis is distinct from the interceptor (which builds a durable modification rule applied to future matching requests). The replay editor makes a one-off call with editable query params — including GET requests, whose params can't be changed by the interceptor.它与拦截器不同(拦截器生成作用于后续匹配请求的持久规则)。重放编辑器发起的是一次性调用,仅查询参数可改——包括 GET 请求(其参数拦截器无法修改)。","status-code-colors--状态码颜色#Status Code Colors / 状态码颜色":"Range\tColor\tDescription\t2xx\tGreen\tSuccess / 成功\t3xx\tBlue\tRedirect / 重定向\t4xx\tOrange\tClient error / 客户端错误\t5xx\tRed\tServer error / 服务器错误","http-method-colors--http-方法颜色#HTTP Method Colors / HTTP 方法颜色":"Method\tColor\tGET\tBlue / 蓝色\tPOST\tGreen / 绿色\tPUT\tOrange / 橙色\tDELETE\tRed / 红色\tPATCH\tPurple / 紫色","usage-example--使用示例#Usage Example / 使用示例":"// http package - auto-captured / http 包 - 自动捕获\nfinal response = await http.get(\n Uri.parse('https://api.example.com/data'),\n);\n// Dio - auto-captured / Dio - 自动捕获\nfinal response = await dio.post(\n 'https://api.example.com/data',\n data: {'key': 'value'},\n);\nNo additional setup required! All requests will appear in the Network tab.无需额外配置!所有请求都会出现在 Network 标签页中。","request-interceptor--请求拦截修改#Request Interceptor / 请求拦截修改":"Available since v1.0.7 (response fields locked to read-only since v1.0.8)v1.0.7 起可用(v1.0.8 起响应字段锁定为只读)\nThe inspector supports intercepting and modifying network requests via rules. This is useful for testing different request parameters without modifying app code.检查器支持通过规则拦截并修改网络请求,适合在不修改应用代码的情况下测试不同的请求参数。","workflow--工作流程#Workflow / 工作流程":"Send a request normally (it will be captured in the Network panel) / 正常发送请求(会被捕获到 Network 面板)\nOpen the request detail and tap the Interceptor icon / 打开请求详情,点击拦截器图标\nConfigure the modification rule (URL pattern, HTTP method, request modifications) / 配置修改规则(URL 模式、HTTP 方法、请求修改)\nSave the rule — subsequent matching requests will use the modified parameters / 保存规则——后续匹配的请求将使用修改后的参数","supported-modifications--支持的修改#Supported Modifications / 支持的修改":"Field\tEditable\tNotes\tRequest Body\t✅\tOnly for requests with body (POST, PUT, PATCH, etc.) / 仅适用于有 body 的请求\tRequest Headers\t✅\tAdd / modify / remove headers / 新增 / 修改 / 删除请求头\tURL\t❌\tGrayed out, read-only / 灰色不可编辑\tResponse Status Code\t❌\tRead-only since v1.0.8 / v1.0.8 起只读\tResponse Body\t❌\tRead-only since v1.0.8 / v1.0.8 起只读\t\nThe interception edit panel only allows modifying the request body and request headers. Response fields (status code, response body) are grayed out and uneditable.拦截编辑面板仅允许修改请求体和请求头。响应字段(状态码、响应体)灰色不可编辑。","rule-matching--规则匹配#Rule Matching / 规则匹配":"URL pattern matching: exact match or regex / URL 模式匹配:精确匹配或正则匹配\nHTTP method filtering: GET, POST, PUT, DELETE, PATCH, HEAD, or Any / HTTP 方法过滤","why-get-requests-cannot-be-modified--为什么-get-请求不能修改#Why GET Requests Cannot Be Modified / 为什么 GET 请求不能修改":"The interceptor currently supports modifying request body and headers only / 拦截器目前仅支持修改请求体和请求头\nGET requests don't have a request body / GET 请求没有请求体\nModifying GET request parameters would require URL modification / 修改 GET 请求参数需要修改 URL\nURL modification may cause unexpected issues with request routing and parameter encoding / 修改 URL 可能导致请求路由和参数编码的意外问题\nGET request detail pages do not display interception edit buttons, making them unmodifiable.GET 请求详情页不显示拦截编辑按钮,因此无法修改。","master-toggle--拦截总开关#Master Toggle / 拦截总开关":"The interception master toggle is displayed only on the Network list page, not in the detail page / 拦截总开关只显示在 Network 列表页,不在详情页\nRules are only applied when modification mode is enabled / 规则仅在启用修改模式时生效\nWhen no rules are configured or rules are disabled, all requests are sent normally without any modification / 未配置规则或禁用规则时,所有请求正常发送,不做任何修改","rule-management--规则管理#Rule Management / 规则管理":"The network panel includes an interceptor rule editor where you can:网络面板包含拦截规则编辑器,可以:\nCreate / edit / delete rules / 创建 / 编辑 / 删除规则\nEnable / disable individual rules / 启用 / 禁用单条规则\nView rule status indicators in the request list / 在请求列表中查看规则状态标识"}},"/Route-Tracker":{"title":"Route Tracker / 路由追踪","data":{"overview--概述#Overview / 概述":"The Route Tracker monitors navigation history, recording all route push, pop, and replacement events.路由追踪器监控导航历史,记录所有路由 push、pop 和替换事件。","auto-injection--自动注入#Auto-Injection / 自动注入":"When using runAppWithInspector() or wrapApp(), the InspectorRouteObserver is automatically injected into MaterialApp's navigatorObservers.使用 runAppWithInspector() 或 wrapApp() 时,InspectorRouteObserver 会自动注入到 MaterialApp 的 navigatorObservers 中。","tracked-route-actions--追踪的路由操作#Tracked Route Actions / 追踪的路由操作":"Action\tColor\tDescription\tpush\tBlue\tNavigator.push() / 推入新路由\tpushNamed\tBlue\tNavigator.pushNamed() / 按名称推入\tpop\tOrange\tNavigator.pop() / 弹出路由\tpopUntil\tOrange\tNavigator.popUntil() / 弹出直到\tpushReplacement\tPurple\tNavigator.pushReplacement() / 替换路由","ui-features--ui-功能#UI Features / UI 功能":"","route-list--路由列表#Route List / 路由列表":"Action badge with color / 带颜色的操作徽章\nRoute name display / 路由名称显示\nTimestamp display / 时间戳显示\nLeft border color indicates action type / 左侧边框颜色表示操作类型","route-detail--路由详情#Route Detail / 路由详情":"Click a route to view details / 点击路由查看详情\nShows: Action, Route Name, Timestamp, Arguments / 显示:操作、路由名、时间戳、参数\nArguments displayed as formatted JSON / 参数以 JSON 格式显示","manual-setup-optional--手动设置可选#Manual Setup (Optional) / 手动设置(可选)":"If you use a custom Navigator or don't use MaterialApp, add the observer manually:如果使用自定义 Navigator 或不使用 MaterialApp,请手动添加观察者:\nMaterialApp(\n navigatorObservers: [InspectorRouteObserver()],\n home: MyHomePage(),\n)"}},"/Timeline":{"title":"Timeline / 统一会话时间线","data":{"overview--概述#Overview / 概述":"The Timeline tab merges network, logs, errors, routes, and alerts into a single time-ordered stream, so you can see the full causal chain of a session at a glance — e.g. \"route push → two requests → error log → 5xx alert\". Each source keeps its own data; the Timeline view only merges them for display.统一会话时间线把网络、日志、异常、路由、告警归并为一条按时间排序的流,让你一眼看清一次会话的完整因果链——例如「路由跳转 → 两个请求 → error 日志 → 5xx 告警」。各来源的数据仍归属各自服务,时间线视图只做归并显示。\nAvailable since v1.12.0v1.12.0 起可用","where-to-find-it--入口#Where to find it / 入口":"Tap the floating inspector button → the Timeline tab (located after Routes and before Widgets in the panel).点击悬浮检查器按钮 → Timeline 标签页(位于面板中 Routes 之后、Widgets 之前)。","features--功能#Features / 功能":"","1-merged-stream--归并流#1. Merged stream / 归并流":"Network / Logs / Errors / Routes / Alerts appear interleaved by timestamp / 网络 / 日志 / 异常 / 路由 / 告警按时间戳交错排列\nEach entry shows its source icon, a colored left accent bar, timestamp, and a concise summary / 每条显示来源图标、左侧彩色强调条、时间戳与精炼摘要\nTap any entry to expand details (e.g. request URL/status, log level/message, route action/name) / 点击任意条目展开详情(如请求 URL/状态、日志级别/内容、路由操作/名称)\n把五类数据按时间归并,每条带来源图标、彩色强调条、时间戳与摘要,可点击展开。","2-per-source-filtering--逐来源筛选#2. Per-source filtering / 逐来源筛选":"Toggle each source on/off to focus on the ones you care about / 逐来源开关,只看关心的来源\nFiltering only hides entries from the merged view; underlying data is untouched / 筛选只隐藏归并视图中的条目,底层数据不受影响\n顶部可按来源开关自由筛选,归并显示内容随之变化,底层数据不变。","3-focus-n-seconds--n-秒聚焦#3. Focus ±N seconds / ±N 秒聚焦":"Tap any event to enter \"focus\" mode: the view keeps only events within ±N seconds of the anchor / 点击任意事件进入「聚焦」模式,仅保留锚点前后 ±N 秒内的事件\nWindow selector: 5s / 10s (default) / 30s / 窗口可选:5s / 10s(默认)/ 30s\nGreat for isolating the exact cause-effect around a failure (e.g. what happened right before a 5xx alert) / 适合隔离某次失败前后的精确因果(如 5xx 告警前到底发生了什么)\n点击任意事件即可「聚焦它前后 ±N 秒」,把噪音过滤掉,只留因果相关事件。","how-it-works--工作原理#How It Works / 工作原理":"View-only merge — TimelineService.build() reads from InspectorService (network / logs / errors), RouteTrackerService, and AlertService, sorts them by time, and tags each with its TimelineEventKind. It does not copy or own any data. / 仅视图归并:TimelineService.build() 从各服务读取数据、按时间排序并打上来源标签,不复制、不持有数据。\nZero cost when closed — no timers, no listeners, no polling while the panel is not open; the merge runs on demand when you open the Timeline tab. / 面板关闭时零开销:未打开面板时不跑定时器、不监听、不轮询,仅在打开 Timeline 时按需归并。\ncontextAround(anchor, window) — given an anchor event and a Duration window, returns the slice of events whose timestamp falls within [anchor - window, anchor + window], oldest-first. / 给定锚点事件与窗口时长,返回落在 [锚点 - 窗口, 锚点 + 窗口] 内的事件,按时间升序。","related--相关#Related / 相关":"Usage — General usage guide / 通用使用指南\nFPS Viewer — Includes the main-thread blocking watchdog / 含主线程阻塞看门狗\nRoute Tracker — Route tracking details / 路由追踪详情"}},"/":{"title":"Zero Inspector Kit","data":{"":"A powerful Flutter plugin for in-app developer console, providing real-time debugging tools including network request inspection, logging, error aggregation, database viewing, and route tracking.一个功能强大的 Flutter 插件,提供应用内开发者控制台,包括网络请求检查、日志记录、异常聚合、数据库查看和路由追踪。","-features--功能特性#✨ Features / 功能特性":"Feature\tDescription\tZero Invasion\tIntegrate with 1 line of code / 一行代码集成\tNetwork Inspector\tReal-time HTTP request viewing (http + Dio) / 实时网络请求查看\tLog Viewer\tAuto-capture print()/debugPrint() and third-party logs / 自动捕获日志\tErrors\tAggregate & dedupe crashes by type + stack, with count and first/last seen / 按类型+堆栈去重聚合崩溃,记录次数与首末次时间\tDatabase Viewer\tSQLite inspection with table data / 数据库查看\tMemory Viewer\tTrend chart, Dart Heap, Native memory, leak detection (incl. Flutter MemoryAllocations bridge) / 内存趋势图、Dart Heap、Native 内存、泄漏检测(含官方 MemoryAllocations 桥接)\tFPS Monitor\tReal-time FPS, jank rate, trend chart / 实时 FPS、卡顿率、趋势图\tRoute Tracker\tNavigation history tracking / 路由追踪\tSession Timeline\tUnified view of network/logs/errors/routes/alerts by time, with ±N-second focus / 网络/日志/异常/路由/告警按时间归并,支持 ±N 秒聚焦\tSession Persistence\tLogs/network/errors survive restarts via a disk ring buffer; logs & errors replay on launch; one-tap session archive export / 日志/网络/异常通过磁盘环形缓冲跨重启保留,启动时回放日志与异常;一键导出会话存档\tAlerts\tRule-based alerts (network/log/memory/FPS) with unread badge / 基于规则的告警(网络/日志/内存/FPS)与未读角标\tSensitive Masking & cURL\tMask secrets on export; one-click cURL copy; batch ops / 导出遮蔽敏感字段、一键复制 cURL、批量操作\tFuzzy Search\tSearch in all viewers / 各查看器模糊搜索\tCross-platform\tAndroid, iOS / 跨平台支持","-table-of-contents--目录#📚 Table of Contents / 目录":"Page\tDescription\tGetting Started\tQuick start guide / 快速开始\tInstallation\tHow to install / 安装方式\tUsage\tDetailed usage / 详细使用\tNetwork Inspector\tNetwork request viewing / 网络检查器\tLog Viewer\tLog capturing and viewing / 日志查看器\tErrors\tAggregated error viewing / 异常聚合查看\tDatabase Viewer\tDatabase inspection / 数据库查看器\tRoute Tracker\tRoute tracking / 路由追踪\tTimeline\tUnified session timeline / 统一会话时间线\tMemory Viewer\tMemory monitoring & leak detection / 内存监控与泄漏检测\tFPS Viewer\tFPS monitoring & jank detection / FPS 监控与卡顿检测\tAlerts\tRule-based alerting / 基于规则的告警\tConfiguration\tConfiguration options / 配置说明\tCustom Database Provider\tExtend database support / 自定义数据库提供者\tFAQ\tFrequently asked questions / 常见问题","-links--链接#🔗 Links / 链接":"GitHub\nOfficial Website\npub.dev","-license--许可证#📄 License / 许可证":"This project is licensed under the GNU General Public License v3.0.本项目采用 GNU General Public License v3.0 许可证。This plugin is provided \"as is\", without warranty of any kind. The author assumes no responsibility or liability for the functionality, security, or any consequences arising from the use of modified versions or derivative projects.本插件按\"原样\"提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及任何使用后果承担责任。"}},"/Usage":{"title":"Usage / 使用指南","data":{"integration-methods--集成方式#Integration Methods / 集成方式":"","1-one-line-integration-recommended--一行代码集成推荐#1. One-Line Integration (Recommended) / 一行代码集成(推荐)":"void main() {\n ZeroInspectorKit.runAppWithInspector(const MyApp());\n}\nThis method:\nAuto-initializes inspector / 自动初始化检查器\nCaptures print() via Zone / 通过 Zone 捕获 print()\nDisplays floating button via Overlay / 通过 Overlay 显示悬浮按钮\nAuto-injects route observer / 自动注入路由观察者","2-two-line-integration--两行代码集成#2. Two-Line Integration / 两行代码集成":"void main() {\n ZeroInspectorKit.init();\n runApp(ZeroInspectorKit.wrapApp(const MyApp()));\n}","inspector-panel--检查器面板#Inspector Panel / 检查器面板":"The inspector panel contains 10 tabs (as of v1.12.0):检查器面板包含 10 个标签页(v1.12.0 起):\nTab\tIcon\tFeature\tNetwork\t🌐\tHTTP request viewing + interceptor rules / 网络请求查看 + 拦截修改\tLogs\t📝\tLog viewing with level filter / 日志查看\tErrors\t🚨\tAggregated & deduped crash viewing / 去重聚合的异常查看\tDatabase\t💾\tDatabase and table inspection / 数据库查看\tMemory\t📊\tMemory trend, Dart Heap, Native memory, leak detection / 内存趋势、Dart Heap、Native 内存、泄漏检测\tFPS\t🎯\tReal-time FPS, jank rate, trend chart, plus main-thread blocking watchdog / 实时 FPS、卡顿率、趋势图,以及主线程阻塞看门狗\tRoutes\t🧭\tRoute navigation tracking / 路由追踪\tTimeline\t🧵\tUnified session timeline: network / logs / errors / routes / alerts merged into one stream, with ±N-second focus / 统一会话时间线:网络/日志/异常/路由/告警按时间归并,支持 ±N 秒聚焦\tWidgets\t🔍\tWidget tree snapshot for the current route / 当前路由的 Widget 树快照\tAlerts\t🔔\tRule-based alerts with unread badge / 基于规则的告警与未读角标\t\nThe Memory and FPS monitors are off by default to avoid performance overhead. Toggle the switch at the top of each panel to start collecting data.Memory 与 FPS 监控默认关闭以避免性能开销。在各自面板顶部打开开关才会开始采集数据。","one-click-bug-report--一键-bug-报告#One-Click Bug Report / 一键 Bug 报告":"Tap the bug icon in the inspector panel header to generate and share a bug report in one tap — ideal for QA to attach environment context when filing issues.点击检查器面板头部的虫子图标,即可一键生成并分享一份 Bug 报告——非常适合 QA 在提 issue 时附上环境上下文。The shared text snapshot includes / 分享的文本快照包含:\nDevice / 设备: real model (e.g. Pixel 8 Pro / iPhone (iPhone16,1)), OS & version, locale, Dart runtime, CPU cores.\nMemory / 内存: current heap usage and whether Native memory is supported.\nRecent logs / 最近日志: the latest captured log entries.\nRecent network / 最近网络: the latest captured requests.\nSensitive headers are masked the same way as in the Network tab. No data leaves the device except through the share target you choose.敏感请求头会与网络标签页一样被遮蔽。除你选择的分享目标外,数据不会离开设备。\nRequires no extra setup — it works as soon as the inspector is running. / 无需额外配置——检查器运行后即可使用。","floating-button--悬浮按钮#Floating Button / 悬浮按钮":"The floating button appears after 1 second delay / 悬浮按钮延迟 1 秒出现\nDrag to move it along the screen edge / 拖动 可沿屏幕边缘移动\nTap (when fully visible) to open/close the inspector panel / 点击(完全可见时)打开/关闭检查器面板\nButton auto-snaps to the nearest screen edge / 按钮自动吸附到最近的屏幕边缘\nBreathing animation when idle / 空闲时有呼吸动画","edge-docking-since-v120--边缘吸附v120-起#Edge Docking (since v1.2.0) / 边缘吸附(v1.2.0 起)":"When released near a screen edge, the button auto-docks and tucks into the edge, leaving only a 24px peek visible:拖动松手后,按钮会自动吸附到最近边缘并\"收入\"边缘,仅露出 24px 小弧边:\nState\tBehavior / 行为\tDocked (tucked in)\tOnly 24px peek visible; icon becomes a directional chevron (left dock → ➡, right dock → ⬅) hinting at tap-to-pull-out / 仅露出 24px;图标变为方向箭头提示可点击拉出\tTap docked peek\tSmoothly pulls out to fully visible (panel NOT opened, avoids accidental open) / 平滑拉出到完全可见(不打开面板,避免误触)\tTap fully visible\tOpens the inspector panel / 打开检查器面板\t\nThis design avoids conflicts with system back gestures (Android/iOS edge swipe to go back) when pulling out from the docked state.此设计避免了从吸附态拖出时与系统返回手势(Android/iOS 边缘右滑退出)的冲突。","search--搜索#Search / 搜索":"The main viewers support fuzzy search:各查看器均支持模糊搜索:\nViewer\tSearch Scope\tNetwork\tURL, HTTP method / URL、请求方法\tLogs\tMessage, tag / 消息、标签\tErrors\tException type, message / 异常类型、消息\tDatabase (global)\tDatabase name, table name / 数据库名、表名\tDatabase (in-database)\tTable name, all column data / 表名、所有列数据","manual-logging-optional--手动记录日志可选#Manual Logging (Optional) / 手动记录日志(可选)":"The inspector auto-captures print() output. You can also use manual log methods for precise level control:检查器会自动捕获 print() 输出。也可以使用手动日志方法进行精确级别控制:Quick shorthand (recommended) / 简化写法(推荐):\nInspectorLog.v('Verbose log / 详细日志');\nInspectorLog.d('Debug log / 调试日志');\nInspectorLog.i('Info log / 信息日志');\nInspectorLog.w('Warning log / 警告日志');\nInspectorLog.e('Error log / 错误日志');\nFull form / 完整写法:\nInspectorLogInterceptor.instance.verbose('Verbose log / 详细日志');\nInspectorLogInterceptor.instance.debug('Debug log / 调试日志');\nInspectorLogInterceptor.instance.info('Info log / 信息日志');\nInspectorLogInterceptor.instance.warning('Warning log / 警告日志');\nInspectorLogInterceptor.instance.error('Error log / 错误日志');","third-party-log-integration--第三方日志库集成#Third-Party Log Integration / 第三方日志库集成":"No configuration needed! The plugin automatically captures logs from third-party logging libraries (e.g., logger, flutter_logger) that use print() or debugPrint().无需配置! 插件会自动捕获所有使用 print() 的第三方日志库的日志。These logs are categorized as INFO level.这些日志统一归类为 INFO 级别。","bidirectional-sync-optional--双向同步可选#Bidirectional Sync (Optional) / 双向同步(可选)":"To sync inspector-captured logs to your third-party logger:将检查器捕获的日志同步到第三方日志库:\nInspectorLogInterceptor.instance.onLogCaptured = (entry) {\n yourLogger.log(entry.message);\n};","feature-pages--功能详情#Feature Pages / 功能详情":"Network Inspector — Network request details + interceptor rules / 网络检查器详情 + 拦截修改\nLog Viewer — Log viewing details / 日志查看器详情\nErrors — Aggregated error viewing / 异常聚合查看\nDatabase Viewer — Database inspection details / 数据库查看器详情\nRoute Tracker — Route tracking details / 路由追踪详情\nMemory Viewer — Memory monitoring & leak detection / 内存监控与泄漏检测\nFPS Viewer — FPS monitoring & jank detection / FPS 监控与卡顿检测","session-persistence--会话持久化#Session Persistence / 会话持久化":"Logs, network requests, and aggregated errors are asynchronously flushed to a local SQLite ring buffer. On the next launch, logs and aggregated errors replay into their tabs; network requests stay archived on disk for later export. Data therefore survives app restarts even if the inspector panel was never opened. Tap the storage icon in the panel header to open the Persisted data manager: view row counts per category, export the full session archive, or clear the disk. See Configuration (PersistenceService section) for the API and tuning parameters.日志、网络请求与聚合异常会被异步写入本地 SQLite 环形缓冲。下次启动时,日志与聚合异常会回放入各自标签页;网络请求保留在磁盘存档,供之后导出。因此即使从未打开过检查器面板,数据也能跨重启保留。点击面板头部的存储图标可打开 Persisted data 管理弹层:查看各类别行数、导出完整会话存档或清空磁盘。API 与调参详见 Configuration(PersistenceService 一节)。"}}} \ No newline at end of file diff --git a/docs/_next/static/chunks/nextra-page-map-.mjs b/docs/_next/static/chunks/nextra-page-map-.mjs index cfaaaae..df43bf9 100644 --- a/docs/_next/static/chunks/nextra-page-map-.mjs +++ b/docs/_next/static/chunks/nextra-page-map-.mjs @@ -85,6 +85,12 @@ export const pageMap = [{ frontMatter: { "sidebarTitle": "Route Tracker" } +}, { + name: "Timeline", + route: "/Timeline", + frontMatter: { + "sidebarTitle": "Timeline" + } }, { name: "Usage", route: "/Usage", diff --git a/docs/_next/static/chunks/pages/Alerts-a0f5430c00bb2cc2.js b/docs/_next/static/chunks/pages/Alerts-a070cfa208c3ff82.js similarity index 76% rename from docs/_next/static/chunks/pages/Alerts-a0f5430c00bb2cc2.js rename to docs/_next/static/chunks/pages/Alerts-a070cfa208c3ff82.js index f04cd76..07d6c95 100644 --- a/docs/_next/static/chunks/pages/Alerts-a0f5430c00bb2cc2.js +++ b/docs/_next/static/chunks/pages/Alerts-a070cfa208c3ff82.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[713],{6399:function(e,t,r){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Alerts",function(){return r(4571)}])},4571:function(e,t,r){"use strict";r.r(t),r.d(t,{useTOC:function(){return l}});var n=r(5893),s=r(7812),i=r(7080),a=r(8925);function l(e){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"How It Works / 工作原理",id:"how-it-works--工作原理",depth:2},{value:"Rule Types / 规则类型",id:"rule-types--规则类型",depth:2},{value:"UI Features / UI 功能",id:"ui-features--ui-功能",depth:2},{value:"Floating Button Badge / 悬浮球角标",id:"floating-button-badge--悬浮球角标",depth:3},{value:"Alerts Tab / Alerts 标签页",id:"alerts-tab--alerts-标签页",depth:3},{value:"Notes / 备注",id:"notes--备注",depth:2}]}t.default=(0,s.c)(function(e){let{toc:t=l(e)}=e,r={blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(r.h1,{children:"Alerts / 告警系统"}),"\n",(0,n.jsx)(r.h2,{id:t[0].id,children:t[0].value}),"\n",(0,n.jsxs)(r.blockquote,{children:["\n",(0,n.jsx)(r.p,{children:(0,n.jsx)(r.strong,{children:"Available since v1.3.0"})}),"\n",(0,n.jsx)(r.p,{children:(0,n.jsx)(r.strong,{children:"v1.3.0 起可用"})}),"\n"]}),"\n",(0,n.jsx)(r.p,{children:"The alert system lets you define rules that proactively surface problems across network requests, logs, memory, and FPS — without manually scanning the panels."}),"\n",(0,n.jsx)(r.p,{children:"告警系统允许你定义规则,主动暴露网络请求、日志、内存、FPS 方面的问题,无需手动逐个面板排查。"}),"\n",(0,n.jsx)(r.h2,{id:t[1].id,children:t[1].value}),"\n",(0,n.jsxs)(r.ul,{children:["\n",(0,n.jsxs)(r.li,{children:["Rules are evaluated by ",(0,n.jsx)(r.code,{children:"AlertService"})," whenever a relevant event occurs / 每当相关事件发生时,",(0,n.jsx)(r.code,{children:"AlertService"})," 会评估规则"]}),"\n",(0,n.jsxs)(r.li,{children:["Matched rules produce alerts, and the unread count is exposed via a ",(0,n.jsx)(r.code,{children:"ValueNotifier"})," / 命中的规则会生成告警,未读数通过 ",(0,n.jsx)(r.code,{children:"ValueNotifier"})," 暴露"]}),"\n",(0,n.jsx)(r.li,{children:"The floating button shows an unread-count badge; opening the panel clears it / 悬浮球显示未读数量角标,打开面板即清零"}),"\n",(0,n.jsxs)(r.li,{children:["An ",(0,n.jsx)(r.strong,{children:"Alerts"})," tab in the inspector panel lists all triggered alerts / 检查器面板的 ",(0,n.jsx)(r.strong,{children:"Alerts"})," 标签页列出所有已触发的告警"]}),"\n"]}),"\n",(0,n.jsx)(r.h2,{id:t[2].id,children:t[2].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(r.table,{children:[(0,n.jsx)(r.thead,{children:(0,n.jsxs)(r.tr,{children:[(0,n.jsx)(r.th,{children:"Type"}),(0,n.jsx)(r.th,{children:"Trigger / 触发条件"}),(0,n.jsx)(r.th,{children:"Example / 示例"})]})}),(0,n.jsxs)(r.tbody,{children:[(0,n.jsxs)(r.tr,{children:[(0,n.jsx)(r.td,{children:(0,n.jsx)(r.strong,{children:"Network"})}),(0,n.jsx)(r.td,{children:"Response status code / 响应状态码"}),(0,n.jsx)(r.td,{children:"Alert when status ≥ 400 / 状态码 ≥ 400 时告警"})]}),(0,n.jsxs)(r.tr,{children:[(0,n.jsx)(r.td,{children:(0,n.jsx)(r.strong,{children:"Log"})}),(0,n.jsx)(r.td,{children:"Log level or message / 日志级别或内容"}),(0,n.jsx)(r.td,{children:"Alert on error logs / 出现 error 日志时告警"})]}),(0,n.jsxs)(r.tr,{children:[(0,n.jsx)(r.td,{children:(0,n.jsx)(r.strong,{children:"Memory"})}),(0,n.jsx)(r.td,{children:"Dart heap / Native memory threshold / 内存阈值"}),(0,n.jsx)(r.td,{children:"Alert when Dart heap > 100 MB / Dart 堆 > 100 MB 时告警"})]}),(0,n.jsxs)(r.tr,{children:[(0,n.jsx)(r.td,{children:(0,n.jsx)(r.strong,{children:"FPS"})}),(0,n.jsx)(r.td,{children:"Frame rate / 帧率"}),(0,n.jsx)(r.td,{children:"Alert when FPS < 50 / FPS < 50 时告警"})]})]})]}),"\n",(0,n.jsx)(r.h2,{id:t[3].id,children:t[3].value}),"\n",(0,n.jsx)(r.h3,{id:t[4].id,children:t[4].value}),"\n",(0,n.jsxs)(r.ul,{children:["\n",(0,n.jsx)(r.li,{children:"A red badge shows the current unread alert count (clamped 0–99) / 红色角标显示当前未读告警数(0–99)"}),"\n",(0,n.jsx)(r.li,{children:"The number is drawn inside the button so it is never clipped at screen edges / 数字直接绘制在按钮内部,吸附屏幕边缘时不会被裁切"}),"\n",(0,n.jsx)(r.li,{children:"Opening the inspector panel clears the unread count / 打开检查器面板即清零未读数"}),"\n"]}),"\n",(0,n.jsx)(r.h3,{id:t[5].id,children:t[5].value}),"\n",(0,n.jsxs)(r.ul,{children:["\n",(0,n.jsx)(r.li,{children:"Lists triggered alerts with type, condition, and timestamp / 列出已触发告警的类型、条件与时间"}),"\n",(0,n.jsx)(r.li,{children:"Tap an alert to jump to the related panel (network / log / memory / FPS) / 点击告警可跳转到相关面板(网络 / 日志 / 内存 / FPS)"}),"\n"]}),"\n",(0,n.jsx)(r.h2,{id:t[6].id,children:t[6].value}),"\n",(0,n.jsxs)(r.ul,{children:["\n",(0,n.jsx)(r.li,{children:"Alerts are evaluated in-process and shown in the developer console only; they do not send data off-device / 告警仅在本机开发者控制台内评估与展示,不会将数据发送到设备外"}),"\n",(0,n.jsxs)(r.li,{children:[(0,n.jsx)(r.strong,{children:"Persisted to disk (since v1.11.0)"}),": triggered alerts are flushed to the SQLite ring buffer and replay into the Alerts tab after an app restart, and are included in the session archive export / ",(0,n.jsx)(r.strong,{children:"已落盘持久化(v1.11.0 起)"}),":已触发的告警会写入 SQLite 环形缓冲,重启后回放至 Alerts 标签页,并包含在会话存档导出中"]}),"\n",(0,n.jsx)(r.li,{children:"The alert system is tree-shaken out in release builds, like the rest of the inspector / 与检查器其余部分一样,告警系统在 release 构建中被 tree-shake 移除"}),"\n"]})]})},"/Alerts",{filePath:"pages/Alerts.md",timestamp:1786656379e3,pageMap:i.v,frontMatter:{},title:"Alerts / 告警系统"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,t,r){"use strict";r.d(t,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=6399)}),_N_E=e.O()}]); \ No newline at end of file +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[713],{6399:function(e,t,r){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Alerts",function(){return r(4571)}])},4571:function(e,t,r){"use strict";r.r(t),r.d(t,{useTOC:function(){return l}});var n=r(5893),i=r(7812),s=r(7080),a=r(8925);function l(e){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"How It Works / 工作原理",id:"how-it-works--工作原理",depth:2},{value:"Rule Types / 规则类型",id:"rule-types--规则类型",depth:2},{value:"UI Features / UI 功能",id:"ui-features--ui-功能",depth:2},{value:"Floating Button Badge / 悬浮球角标",id:"floating-button-badge--悬浮球角标",depth:3},{value:"Alerts Tab / Alerts 标签页",id:"alerts-tab--alerts-标签页",depth:3},{value:"Notes / 备注",id:"notes--备注",depth:2}]}t.default=(0,i.c)(function(e){let{toc:t=l(e)}=e,r={blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(r.h1,{children:"Alerts / 告警系统"}),"\n",(0,n.jsx)(r.h2,{id:t[0].id,children:t[0].value}),"\n",(0,n.jsxs)(r.blockquote,{children:["\n",(0,n.jsx)(r.p,{children:(0,n.jsx)(r.strong,{children:"Available since v1.3.0"})}),"\n",(0,n.jsx)(r.p,{children:(0,n.jsx)(r.strong,{children:"v1.3.0 起可用"})}),"\n"]}),"\n",(0,n.jsx)(r.p,{children:"The alert system lets you define rules that proactively surface problems across network requests, logs, memory, and FPS — without manually scanning the panels."}),"\n",(0,n.jsx)(r.p,{children:"告警系统允许你定义规则,主动暴露网络请求、日志、内存、FPS 方面的问题,无需手动逐个面板排查。"}),"\n",(0,n.jsx)(r.h2,{id:t[1].id,children:t[1].value}),"\n",(0,n.jsxs)(r.ul,{children:["\n",(0,n.jsxs)(r.li,{children:["Rules are evaluated by ",(0,n.jsx)(r.code,{children:"AlertService"})," whenever a relevant event occurs / 每当相关事件发生时,",(0,n.jsx)(r.code,{children:"AlertService"})," 会评估规则"]}),"\n",(0,n.jsxs)(r.li,{children:["Matched rules produce alerts, and the unread count is exposed via a ",(0,n.jsx)(r.code,{children:"ValueNotifier"})," / 命中的规则会生成告警,未读数通过 ",(0,n.jsx)(r.code,{children:"ValueNotifier"})," 暴露"]}),"\n",(0,n.jsx)(r.li,{children:"The floating button shows an unread-count badge; opening the panel clears it / 悬浮球显示未读数量角标,打开面板即清零"}),"\n",(0,n.jsxs)(r.li,{children:["An ",(0,n.jsx)(r.strong,{children:"Alerts"})," tab in the inspector panel lists all triggered alerts / 检查器面板的 ",(0,n.jsx)(r.strong,{children:"Alerts"})," 标签页列出所有已触发的告警"]}),"\n"]}),"\n",(0,n.jsx)(r.h2,{id:t[2].id,children:t[2].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(r.table,{children:[(0,n.jsx)(r.thead,{children:(0,n.jsxs)(r.tr,{children:[(0,n.jsx)(r.th,{children:"Type"}),(0,n.jsx)(r.th,{children:"Trigger / 触发条件"}),(0,n.jsx)(r.th,{children:"Example / 示例"})]})}),(0,n.jsxs)(r.tbody,{children:[(0,n.jsxs)(r.tr,{children:[(0,n.jsx)(r.td,{children:(0,n.jsx)(r.strong,{children:"Network"})}),(0,n.jsx)(r.td,{children:"Response status code / 响应状态码"}),(0,n.jsx)(r.td,{children:"Alert when status ≥ 400 / 状态码 ≥ 400 时告警"})]}),(0,n.jsxs)(r.tr,{children:[(0,n.jsx)(r.td,{children:(0,n.jsx)(r.strong,{children:"Log"})}),(0,n.jsx)(r.td,{children:"Log level or message / 日志级别或内容"}),(0,n.jsx)(r.td,{children:"Alert on error logs / 出现 error 日志时告警"})]}),(0,n.jsxs)(r.tr,{children:[(0,n.jsx)(r.td,{children:(0,n.jsx)(r.strong,{children:"Memory"})}),(0,n.jsx)(r.td,{children:"Dart heap / Native memory threshold / 内存阈值"}),(0,n.jsx)(r.td,{children:"Alert when Dart heap > 100 MB / Dart 堆 > 100 MB 时告警"})]}),(0,n.jsxs)(r.tr,{children:[(0,n.jsx)(r.td,{children:(0,n.jsx)(r.strong,{children:"FPS"})}),(0,n.jsx)(r.td,{children:"Frame rate / 帧率"}),(0,n.jsx)(r.td,{children:"Alert when FPS < 50 / FPS < 50 时告警"})]})]})]}),"\n",(0,n.jsx)(r.h2,{id:t[3].id,children:t[3].value}),"\n",(0,n.jsx)(r.h3,{id:t[4].id,children:t[4].value}),"\n",(0,n.jsxs)(r.ul,{children:["\n",(0,n.jsx)(r.li,{children:"A red badge shows the current unread alert count (clamped 0–99) / 红色角标显示当前未读告警数(0–99)"}),"\n",(0,n.jsx)(r.li,{children:"The number is drawn inside the button so it is never clipped at screen edges / 数字直接绘制在按钮内部,吸附屏幕边缘时不会被裁切"}),"\n",(0,n.jsx)(r.li,{children:"Opening the inspector panel clears the unread count / 打开检查器面板即清零未读数"}),"\n"]}),"\n",(0,n.jsx)(r.h3,{id:t[5].id,children:t[5].value}),"\n",(0,n.jsxs)(r.ul,{children:["\n",(0,n.jsx)(r.li,{children:"Lists triggered alerts with type, condition, and timestamp / 列出已触发告警的类型、条件与时间"}),"\n",(0,n.jsx)(r.li,{children:"Tap an alert to jump to the related panel (network / log / memory / FPS) / 点击告警可跳转到相关面板(网络 / 日志 / 内存 / FPS)"}),"\n"]}),"\n",(0,n.jsx)(r.h2,{id:t[6].id,children:t[6].value}),"\n",(0,n.jsxs)(r.ul,{children:["\n",(0,n.jsx)(r.li,{children:"Alerts are evaluated in-process and shown in the developer console only; they do not send data off-device / 告警仅在本机开发者控制台内评估与展示,不会将数据发送到设备外"}),"\n",(0,n.jsxs)(r.li,{children:[(0,n.jsx)(r.strong,{children:"Persisted to disk (since v1.11.0)"}),": triggered alerts are flushed to the SQLite ring buffer and replay into the Alerts tab after an app restart, and are included in the session archive export / ",(0,n.jsx)(r.strong,{children:"已落盘持久化(v1.11.0 起)"}),":已触发的告警会写入 SQLite 环形缓冲,重启后回放至 Alerts 标签页,并包含在会话存档导出中"]}),"\n",(0,n.jsx)(r.li,{children:"The alert system is tree-shaken out in release builds, like the rest of the inspector / 与检查器其余部分一样,告警系统在 release 构建中被 tree-shake 移除"}),"\n"]})]})},"/Alerts",{filePath:"pages/Alerts.md",timestamp:1786656379e3,pageMap:s.v,frontMatter:{},title:"Alerts / 告警系统"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,t,r){"use strict";r.d(t,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=6399)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Configuration-c728c0430f76605b.js b/docs/_next/static/chunks/pages/Configuration-59d63292bf84e213.js similarity index 97% rename from docs/_next/static/chunks/pages/Configuration-c728c0430f76605b.js rename to docs/_next/static/chunks/pages/Configuration-59d63292bf84e213.js index 13ebb0d..7b1b016 100644 --- a/docs/_next/static/chunks/pages/Configuration-c728c0430f76605b.js +++ b/docs/_next/static/chunks/pages/Configuration-59d63292bf84e213.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[717],{9321:function(e,i,s){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Configuration",function(){return s(3223)}])},3223:function(e,i,s){"use strict";s.r(i),s.d(i,{useTOC:function(){return h}});var n=s(5893),r=s(7812),d=s(7080),l=s(8925);function h(e){return[{value:"ZeroInspectorKit.init() Parameters / 初始化参数",id:"zeroinspectorkitinit-parameters--初始化参数",depth:2},{value:"Usage Examples / 使用示例",id:"usage-examples--使用示例",depth:2},{value:"Disable Specific Features / 禁用特定功能",id:"disable-specific-features--禁用特定功能",depth:3},{value:"With Log Callback / 带日志回调",id:"with-log-callback--带日志回调",depth:3},{value:"ConditionalInspector / 条件检查器组件",id:"conditionalinspector--条件检查器组件",depth:2},{value:"FloatingInspectorButton / 悬浮检查器按钮",id:"floatinginspectorbutton--悬浮检查器按钮",depth:2},{value:"InspectorLogInterceptor / 日志拦截器",id:"inspectorloginterceptor--日志拦截器",depth:2},{value:"InspectorRouteObserver / 路由观察者",id:"inspectorrouteobserver--路由观察者",depth:2},{value:"DatabaseRegistry / 数据库注册表",id:"databaseregistry--数据库注册表",depth:2},{value:"InspectorLog / 简化日志 API",id:"inspectorlog--简化日志-api",depth:2},{value:"MemoryInspectorService / 内存监控服务",id:"memoryinspectorservice--内存监控服务",depth:2},{value:"Full API / 完整接口",id:"full-api--完整接口",depth:3},{value:"Simplified API / 简化接口",id:"simplified-api--简化接口",depth:3},{value:"Flutter MemoryAllocations Bridge / 官方泄漏追踪桥接",id:"flutter-memoryallocations-bridge--官方泄漏追踪桥接",depth:3},{value:"ErrorService / 异常聚合服务",id:"errorservice--异常聚合服务",depth:2},{value:"PersistenceService / 持久化服务",id:"persistenceservice--持久化服务",depth:2},{value:"Tuning / 调参",id:"tuning--调参",depth:3},{value:"Persisted Data Manager / 持久化数据管理",id:"persisted-data-manager--持久化数据管理",depth:3},{value:"FpsService / FPS 监控服务",id:"fpsservice--fps-监控服务",depth:2}]}i.default=(0,r.c)(function(e){let{toc:i=h(e)}=e,s={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.h1,{children:"Configuration / 配置说明"}),"\n",(0,n.jsx)(s.h2,{id:i[0].id,children:i[0].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Parameter"}),(0,n.jsx)(s.th,{children:"Type"}),(0,n.jsx)(s.th,{children:"Default"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enable"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsxs)(s.td,{children:["Enable inspector (auto ",(0,n.jsx)(s.code,{children:"false"})," in release mode) / 启用检查器"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableLogCapture"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable log capture / 启用日志捕获"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableNetworkCapture"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable network interception / 启用网络拦截"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableErrorCapture"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable error aggregation (Errors tab) / 启用异常聚合(Errors 标签页)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enablePersistence"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Persist logs/network/errors to a SQLite ring buffer; logs & errors replay on launch / 将日志/网络/异常落盘到 SQLite 环形缓冲,启动时回放日志与异常"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableFlutterLeakTracker"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsxs)(s.td,{children:["Bridge Flutter’s official ",(0,n.jsx)(s.code,{children:"MemoryAllocations"})," as a second leak-detection source / 桥接 Flutter 官方 ",(0,n.jsx)(s.code,{children:"MemoryAllocations"})," 作为泄漏检测第二来源"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableDatabaseScan"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable database scan / 启用数据库扫描"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableRouteTracking"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable route tracking / 启用路由追踪"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableWidgetInspector"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable Widget tree snapshot / 启用 Widget 树快照"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableNetworkTimeline"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Prefer the network timeline (waterfall) in request details / 网络详情页默认展示时间轴(瀑布图)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"customButton"})}),(0,n.jsx)(s.td,{children:"Widget?"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"null"})}),(0,n.jsx)(s.td,{children:"Custom floating button widget / 自定义悬浮按钮"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"onLogCaptured"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"void Function(LogEntry)?"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"null"})}),(0,n.jsx)(s.td,{children:"Log capture callback for third-party integration / 日志捕获回调"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"maxNetworkItems"})}),(0,n.jsx)(s.td,{children:"int?"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"100"})}),(0,n.jsx)(s.td,{children:"Network request cache cap / 网络请求缓存上限"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"maxLogItems"})}),(0,n.jsx)(s.td,{children:"int?"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"500"})}),(0,n.jsx)(s.td,{children:"Log entry cache cap / 日志条目缓存上限"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"maxRouteItems"})}),(0,n.jsx)(s.td,{children:"int?"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"200"})}),(0,n.jsx)(s.td,{children:"Route record cache cap / 路由记录缓存上限"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"maxBodyPreviewBytes"})}),(0,n.jsx)(s.td,{children:"int?"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"32KB"})}),(0,n.jsx)(s.td,{children:"Body preview cap, longer bodies truncated / body 预览字节上限,超出截断"})]})]})]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"enableWidgetInspector"})," / ",(0,n.jsx)(s.code,{children:"enableNetworkTimeline"})," are also exposed on ",(0,n.jsx)(s.code,{children:"runAppWithInspector()"}),". (",(0,n.jsx)(s.code,{children:"wrapApp()"})," takes only ",(0,n.jsx)(s.code,{children:"enable"}),".)"]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"enableWidgetInspector"})," / ",(0,n.jsx)(s.code,{children:"enableNetworkTimeline"})," 也可在 ",(0,n.jsx)(s.code,{children:"runAppWithInspector()"})," 上设置(",(0,n.jsx)(s.code,{children:"wrapApp()"})," 仅接受 ",(0,n.jsx)(s.code,{children:"enable"}),")。"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[1].id,children:i[1].value}),"\n",(0,n.jsx)(s.h3,{id:i[2].id,children:i[2].value}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"init"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" enableLogCapture"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" true"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" enableNetworkCapture"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" false"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Disable network monitoring / 禁用网络监控"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" enableDatabaseScan"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" true"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" enableRouteTracking"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" false"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Disable route tracking / 禁用路由追踪"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})})]})}),"\n",(0,n.jsx)(s.h3,{id:i[3].id,children:i[3].value}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"init"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" onLogCaptured"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" (entry) {"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" // Forward to your logging service / 转发到你的日志服务"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" myLogger."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"log"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(entry.message);"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" },"})}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})})]})}),"\n",(0,n.jsx)(s.h2,{id:i[4].id,children:i[4].value}),"\n",(0,n.jsx)(s.p,{children:"A convenience widget that automatically shows/hides the inspector based on build mode."}),"\n",(0,n.jsx)(s.p,{children:"根据构建模式自动显示/隐藏检查器的便利组件。"}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ConditionalInspector"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" child"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" YourAppWidget"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(),"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:")"})})]})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Parameter"}),(0,n.jsx)(s.th,{children:"Type"}),(0,n.jsx)(s.th,{children:"Default"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"child"})}),(0,n.jsx)(s.td,{children:"Widget"}),(0,n.jsx)(s.td,{children:"required"}),(0,n.jsx)(s.td,{children:"Child widget / 子组件"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enabled"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable inspector / 启用检查器"})]})]})]}),"\n",(0,n.jsx)(s.h2,{id:i[5].id,children:i[5].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Parameter"}),(0,n.jsx)(s.th,{children:"Type"}),(0,n.jsx)(s.th,{children:"Default"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsx)(s.tbody,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enabled"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsxs)(s.td,{children:["Enable button (auto ",(0,n.jsx)(s.code,{children:"false"})," in release mode) / 启用按钮"]})]})})]}),"\n",(0,n.jsx)(s.h2,{id:i[6].id,children:i[6].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Method"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"start()"})}),(0,n.jsx)(s.td,{children:"Start capturing logs / 开始捕获日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"stop()"})}),(0,n.jsx)(s.td,{children:"Stop capturing logs / 停止捕获日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"log(level, message, tag)"})}),(0,n.jsx)(s.td,{children:"Add a log entry / 添加日志条目"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"verbose(message, tag)"})}),(0,n.jsx)(s.td,{children:"Add verbose log / 添加详细日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"debug(message, tag)"})}),(0,n.jsx)(s.td,{children:"Add debug log / 添加调试日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"info(message, tag)"})}),(0,n.jsx)(s.td,{children:"Add info log / 添加信息日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"warning(message, tag)"})}),(0,n.jsx)(s.td,{children:"Add warning log / 添加警告日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"error(message, tag)"})}),(0,n.jsx)(s.td,{children:"Add error log / 添加错误日志"})]})]})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Property"}),(0,n.jsx)(s.th,{children:"Type"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsx)(s.tbody,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"onLogCaptured"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"void Function(LogEntry)?"})}),(0,n.jsx)(s.td,{children:"Callback when a log is captured / 日志捕获回调"})]})})]}),"\n",(0,n.jsx)(s.h2,{id:i[7].id,children:i[7].value}),"\n",(0,n.jsxs)(s.p,{children:["Navigator observer for tracking route changes. Auto-injected when using ",(0,n.jsx)(s.code,{children:"runAppWithInspector()"})," or ",(0,n.jsx)(s.code,{children:"wrapApp()"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["用于追踪路由变化的 Navigator 观察者。使用 ",(0,n.jsx)(s.code,{children:"runAppWithInspector()"})," 或 ",(0,n.jsx)(s.code,{children:"wrapApp()"})," 时自动注入。"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MaterialApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" navigatorObservers"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ["}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorRouteObserver"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"()],"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" home"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyHomePage"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(),"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:")"})})]})}),"\n",(0,n.jsx)(s.h2,{id:i[8].id,children:i[8].value}),"\n",(0,n.jsx)(s.p,{children:"Register custom database providers:"}),"\n",(0,n.jsx)(s.p,{children:"注册自定义数据库提供者:"}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsx)(s.code,{children:(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"DatabaseRegistry"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"registerProvider"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"SqliteDatabaseProvider"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"());"})]})})}),"\n",(0,n.jsxs)(s.p,{children:["See ",(0,n.jsx)(s.a,{href:"Custom-Database-Provider",children:"Custom Database Provider"})," for more details."]}),"\n",(0,n.jsxs)(s.p,{children:["详见 ",(0,n.jsx)(s.a,{href:"Custom-Database-Provider",children:"自定义数据库提供者"}),"。"]}),"\n",(0,n.jsx)(s.h2,{id:i[9].id,children:i[9].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.1.2 / v1.1.2 起可用"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["A static wrapper around ",(0,n.jsx)(s.code,{children:"InspectorLogInterceptor.instance"})," for shorter log calls."]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"InspectorLogInterceptor.instance"})," 的静态包装,用于更简短的日志调用。"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"v"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Verbose log'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"d"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Debug log'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"i"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Info log'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", tag"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'Auth'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"w"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Warning log'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"e"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Error log'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]})]})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Method"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"start()"})}),(0,n.jsx)(s.td,{children:"Start capturing logs / 开始捕获日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"stop()"})}),(0,n.jsx)(s.td,{children:"Stop capturing logs / 停止捕获日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"log(level, message, {tag})"})}),(0,n.jsx)(s.td,{children:"Add a log entry / 添加日志条目"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"v(message, {tag})"})}),(0,n.jsx)(s.td,{children:"Add verbose log / 添加详细日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"d(message, {tag})"})}),(0,n.jsx)(s.td,{children:"Add debug log / 添加调试日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"i(message, {tag})"})}),(0,n.jsx)(s.td,{children:"Add info log / 添加信息日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"w(message, {tag})"})}),(0,n.jsx)(s.td,{children:"Add warning log / 添加警告日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"e(message, {tag})"})}),(0,n.jsx)(s.td,{children:"Add error log / 添加错误日志"})]})]})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Property"}),(0,n.jsx)(s.th,{children:"Type"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsx)(s.tbody,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"isRunning"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:"Whether log capture is currently active / 日志捕获是否正在运行"})]})})]}),"\n",(0,n.jsx)(s.h2,{id:i[10].id,children:i[10].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.1.0 / v1.1.0 起可用"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Singleton service for memory monitoring and leak detection, extends ",(0,n.jsx)(s.code,{children:"ChangeNotifier"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["内存监控与泄漏检测单例服务,继承 ",(0,n.jsx)(s.code,{children:"ChangeNotifier"}),"。"]}),"\n",(0,n.jsx)(s.h3,{id:i[11].id,children:i[11].value}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Leak tracking (full form) / 泄漏追踪(完整写法)"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MemoryInspectorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"trackObject"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" myController,"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" tag"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'HomeController_textController'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" expectedReleaseAfter"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Duration"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(seconds"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 30"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"),"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MemoryInspectorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"untrackObject"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(myController);"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MemoryInspectorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"clearLeakRecords"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n",(0,n.jsx)(s.h3,{id:i[12].id,children:i[12].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.1.2 / v1.1.2 起可用"}),"\n"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Extension method on Object / Object 上的扩展方法"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"myBloc."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"trackMemoryLeak"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(tag"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'HomePage_myBloc'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Top-level function / 顶层函数"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"trackMemoryLeak"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(myBloc, tag"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'HomePage_myBloc'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Cancel tracking / 取消追踪"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"myBloc."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"untrackMemoryLeak"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n",(0,n.jsxs)(s.p,{children:["See ",(0,n.jsx)(s.a,{href:"Memory-Viewer",children:"Memory Viewer"})," for full feature details."]}),"\n",(0,n.jsxs)(s.p,{children:["完整功能详情见 ",(0,n.jsx)(s.a,{href:"Memory-Viewer",children:"Memory Viewer"}),"。"]}),"\n",(0,n.jsx)(s.h3,{id:i[13].id,children:i[13].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.9.0 / v1.9.0 起可用"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["The leak detector’s own ",(0,n.jsx)(s.code,{children:"WeakReference"})," state machine has a blind spot: an object whose ",(0,n.jsx)(s.code,{children:"dispose()"})," ran but which has not been GC’d yet still resolves through the weak reference and is flagged as a suspected leak (false positive). ",(0,n.jsx)(s.code,{children:"LeakTrackerBridge"})," subscribes to Flutter’s official ",(0,n.jsx)(s.code,{children:"FlutterMemoryAllocations"})," (the data source behind ",(0,n.jsx)(s.code,{children:"leak_tracker"}),") as a ",(0,n.jsx)(s.strong,{children:"second source"}),": once the official stream reports ",(0,n.jsx)(s.code,{children:"disposed"}),", the object is treated as released (awaiting GC) even if the weak reference still resolves — cutting false positives."]}),"\n",(0,n.jsxs)(s.p,{children:["自研泄漏检测的 ",(0,n.jsx)(s.code,{children:"WeakReference"})," 状态机有一个盲点:对象已调用 ",(0,n.jsx)(s.code,{children:"dispose()"})," 但尚未被 GC 时,弱引用仍存活,会被判定为”疑似泄漏”(误报)。",(0,n.jsx)(s.code,{children:"LeakTrackerBridge"})," 订阅 Flutter 官方的 ",(0,n.jsx)(s.code,{children:"FlutterMemoryAllocations"}),"(",(0,n.jsx)(s.code,{children:"leak_tracker"})," 背后的数据源)作为",(0,n.jsx)(s.strong,{children:"第二来源"}),":只要官方上报了 ",(0,n.jsx)(s.code,{children:"disposed"}),",即便弱引用仍存活也判定为已释放(等待 GC),从而显著降低误报。"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["Enabled by default via ",(0,n.jsx)(s.code,{children:"ZeroInspectorKit.init()"}),"’s ",(0,n.jsx)(s.code,{children:"enableFlutterLeakTracker"})," / 通过 ",(0,n.jsx)(s.code,{children:"init()"})," 的 ",(0,n.jsx)(s.code,{children:"enableFlutterLeakTracker"})," 默认启用"]}),"\n",(0,n.jsxs)(s.li,{children:["Official events only cover framework types that report to ",(0,n.jsx)(s.code,{children:"FlutterMemoryAllocations"})," (Image / Picture / Layer …) — it ",(0,n.jsx)(s.strong,{children:"supplements"}),", not replaces, the custom detector / 官方事件只覆盖会上报给 ",(0,n.jsx)(s.code,{children:"FlutterMemoryAllocations"})," 的框架类型(如 Image / Picture / Layer),因此是",(0,n.jsx)(s.strong,{children:"补充"}),"而非替代自研方案"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[14].id,children:i[14].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.9.0 / v1.9.0 起可用"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Singleton service for aggregated error capture, extends ",(0,n.jsx)(s.code,{children:"ChangeNotifier"}),". Hooks ",(0,n.jsx)(s.code,{children:"FlutterError.onError"})," (keeping the default red error behavior) and dedups each exception by type + stack signature. See ",(0,n.jsx)(s.a,{href:"Errors",children:"Errors"})," for full feature details."]}),"\n",(0,n.jsxs)(s.p,{children:["异常聚合单例服务,继承 ",(0,n.jsx)(s.code,{children:"ChangeNotifier"}),"。接管 ",(0,n.jsx)(s.code,{children:"FlutterError.onError"}),"(保留默认红色报错行为),按类型 + 堆栈签名去重聚合。完整功能见 ",(0,n.jsx)(s.a,{href:"Errors",children:"Errors"}),"。"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Report an exception manually (gRPC / custom protocols) / 手动上报异常"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ErrorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"report"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(error, stackTrace, "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'myModule'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Read aggregated records (newest first) / 读取聚合记录(最新在前)"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ErrorRecord"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" r "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ErrorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.errors.first;"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Toggle capture / 切换抓取开关"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ErrorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.isEnabled "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" false"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Clear all records / 清空全部记录"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ErrorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"clear"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Member"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"errors"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"List"})," — aggregated records, newest first / 聚合记录,最新在前"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"errorCount"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"int"})," — aggregated record count / 聚合记录条数"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"isEnabled"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"bool"})," — capture toggle (programmatic) / 抓取开关(代码控制)"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"report(exception, stack, [context])"})}),(0,n.jsx)(s.td,{children:"Manually report an exception / 手动上报异常"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"restore(records)"})}),(0,n.jsx)(s.td,{children:"Restore persisted records (replay on launch) / 恢复持久化记录(启动回放)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"clear()"})}),(0,n.jsx)(s.td,{children:"Clear all aggregated records / 清空所有聚合记录"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"install()"})," / ",(0,n.jsx)(s.code,{children:"uninstall()"})]}),(0,n.jsxs)(s.td,{children:["Hook / restore ",(0,n.jsx)(s.code,{children:"FlutterError.onError"})," / 接管 / 还原 ",(0,n.jsx)(s.code,{children:"FlutterError.onError"})]})]})]})]}),"\n",(0,n.jsx)(s.h2,{id:i[15].id,children:i[15].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.9.0 / v1.9.0 起可用"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Singleton service that async-flushes logs, network requests, and aggregated errors to a local SQLite ",(0,n.jsx)(s.strong,{children:"ring buffer"})," (",(0,n.jsx)(s.code,{children:"zero_inspector_kit.db"}),") so data survives app restarts. On launch, ",(0,n.jsx)(s.strong,{children:"logs and aggregated errors replay into their tabs"}),"; network requests stay archived on disk for later export. Everything is guarded and degrades gracefully: if the DB is unavailable (e.g. desktop without sqflite FFI) ",(0,n.jsx)(s.code,{children:"isEnabled"})," is ",(0,n.jsx)(s.code,{children:"false"})," and writes become no-ops, never affecting the host app."]}),"\n",(0,n.jsxs)(s.p,{children:["单例服务,将日志、网络请求与聚合异常异步落盘到本地 SQLite ",(0,n.jsx)(s.strong,{children:"环形缓冲"}),"(",(0,n.jsx)(s.code,{children:"zero_inspector_kit.db"}),"),使数据跨重启不丢。启动时",(0,n.jsx)(s.strong,{children:"日志与聚合异常回放入各自标签页"}),";网络请求保留在磁盘存档,供之后导出。所有操作都有保护并优雅降级:数据库不可用(如桌面端未配置 sqflite FFI)时 ",(0,n.jsx)(s.code,{children:"isEnabled"})," 为 ",(0,n.jsx)(s.code,{children:"false"}),",写入变为空操作,绝不影响宿主应用。"]}),"\n",(0,n.jsx)(s.h3,{id:i[16].id,children:i[16].value}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"await"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" PersistenceService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"init"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" maxRowsPerTable"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 5000"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// rows kept per table / 每表保留行数"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" retention"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Duration"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(days"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 7"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"), "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// retention window / 保留时长"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" flushInterval"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Duration"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(seconds"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 2"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"), "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// disk flush interval / 刷盘间隔"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})})]})}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:["Enabled by default via ",(0,n.jsx)(s.code,{children:"ZeroInspectorKit.init()"}),"’s ",(0,n.jsx)(s.code,{children:"enablePersistence"})," — the ring buffer flushes automatically, so no manual ",(0,n.jsx)(s.code,{children:"init()"})," call is needed in normal use."]}),"\n",(0,n.jsxs)(s.p,{children:["通过 ",(0,n.jsx)(s.code,{children:"init()"})," 的 ",(0,n.jsx)(s.code,{children:"enablePersistence"})," 默认启用——环形缓冲会自动刷盘,常规使用无需手动调用 ",(0,n.jsx)(s.code,{children:"init()"}),"。"]}),"\n"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Member"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"isEnabled"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"bool"})," — whether the DB is available / 数据库是否可用"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"maxRowsPerTable"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"int"})," — row cap per table (ring-buffer trim line) / 每表行数上限"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"enqueueLog(e)"})," / ",(0,n.jsx)(s.code,{children:"enqueueNetwork(r)"})," / ",(0,n.jsx)(s.code,{children:"enqueueError(e)"})]}),(0,n.jsx)(s.td,{children:"Enqueue an item for async flush / 入队待异步落盘"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"flush()"})}),(0,n.jsx)(s.td,{children:"Flush the buffer to disk and trim / 刷盘并按环形缓冲裁剪"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"loadLogs()"})," / ",(0,n.jsx)(s.code,{children:"loadErrors()"})," / ",(0,n.jsx)(s.code,{children:"loadNetworkJson()"})]}),(0,n.jsx)(s.td,{children:"Load persisted data, newest first / 读取已持久化数据(最新在前)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"buildSessionArchiveJson()"})}),(0,n.jsx)(s.td,{children:"Build the full-session archive JSON (logs + errors + network) / 构建完整会话存档 JSON"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"exportSessionArchiveAndShare()"})}),(0,n.jsx)(s.td,{children:"Export & share the full-session archive via the system share sheet / 导出并分享完整会话存档"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"clearAll()"})}),(0,n.jsx)(s.td,{children:"Clear persisted data on disk / 清空磁盘上的持久化数据"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"dispose()"})}),(0,n.jsx)(s.td,{children:"Flush then close / 先刷盘再关闭"})]})]})]}),"\n",(0,n.jsx)(s.h3,{id:i[17].id,children:i[17].value}),"\n",(0,n.jsxs)(s.p,{children:["Tap the ",(0,n.jsx)(s.strong,{children:"storage icon"})," in the panel header to open the ",(0,n.jsx)(s.strong,{children:"Persisted data"})," sheet: it shows the current row counts per category (",(0,n.jsx)(s.code,{children:"logs / network / errors"}),"), the per-table cap, and whether the data will be replayed on the next launch. Actions:"]}),"\n",(0,n.jsxs)(s.p,{children:["点击面板头部的",(0,n.jsx)(s.strong,{children:"存储图标"}),"打开 ",(0,n.jsx)(s.strong,{children:"Persisted data"})," 管理弹层:展示各类别当前行数(",(0,n.jsx)(s.code,{children:"logs / network / errors"}),")、每表上限,以及下次启动是否会回放。支持的操作:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Export & share session archive"})," — share a JSON snapshot of the whole session / ",(0,n.jsx)(s.strong,{children:"导出并分享会话存档"}),"——分享本次会话完整 JSON 快照"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Clear disk"})," — wipe persisted rows so the next launch replays nothing / ",(0,n.jsx)(s.strong,{children:"清空磁盘"}),"——清空持久化数据,下次启动干净"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Clear disk & lists"})," — wipe disk and the in-memory lists together / ",(0,n.jsx)(s.strong,{children:"同时清空磁盘与列表"}),"——连同内存列表一并清空"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[18].id,children:i[18].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.2.0 / v1.2.0 起可用"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Singleton service for FPS monitoring, extends ",(0,n.jsx)(s.code,{children:"ChangeNotifier"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["FPS 监控单例服务,继承 ",(0,n.jsx)(s.code,{children:"ChangeNotifier"}),"。"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"start"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"stop"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"clear"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" fps "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.currentFps;"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" jankRate "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.jankRate;"})]})]})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Method"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"start()"})}),(0,n.jsx)(s.td,{children:"Start FPS monitoring / 开始 FPS 监控"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"stop()"})}),(0,n.jsx)(s.td,{children:"Stop FPS monitoring / 停止 FPS 监控"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"clear()"})}),(0,n.jsx)(s.td,{children:"Clear all historical data and counters / 清空所有历史数据和计数器"})]})]})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Property"}),(0,n.jsx)(s.th,{children:"Type"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"isRunning"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:"Whether monitoring is currently active / 是否正在监控"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"currentFps"})}),(0,n.jsx)(s.td,{children:"double"}),(0,n.jsx)(s.td,{children:"Current FPS (updated every 500ms) / 当前 FPS(每 500ms 更新)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"jankRate"})}),(0,n.jsx)(s.td,{children:"double"}),(0,n.jsx)(s.td,{children:"Jank rate as percentage / 卡顿率(百分比)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"totalFrameCount"})}),(0,n.jsx)(s.td,{children:"int"}),(0,n.jsx)(s.td,{children:"Total frames captured / 总帧数"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"totalJankyCount"})}),(0,n.jsx)(s.td,{children:"int"}),(0,n.jsx)(s.td,{children:"Total janky frames (>16ms) / 总卡顿帧数(>16ms)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"lastFrameJanky"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:"Whether the most recent frame was janky / 最近一帧是否卡顿"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"fpsHistory"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"List"})}),(0,n.jsx)(s.td,{children:"Recent 60 FPS values (unmodifiable) / 最近 60 个 FPS 值(不可变)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"frameRecords"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"List"})}),(0,n.jsx)(s.td,{children:"Recent frame records (unmodifiable, up to 3600) / 最近帧记录(不可变,最多 3600 条)"})]})]})]}),"\n",(0,n.jsxs)(s.p,{children:["See ",(0,n.jsx)(s.a,{href:"FPS-Viewer",children:"FPS Viewer"})," for full feature details."]}),"\n",(0,n.jsxs)(s.p,{children:["完整功能详情见 ",(0,n.jsx)(s.a,{href:"FPS-Viewer",children:"FPS Viewer"}),"。"]})]})},"/Configuration",{filePath:"pages/Configuration.md",timestamp:1786656379e3,pageMap:d.v,frontMatter:{},title:"Configuration / 配置说明"},"undefined"==typeof RemoteContent?h:RemoteContent.useTOC)},7080:function(e,i,s){"use strict";s.d(i,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=9321)}),_N_E=e.O()}]); \ No newline at end of file +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[717],{9321:function(e,i,s){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Configuration",function(){return s(3223)}])},3223:function(e,i,s){"use strict";s.r(i),s.d(i,{useTOC:function(){return h}});var n=s(5893),r=s(7812),d=s(7080),l=s(8925);function h(e){return[{value:"ZeroInspectorKit.init() Parameters / 初始化参数",id:"zeroinspectorkitinit-parameters--初始化参数",depth:2},{value:"Usage Examples / 使用示例",id:"usage-examples--使用示例",depth:2},{value:"Disable Specific Features / 禁用特定功能",id:"disable-specific-features--禁用特定功能",depth:3},{value:"With Log Callback / 带日志回调",id:"with-log-callback--带日志回调",depth:3},{value:"ConditionalInspector / 条件检查器组件",id:"conditionalinspector--条件检查器组件",depth:2},{value:"FloatingInspectorButton / 悬浮检查器按钮",id:"floatinginspectorbutton--悬浮检查器按钮",depth:2},{value:"InspectorLogInterceptor / 日志拦截器",id:"inspectorloginterceptor--日志拦截器",depth:2},{value:"InspectorRouteObserver / 路由观察者",id:"inspectorrouteobserver--路由观察者",depth:2},{value:"DatabaseRegistry / 数据库注册表",id:"databaseregistry--数据库注册表",depth:2},{value:"InspectorLog / 简化日志 API",id:"inspectorlog--简化日志-api",depth:2},{value:"MemoryInspectorService / 内存监控服务",id:"memoryinspectorservice--内存监控服务",depth:2},{value:"Full API / 完整接口",id:"full-api--完整接口",depth:3},{value:"Simplified API / 简化接口",id:"simplified-api--简化接口",depth:3},{value:"Flutter MemoryAllocations Bridge / 官方泄漏追踪桥接",id:"flutter-memoryallocations-bridge--官方泄漏追踪桥接",depth:3},{value:"ErrorService / 异常聚合服务",id:"errorservice--异常聚合服务",depth:2},{value:"PersistenceService / 持久化服务",id:"persistenceservice--持久化服务",depth:2},{value:"Tuning / 调参",id:"tuning--调参",depth:3},{value:"Persisted Data Manager / 持久化数据管理",id:"persisted-data-manager--持久化数据管理",depth:3},{value:"FpsService / FPS 监控服务",id:"fpsservice--fps-监控服务",depth:2}]}i.default=(0,r.c)(function(e){let{toc:i=h(e)}=e,s={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.h1,{children:"Configuration / 配置说明"}),"\n",(0,n.jsx)(s.h2,{id:i[0].id,children:i[0].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Parameter"}),(0,n.jsx)(s.th,{children:"Type"}),(0,n.jsx)(s.th,{children:"Default"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enable"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsxs)(s.td,{children:["Enable inspector (auto ",(0,n.jsx)(s.code,{children:"false"})," in release mode) / 启用检查器"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableLogCapture"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable log capture / 启用日志捕获"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableNetworkCapture"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable network interception / 启用网络拦截"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableErrorCapture"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable error aggregation (Errors tab) / 启用异常聚合(Errors 标签页)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enablePersistence"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Persist logs/network/errors to a SQLite ring buffer; logs & errors replay on launch / 将日志/网络/异常落盘到 SQLite 环形缓冲,启动时回放日志与异常"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableFlutterLeakTracker"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsxs)(s.td,{children:["Bridge Flutter’s official ",(0,n.jsx)(s.code,{children:"MemoryAllocations"})," as a second leak-detection source / 桥接 Flutter 官方 ",(0,n.jsx)(s.code,{children:"MemoryAllocations"})," 作为泄漏检测第二来源"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableDatabaseScan"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable database scan / 启用数据库扫描"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableRouteTracking"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable route tracking / 启用路由追踪"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableWidgetInspector"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable Widget tree snapshot / 启用 Widget 树快照"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enableNetworkTimeline"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Prefer the network timeline (waterfall) in request details / 网络详情页默认展示时间轴(瀑布图)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"customButton"})}),(0,n.jsx)(s.td,{children:"Widget?"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"null"})}),(0,n.jsx)(s.td,{children:"Custom floating button widget / 自定义悬浮按钮"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"onLogCaptured"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"void Function(LogEntry)?"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"null"})}),(0,n.jsx)(s.td,{children:"Log capture callback for third-party integration / 日志捕获回调"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"maxNetworkItems"})}),(0,n.jsx)(s.td,{children:"int?"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"100"})}),(0,n.jsx)(s.td,{children:"Network request cache cap / 网络请求缓存上限"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"maxLogItems"})}),(0,n.jsx)(s.td,{children:"int?"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"500"})}),(0,n.jsx)(s.td,{children:"Log entry cache cap / 日志条目缓存上限"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"maxRouteItems"})}),(0,n.jsx)(s.td,{children:"int?"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"200"})}),(0,n.jsx)(s.td,{children:"Route record cache cap / 路由记录缓存上限"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"maxBodyPreviewBytes"})}),(0,n.jsx)(s.td,{children:"int?"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"32KB"})}),(0,n.jsx)(s.td,{children:"Body preview cap, longer bodies truncated / body 预览字节上限,超出截断"})]})]})]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"enableWidgetInspector"})," / ",(0,n.jsx)(s.code,{children:"enableNetworkTimeline"})," are also exposed on ",(0,n.jsx)(s.code,{children:"runAppWithInspector()"}),". (",(0,n.jsx)(s.code,{children:"wrapApp()"})," takes only ",(0,n.jsx)(s.code,{children:"enable"}),".)"]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"enableWidgetInspector"})," / ",(0,n.jsx)(s.code,{children:"enableNetworkTimeline"})," 也可在 ",(0,n.jsx)(s.code,{children:"runAppWithInspector()"})," 上设置(",(0,n.jsx)(s.code,{children:"wrapApp()"})," 仅接受 ",(0,n.jsx)(s.code,{children:"enable"}),")。"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[1].id,children:i[1].value}),"\n",(0,n.jsx)(s.h3,{id:i[2].id,children:i[2].value}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"init"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" enableLogCapture"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" true"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" enableNetworkCapture"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" false"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Disable network monitoring / 禁用网络监控"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" enableDatabaseScan"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" true"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" enableRouteTracking"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" false"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Disable route tracking / 禁用路由追踪"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})})]})}),"\n",(0,n.jsx)(s.h3,{id:i[3].id,children:i[3].value}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"init"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" onLogCaptured"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" (entry) {"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" // Forward to your logging service / 转发到你的日志服务"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" myLogger."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"log"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(entry.message);"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" },"})}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})})]})}),"\n",(0,n.jsx)(s.h2,{id:i[4].id,children:i[4].value}),"\n",(0,n.jsx)(s.p,{children:"A convenience widget that automatically shows/hides the inspector based on build mode."}),"\n",(0,n.jsx)(s.p,{children:"根据构建模式自动显示/隐藏检查器的便利组件。"}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ConditionalInspector"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" child"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" YourAppWidget"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(),"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:")"})})]})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Parameter"}),(0,n.jsx)(s.th,{children:"Type"}),(0,n.jsx)(s.th,{children:"Default"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"child"})}),(0,n.jsx)(s.td,{children:"Widget"}),(0,n.jsx)(s.td,{children:"required"}),(0,n.jsx)(s.td,{children:"Child widget / 子组件"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enabled"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsx)(s.td,{children:"Enable inspector / 启用检查器"})]})]})]}),"\n",(0,n.jsx)(s.h2,{id:i[5].id,children:i[5].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Parameter"}),(0,n.jsx)(s.th,{children:"Type"}),(0,n.jsx)(s.th,{children:"Default"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsx)(s.tbody,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"enabled"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"true"})}),(0,n.jsxs)(s.td,{children:["Enable button (auto ",(0,n.jsx)(s.code,{children:"false"})," in release mode) / 启用按钮"]})]})})]}),"\n",(0,n.jsx)(s.h2,{id:i[6].id,children:i[6].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Method"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"start()"})}),(0,n.jsx)(s.td,{children:"Start capturing logs / 开始捕获日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"stop()"})}),(0,n.jsx)(s.td,{children:"Stop capturing logs / 停止捕获日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"log(level, message, tag)"})}),(0,n.jsx)(s.td,{children:"Add a log entry / 添加日志条目"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"verbose(message, tag)"})}),(0,n.jsx)(s.td,{children:"Add verbose log / 添加详细日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"debug(message, tag)"})}),(0,n.jsx)(s.td,{children:"Add debug log / 添加调试日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"info(message, tag)"})}),(0,n.jsx)(s.td,{children:"Add info log / 添加信息日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"warning(message, tag)"})}),(0,n.jsx)(s.td,{children:"Add warning log / 添加警告日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"error(message, tag)"})}),(0,n.jsx)(s.td,{children:"Add error log / 添加错误日志"})]})]})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Property"}),(0,n.jsx)(s.th,{children:"Type"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsx)(s.tbody,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"onLogCaptured"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"void Function(LogEntry)?"})}),(0,n.jsx)(s.td,{children:"Callback when a log is captured / 日志捕获回调"})]})})]}),"\n",(0,n.jsx)(s.h2,{id:i[7].id,children:i[7].value}),"\n",(0,n.jsxs)(s.p,{children:["Navigator observer for tracking route changes. Auto-injected when using ",(0,n.jsx)(s.code,{children:"runAppWithInspector()"})," or ",(0,n.jsx)(s.code,{children:"wrapApp()"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["用于追踪路由变化的 Navigator 观察者。使用 ",(0,n.jsx)(s.code,{children:"runAppWithInspector()"})," 或 ",(0,n.jsx)(s.code,{children:"wrapApp()"})," 时自动注入。"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MaterialApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" navigatorObservers"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ["}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorRouteObserver"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"()],"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" home"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyHomePage"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(),"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:")"})})]})}),"\n",(0,n.jsx)(s.h2,{id:i[8].id,children:i[8].value}),"\n",(0,n.jsx)(s.p,{children:"Register custom database providers:"}),"\n",(0,n.jsx)(s.p,{children:"注册自定义数据库提供者:"}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsx)(s.code,{children:(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"DatabaseRegistry"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"registerProvider"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"SqliteDatabaseProvider"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"());"})]})})}),"\n",(0,n.jsxs)(s.p,{children:["See ",(0,n.jsx)(s.a,{href:"Custom-Database-Provider",children:"Custom Database Provider"})," for more details."]}),"\n",(0,n.jsxs)(s.p,{children:["详见 ",(0,n.jsx)(s.a,{href:"Custom-Database-Provider",children:"自定义数据库提供者"}),"。"]}),"\n",(0,n.jsx)(s.h2,{id:i[9].id,children:i[9].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.1.2 / v1.1.2 起可用"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["A static wrapper around ",(0,n.jsx)(s.code,{children:"InspectorLogInterceptor.instance"})," for shorter log calls."]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"InspectorLogInterceptor.instance"})," 的静态包装,用于更简短的日志调用。"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"v"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Verbose log'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"d"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Debug log'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"i"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Info log'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", tag"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'Auth'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"w"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Warning log'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"e"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Error log'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]})]})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Method"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"start()"})}),(0,n.jsx)(s.td,{children:"Start capturing logs / 开始捕获日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"stop()"})}),(0,n.jsx)(s.td,{children:"Stop capturing logs / 停止捕获日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"log(level, message, {tag})"})}),(0,n.jsx)(s.td,{children:"Add a log entry / 添加日志条目"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"v(message, {tag})"})}),(0,n.jsx)(s.td,{children:"Add verbose log / 添加详细日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"d(message, {tag})"})}),(0,n.jsx)(s.td,{children:"Add debug log / 添加调试日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"i(message, {tag})"})}),(0,n.jsx)(s.td,{children:"Add info log / 添加信息日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"w(message, {tag})"})}),(0,n.jsx)(s.td,{children:"Add warning log / 添加警告日志"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"e(message, {tag})"})}),(0,n.jsx)(s.td,{children:"Add error log / 添加错误日志"})]})]})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Property"}),(0,n.jsx)(s.th,{children:"Type"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsx)(s.tbody,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"isRunning"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:"Whether log capture is currently active / 日志捕获是否正在运行"})]})})]}),"\n",(0,n.jsx)(s.h2,{id:i[10].id,children:i[10].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.1.0 / v1.1.0 起可用"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Singleton service for memory monitoring and leak detection, extends ",(0,n.jsx)(s.code,{children:"ChangeNotifier"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["内存监控与泄漏检测单例服务,继承 ",(0,n.jsx)(s.code,{children:"ChangeNotifier"}),"。"]}),"\n",(0,n.jsx)(s.h3,{id:i[11].id,children:i[11].value}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Leak tracking (full form) / 泄漏追踪(完整写法)"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MemoryInspectorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"trackObject"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" myController,"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" tag"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'HomeController_textController'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" expectedReleaseAfter"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Duration"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(seconds"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 30"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"),"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MemoryInspectorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"untrackObject"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(myController);"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MemoryInspectorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"clearLeakRecords"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n",(0,n.jsx)(s.h3,{id:i[12].id,children:i[12].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.1.2 / v1.1.2 起可用"}),"\n"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Extension method on Object / Object 上的扩展方法"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"myBloc."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"trackMemoryLeak"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(tag"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'HomePage_myBloc'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Top-level function / 顶层函数"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"trackMemoryLeak"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(myBloc, tag"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'HomePage_myBloc'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Cancel tracking / 取消追踪"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"myBloc."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"untrackMemoryLeak"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n",(0,n.jsxs)(s.p,{children:["See ",(0,n.jsx)(s.a,{href:"Memory-Viewer",children:"Memory Viewer"})," for full feature details."]}),"\n",(0,n.jsxs)(s.p,{children:["完整功能详情见 ",(0,n.jsx)(s.a,{href:"Memory-Viewer",children:"Memory Viewer"}),"。"]}),"\n",(0,n.jsx)(s.h3,{id:i[13].id,children:i[13].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.9.0 / v1.9.0 起可用"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["The leak detector’s own ",(0,n.jsx)(s.code,{children:"WeakReference"})," state machine has a blind spot: an object whose ",(0,n.jsx)(s.code,{children:"dispose()"})," ran but which has not been GC’d yet still resolves through the weak reference and is flagged as a suspected leak (false positive). ",(0,n.jsx)(s.code,{children:"LeakTrackerBridge"})," subscribes to Flutter’s official ",(0,n.jsx)(s.code,{children:"FlutterMemoryAllocations"})," (the data source behind ",(0,n.jsx)(s.code,{children:"leak_tracker"}),") as a ",(0,n.jsx)(s.strong,{children:"second source"}),": once the official stream reports ",(0,n.jsx)(s.code,{children:"disposed"}),", the object is treated as released (awaiting GC) even if the weak reference still resolves — cutting false positives."]}),"\n",(0,n.jsxs)(s.p,{children:["自研泄漏检测的 ",(0,n.jsx)(s.code,{children:"WeakReference"})," 状态机有一个盲点:对象已调用 ",(0,n.jsx)(s.code,{children:"dispose()"})," 但尚未被 GC 时,弱引用仍存活,会被判定为”疑似泄漏”(误报)。",(0,n.jsx)(s.code,{children:"LeakTrackerBridge"})," 订阅 Flutter 官方的 ",(0,n.jsx)(s.code,{children:"FlutterMemoryAllocations"}),"(",(0,n.jsx)(s.code,{children:"leak_tracker"})," 背后的数据源)作为",(0,n.jsx)(s.strong,{children:"第二来源"}),":只要官方上报了 ",(0,n.jsx)(s.code,{children:"disposed"}),",即便弱引用仍存活也判定为已释放(等待 GC),从而显著降低误报。"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["Enabled by default via ",(0,n.jsx)(s.code,{children:"ZeroInspectorKit.init()"}),"’s ",(0,n.jsx)(s.code,{children:"enableFlutterLeakTracker"})," / 通过 ",(0,n.jsx)(s.code,{children:"init()"})," 的 ",(0,n.jsx)(s.code,{children:"enableFlutterLeakTracker"})," 默认启用"]}),"\n",(0,n.jsxs)(s.li,{children:["Official events only cover framework types that report to ",(0,n.jsx)(s.code,{children:"FlutterMemoryAllocations"})," (Image / Picture / Layer …) — it ",(0,n.jsx)(s.strong,{children:"supplements"}),", not replaces, the custom detector / 官方事件只覆盖会上报给 ",(0,n.jsx)(s.code,{children:"FlutterMemoryAllocations"})," 的框架类型(如 Image / Picture / Layer),因此是",(0,n.jsx)(s.strong,{children:"补充"}),"而非替代自研方案"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[14].id,children:i[14].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.9.0 / v1.9.0 起可用"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Singleton service for aggregated error capture, extends ",(0,n.jsx)(s.code,{children:"ChangeNotifier"}),". Hooks ",(0,n.jsx)(s.code,{children:"FlutterError.onError"})," (keeping the default red error behavior) and dedups each exception by type + stack signature. See ",(0,n.jsx)(s.a,{href:"Errors",children:"Errors"})," for full feature details."]}),"\n",(0,n.jsxs)(s.p,{children:["异常聚合单例服务,继承 ",(0,n.jsx)(s.code,{children:"ChangeNotifier"}),"。接管 ",(0,n.jsx)(s.code,{children:"FlutterError.onError"}),"(保留默认红色报错行为),按类型 + 堆栈签名去重聚合。完整功能见 ",(0,n.jsx)(s.a,{href:"Errors",children:"Errors"}),"。"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Report an exception manually (gRPC / custom protocols) / 手动上报异常"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ErrorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"report"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(error, stackTrace, "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'myModule'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Read aggregated records (newest first) / 读取聚合记录(最新在前)"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ErrorRecord"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" r "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ErrorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.errors.first;"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Toggle capture / 切换抓取开关"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ErrorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.isEnabled "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" false"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Clear all records / 清空全部记录"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ErrorService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"clear"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Member"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"errors"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"List"})," — aggregated records, newest first / 聚合记录,最新在前"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"errorCount"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"int"})," — aggregated record count / 聚合记录条数"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"isEnabled"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"bool"})," — capture toggle (programmatic) / 抓取开关(代码控制)"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"report(exception, stack, [context])"})}),(0,n.jsx)(s.td,{children:"Manually report an exception / 手动上报异常"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"restore(records)"})}),(0,n.jsx)(s.td,{children:"Restore persisted records (replay on launch) / 恢复持久化记录(启动回放)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"clear()"})}),(0,n.jsx)(s.td,{children:"Clear all aggregated records / 清空所有聚合记录"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"install()"})," / ",(0,n.jsx)(s.code,{children:"uninstall()"})]}),(0,n.jsxs)(s.td,{children:["Hook / restore ",(0,n.jsx)(s.code,{children:"FlutterError.onError"})," / 接管 / 还原 ",(0,n.jsx)(s.code,{children:"FlutterError.onError"})]})]})]})]}),"\n",(0,n.jsx)(s.h2,{id:i[15].id,children:i[15].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.9.0 / v1.9.0 起可用"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Singleton service that async-flushes logs, network requests, and aggregated errors to a local SQLite ",(0,n.jsx)(s.strong,{children:"ring buffer"})," (",(0,n.jsx)(s.code,{children:"zero_inspector_kit.db"}),") so data survives app restarts. On launch, ",(0,n.jsx)(s.strong,{children:"logs and aggregated errors replay into their tabs"}),"; network requests stay archived on disk for later export. Everything is guarded and degrades gracefully: if the DB is unavailable (e.g. desktop without sqflite FFI) ",(0,n.jsx)(s.code,{children:"isEnabled"})," is ",(0,n.jsx)(s.code,{children:"false"})," and writes become no-ops, never affecting the host app."]}),"\n",(0,n.jsxs)(s.p,{children:["单例服务,将日志、网络请求与聚合异常异步落盘到本地 SQLite ",(0,n.jsx)(s.strong,{children:"环形缓冲"}),"(",(0,n.jsx)(s.code,{children:"zero_inspector_kit.db"}),"),使数据跨重启不丢。启动时",(0,n.jsx)(s.strong,{children:"日志与聚合异常回放入各自标签页"}),";网络请求保留在磁盘存档,供之后导出。所有操作都有保护并优雅降级:数据库不可用(如桌面端未配置 sqflite FFI)时 ",(0,n.jsx)(s.code,{children:"isEnabled"})," 为 ",(0,n.jsx)(s.code,{children:"false"}),",写入变为空操作,绝不影响宿主应用。"]}),"\n",(0,n.jsx)(s.h3,{id:i[16].id,children:i[16].value}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"await"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" PersistenceService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"init"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" maxRowsPerTable"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 5000"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// rows kept per table / 每表保留行数"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" retention"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Duration"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(days"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 7"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"), "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// retention window / 保留时长"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" flushInterval"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Duration"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(seconds"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 2"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"), "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// disk flush interval / 刷盘间隔"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})})]})}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:["Enabled by default via ",(0,n.jsx)(s.code,{children:"ZeroInspectorKit.init()"}),"’s ",(0,n.jsx)(s.code,{children:"enablePersistence"})," — the ring buffer flushes automatically, so no manual ",(0,n.jsx)(s.code,{children:"init()"})," call is needed in normal use."]}),"\n",(0,n.jsxs)(s.p,{children:["通过 ",(0,n.jsx)(s.code,{children:"init()"})," 的 ",(0,n.jsx)(s.code,{children:"enablePersistence"})," 默认启用——环形缓冲会自动刷盘,常规使用无需手动调用 ",(0,n.jsx)(s.code,{children:"init()"}),"。"]}),"\n"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Member"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"isEnabled"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"bool"})," — whether the DB is available / 数据库是否可用"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"maxRowsPerTable"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"int"})," — row cap per table (ring-buffer trim line) / 每表行数上限"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"enqueueLog(e)"})," / ",(0,n.jsx)(s.code,{children:"enqueueNetwork(r)"})," / ",(0,n.jsx)(s.code,{children:"enqueueError(e)"})]}),(0,n.jsx)(s.td,{children:"Enqueue an item for async flush / 入队待异步落盘"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"flush()"})}),(0,n.jsx)(s.td,{children:"Flush the buffer to disk and trim / 刷盘并按环形缓冲裁剪"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"loadLogs()"})," / ",(0,n.jsx)(s.code,{children:"loadErrors()"})," / ",(0,n.jsx)(s.code,{children:"loadNetworkJson()"})]}),(0,n.jsx)(s.td,{children:"Load persisted data, newest first / 读取已持久化数据(最新在前)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"buildSessionArchiveJson()"})}),(0,n.jsx)(s.td,{children:"Build the full-session archive JSON (logs + errors + network) / 构建完整会话存档 JSON"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"exportSessionArchiveAndShare()"})}),(0,n.jsx)(s.td,{children:"Export & share the full-session archive via the system share sheet / 导出并分享完整会话存档"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"clearAll()"})}),(0,n.jsx)(s.td,{children:"Clear persisted data on disk / 清空磁盘上的持久化数据"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"dispose()"})}),(0,n.jsx)(s.td,{children:"Flush then close / 先刷盘再关闭"})]})]})]}),"\n",(0,n.jsx)(s.h3,{id:i[17].id,children:i[17].value}),"\n",(0,n.jsxs)(s.p,{children:["Tap the ",(0,n.jsx)(s.strong,{children:"storage icon"})," in the panel header to open the ",(0,n.jsx)(s.strong,{children:"Persisted data"})," sheet: it shows the current row counts per category (",(0,n.jsx)(s.code,{children:"logs / network / errors"}),"), the per-table cap, and whether the data will be replayed on the next launch. Actions:"]}),"\n",(0,n.jsxs)(s.p,{children:["点击面板头部的",(0,n.jsx)(s.strong,{children:"存储图标"}),"打开 ",(0,n.jsx)(s.strong,{children:"Persisted data"})," 管理弹层:展示各类别当前行数(",(0,n.jsx)(s.code,{children:"logs / network / errors"}),")、每表上限,以及下次启动是否会回放。支持的操作:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Export & share session archive"})," — share a JSON snapshot of the whole session / ",(0,n.jsx)(s.strong,{children:"导出并分享会话存档"}),"——分享本次会话完整 JSON 快照"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Clear disk"})," — wipe persisted rows so the next launch replays nothing / ",(0,n.jsx)(s.strong,{children:"清空磁盘"}),"——清空持久化数据,下次启动干净"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Clear disk & lists"})," — wipe disk and the in-memory lists together / ",(0,n.jsx)(s.strong,{children:"同时清空磁盘与列表"}),"——连同内存列表一并清空"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[18].id,children:i[18].value}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Available since v1.2.0 / v1.2.0 起可用"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Singleton service for FPS monitoring, extends ",(0,n.jsx)(s.code,{children:"ChangeNotifier"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["FPS 监控单例服务,继承 ",(0,n.jsx)(s.code,{children:"ChangeNotifier"}),"。"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"start"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"stop"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"clear"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" fps "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.currentFps;"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" jankRate "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.jankRate;"})]})]})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Method"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"start()"})}),(0,n.jsx)(s.td,{children:"Start FPS monitoring / 开始 FPS 监控"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"stop()"})}),(0,n.jsx)(s.td,{children:"Stop FPS monitoring / 停止 FPS 监控"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"clear()"})}),(0,n.jsx)(s.td,{children:"Clear all historical data and counters / 清空所有历史数据和计数器"})]})]})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Property"}),(0,n.jsx)(s.th,{children:"Type"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"isRunning"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:"Whether monitoring is currently active / 是否正在监控"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"currentFps"})}),(0,n.jsx)(s.td,{children:"double"}),(0,n.jsx)(s.td,{children:"Current FPS (updated every 500ms) / 当前 FPS(每 500ms 更新)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"jankRate"})}),(0,n.jsx)(s.td,{children:"double"}),(0,n.jsx)(s.td,{children:"Jank rate as percentage / 卡顿率(百分比)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"totalFrameCount"})}),(0,n.jsx)(s.td,{children:"int"}),(0,n.jsx)(s.td,{children:"Total frames captured / 总帧数"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"totalJankyCount"})}),(0,n.jsx)(s.td,{children:"int"}),(0,n.jsx)(s.td,{children:"Total janky frames (>16ms) / 总卡顿帧数(>16ms)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"lastFrameJanky"})}),(0,n.jsx)(s.td,{children:"bool"}),(0,n.jsx)(s.td,{children:"Whether the most recent frame was janky / 最近一帧是否卡顿"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"fpsHistory"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"List"})}),(0,n.jsx)(s.td,{children:"Recent 60 FPS values (unmodifiable) / 最近 60 个 FPS 值(不可变)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"frameRecords"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"List"})}),(0,n.jsx)(s.td,{children:"Recent frame records (unmodifiable, up to 3600) / 最近帧记录(不可变,最多 3600 条)"})]})]})]}),"\n",(0,n.jsxs)(s.p,{children:["See ",(0,n.jsx)(s.a,{href:"FPS-Viewer",children:"FPS Viewer"})," for full feature details."]}),"\n",(0,n.jsxs)(s.p,{children:["完整功能详情见 ",(0,n.jsx)(s.a,{href:"FPS-Viewer",children:"FPS Viewer"}),"。"]})]})},"/Configuration",{filePath:"pages/Configuration.md",timestamp:1786656379e3,pageMap:d.v,frontMatter:{},title:"Configuration / 配置说明"},"undefined"==typeof RemoteContent?h:RemoteContent.useTOC)},7080:function(e,i,s){"use strict";s.d(i,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=9321)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Custom-Database-Provider-838fb9c0a6151981.js b/docs/_next/static/chunks/pages/Custom-Database-Provider-bbe4c3db5859578c.js similarity index 92% rename from docs/_next/static/chunks/pages/Custom-Database-Provider-838fb9c0a6151981.js rename to docs/_next/static/chunks/pages/Custom-Database-Provider-bbe4c3db5859578c.js index a82e74c..197f8d9 100644 --- a/docs/_next/static/chunks/pages/Custom-Database-Provider-838fb9c0a6151981.js +++ b/docs/_next/static/chunks/pages/Custom-Database-Provider-bbe4c3db5859578c.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[824],{1346:function(i,s,e){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Custom-Database-Provider",function(){return e(3471)}])},3471:function(i,s,e){"use strict";e.r(s),e.d(s,{useTOC:function(){return a}});var h=e(5893),n=e(7812),r=e(7080),t=e(8925);function a(i){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"DatabaseProvider Interface / 接口定义",id:"databaseprovider-interface--接口定义",depth:2},{value:"Data Models / 数据模型",id:"data-models--数据模型",depth:2},{value:"DatabaseInfo",id:"databaseinfo",depth:3},{value:"TableInfo",id:"tableinfo",depth:3},{value:"QueryResult",id:"queryresult",depth:3},{value:"Example: Custom Provider / 示例:自定义提供者",id:"example-custom-provider--示例自定义提供者",depth:2},{value:"Register Provider / 注册提供者",id:"register-provider--注册提供者",depth:2},{value:"Built-in Provider / 内置提供者",id:"built-in-provider--内置提供者",depth:2}]}s.default=(0,n.c)(function(i){let{toc:s=a(i)}=i,e={code:"code",h1:"h1",h2:"h2",h3:"h3",p:"p",pre:"pre",span:"span",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",...(0,t.a)(),...i.components};return(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(e.h1,{children:"Custom Database Provider / 自定义数据库提供者"}),"\n",(0,h.jsx)(e.h2,{id:s[0].id,children:s[0].value}),"\n",(0,h.jsxs)(e.p,{children:["Zero Inspector Kit supports extending database inspection beyond SQLite through the ",(0,h.jsx)(e.code,{children:"DatabaseProvider"})," interface."]}),"\n",(0,h.jsxs)(e.p,{children:["Zero Inspector Kit 通过 ",(0,h.jsx)(e.code,{children:"DatabaseProvider"})," 接口支持扩展数据库检查功能到 SQLite 以外的数据库。"]}),"\n",(0,h.jsx)(e.h2,{id:s[1].id,children:s[1].value}),"\n",(0,h.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,h.jsxs)(e.code,{children:[(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"abstract"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" class"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" DatabaseProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" /// Provider name / 提供者名称"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" String"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" get"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" name;"})]}),"\n",(0,h.jsx)(e.span,{children:" "}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" /// Get list of databases / 获取数据库列表"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Future"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"<"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"List"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"<"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"DatabaseInfo"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:">> "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"getDatabases"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,h.jsx)(e.span,{children:" "}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" /// Query table data / 查询表数据"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Future"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"<"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"QueryResult"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"> "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"queryTable"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"String"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" dbPath, "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"String"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" tableName, {"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"int"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" limit "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 50"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"});"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})})]})}),"\n",(0,h.jsx)(e.h2,{id:s[2].id,children:s[2].value}),"\n",(0,h.jsx)(e.h3,{id:s[3].id,children:s[3].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,h.jsxs)(e.table,{children:[(0,h.jsx)(e.thead,{children:(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.th,{children:"Field"}),(0,h.jsx)(e.th,{children:"Type"}),(0,h.jsx)(e.th,{children:"Description"})]})}),(0,h.jsxs)(e.tbody,{children:[(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"name"})}),(0,h.jsx)(e.td,{children:"String"}),(0,h.jsx)(e.td,{children:"Database name / 数据库名称"})]}),(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"path"})}),(0,h.jsx)(e.td,{children:"String"}),(0,h.jsx)(e.td,{children:"Database file path / 数据库文件路径"})]}),(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"tables"})}),(0,h.jsx)(e.td,{children:"List"}),(0,h.jsx)(e.td,{children:"List of tables / 表列表"})]})]})]}),"\n",(0,h.jsx)(e.h3,{id:s[4].id,children:s[4].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,h.jsxs)(e.table,{children:[(0,h.jsx)(e.thead,{children:(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.th,{children:"Field"}),(0,h.jsx)(e.th,{children:"Type"}),(0,h.jsx)(e.th,{children:"Description"})]})}),(0,h.jsxs)(e.tbody,{children:[(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"name"})}),(0,h.jsx)(e.td,{children:"String"}),(0,h.jsx)(e.td,{children:"Table name / 表名"})]}),(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"rowCount"})}),(0,h.jsx)(e.td,{children:"int"}),(0,h.jsx)(e.td,{children:"Row count / 行数"})]})]})]}),"\n",(0,h.jsx)(e.h3,{id:s[5].id,children:s[5].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,h.jsxs)(e.table,{children:[(0,h.jsx)(e.thead,{children:(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.th,{children:"Field"}),(0,h.jsx)(e.th,{children:"Type"}),(0,h.jsx)(e.th,{children:"Description"})]})}),(0,h.jsxs)(e.tbody,{children:[(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"columns"})}),(0,h.jsx)(e.td,{children:"List"}),(0,h.jsx)(e.td,{children:"Column names / 列名"})]}),(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"rows"})}),(0,h.jsx)(e.td,{children:"List>"}),(0,h.jsx)(e.td,{children:"Row data / 行数据"})]})]})]}),"\n",(0,h.jsx)(e.h2,{id:s[6].id,children:s[6].value}),"\n",(0,h.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,h.jsxs)(e.code,{children:[(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"class"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyCustomDatabaseProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" implements"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" DatabaseProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" @override"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" String"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" get"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" name "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"=>"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'CustomDB'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]}),"\n",(0,h.jsx)(e.span,{children:" "}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" @override"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Future"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"<"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"List"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"<"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"DatabaseInfo"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:">> "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"getDatabases"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"() "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"async"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" // Return your custom databases / 返回你的自定义数据库列表"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" return"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ["})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" DatabaseInfo"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" name"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'my_database'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" path"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" '/path/to/my_database'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" tables"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ["})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" TableInfo"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(name"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'users'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", rowCount"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 100"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"),"})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" TableInfo"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(name"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'orders'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", rowCount"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 500"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"),"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ],"})}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ),"})}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ];"})}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" }"})}),"\n",(0,h.jsx)(e.span,{children:" "}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" @override"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Future"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"<"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"QueryResult"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"> "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"queryTable"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" String"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" dbPath,"})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" String"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" tableName, {"})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" int"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" limit "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 50"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" }) "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"async"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" // Execute query and return results / 执行查询并返回结果"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" return"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" QueryResult"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" columns"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ["}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'id'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'name'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'value'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"],"})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" rows"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ["})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'id'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 1"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'name'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'item1'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'value'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" '100'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"},"})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'id'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 2"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'name'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'item2'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'value'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" '200'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"},"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ],"})}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" );"})}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" }"})}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})})]})}),"\n",(0,h.jsx)(e.h2,{id:s[7].id,children:s[7].value}),"\n",(0,h.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,h.jsxs)(e.code,{children:[(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Register your custom provider / 注册自定义提供者"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"DatabaseRegistry"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"registerProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MyCustomDatabaseProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"());"})]})]})}),"\n",(0,h.jsx)(e.h2,{id:s[8].id,children:s[8].value}),"\n",(0,h.jsxs)(e.p,{children:["The plugin includes ",(0,h.jsx)(e.code,{children:"SqliteDatabaseProvider"})," which is auto-registered when ",(0,h.jsx)(e.code,{children:"enableDatabaseScan"})," is ",(0,h.jsx)(e.code,{children:"true"})," (default)."]}),"\n",(0,h.jsxs)(e.p,{children:["插件内置 ",(0,h.jsx)(e.code,{children:"SqliteDatabaseProvider"}),",当 ",(0,h.jsx)(e.code,{children:"enableDatabaseScan"})," 为 ",(0,h.jsx)(e.code,{children:"true"}),"(默认)时自动注册。"]}),"\n",(0,h.jsx)(e.p,{children:"You can also register it manually:"}),"\n",(0,h.jsx)(e.p,{children:"也可以手动注册:"}),"\n",(0,h.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,h.jsx)(e.code,{children:(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"DatabaseRegistry"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"registerProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"SqliteDatabaseProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"());"})]})})})]})},"/Custom-Database-Provider",{filePath:"pages/Custom-Database-Provider.md",timestamp:1786656379e3,pageMap:r.v,frontMatter:{},title:"Custom Database Provider / 自定义数据库提供者"},"undefined"==typeof RemoteContent?a:RemoteContent.useTOC)},7080:function(i,s,e){"use strict";e.d(s,{v:function(){return h}});let h=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(i){i.O(0,[812,888,774,179],function(){return i(i.s=1346)}),_N_E=i.O()}]); \ No newline at end of file +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[824],{1346:function(i,s,e){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Custom-Database-Provider",function(){return e(3471)}])},3471:function(i,s,e){"use strict";e.r(s),e.d(s,{useTOC:function(){return l}});var h=e(5893),n=e(7812),r=e(7080),t=e(8925);function l(i){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"DatabaseProvider Interface / 接口定义",id:"databaseprovider-interface--接口定义",depth:2},{value:"Data Models / 数据模型",id:"data-models--数据模型",depth:2},{value:"DatabaseInfo",id:"databaseinfo",depth:3},{value:"TableInfo",id:"tableinfo",depth:3},{value:"QueryResult",id:"queryresult",depth:3},{value:"Example: Custom Provider / 示例:自定义提供者",id:"example-custom-provider--示例自定义提供者",depth:2},{value:"Register Provider / 注册提供者",id:"register-provider--注册提供者",depth:2},{value:"Built-in Provider / 内置提供者",id:"built-in-provider--内置提供者",depth:2}]}s.default=(0,n.c)(function(i){let{toc:s=l(i)}=i,e={code:"code",h1:"h1",h2:"h2",h3:"h3",p:"p",pre:"pre",span:"span",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",...(0,t.a)(),...i.components};return(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(e.h1,{children:"Custom Database Provider / 自定义数据库提供者"}),"\n",(0,h.jsx)(e.h2,{id:s[0].id,children:s[0].value}),"\n",(0,h.jsxs)(e.p,{children:["Zero Inspector Kit supports extending database inspection beyond SQLite through the ",(0,h.jsx)(e.code,{children:"DatabaseProvider"})," interface."]}),"\n",(0,h.jsxs)(e.p,{children:["Zero Inspector Kit 通过 ",(0,h.jsx)(e.code,{children:"DatabaseProvider"})," 接口支持扩展数据库检查功能到 SQLite 以外的数据库。"]}),"\n",(0,h.jsx)(e.h2,{id:s[1].id,children:s[1].value}),"\n",(0,h.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,h.jsxs)(e.code,{children:[(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"abstract"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" class"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" DatabaseProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" /// Provider name / 提供者名称"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" String"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" get"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" name;"})]}),"\n",(0,h.jsx)(e.span,{children:" "}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" /// Get list of databases / 获取数据库列表"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Future"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"<"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"List"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"<"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"DatabaseInfo"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:">> "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"getDatabases"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,h.jsx)(e.span,{children:" "}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" /// Query table data / 查询表数据"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Future"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"<"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"QueryResult"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"> "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"queryTable"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"String"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" dbPath, "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"String"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" tableName, {"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"int"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" limit "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 50"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"});"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})})]})}),"\n",(0,h.jsx)(e.h2,{id:s[2].id,children:s[2].value}),"\n",(0,h.jsx)(e.h3,{id:s[3].id,children:s[3].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,h.jsxs)(e.table,{children:[(0,h.jsx)(e.thead,{children:(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.th,{children:"Field"}),(0,h.jsx)(e.th,{children:"Type"}),(0,h.jsx)(e.th,{children:"Description"})]})}),(0,h.jsxs)(e.tbody,{children:[(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"name"})}),(0,h.jsx)(e.td,{children:"String"}),(0,h.jsx)(e.td,{children:"Database name / 数据库名称"})]}),(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"path"})}),(0,h.jsx)(e.td,{children:"String"}),(0,h.jsx)(e.td,{children:"Database file path / 数据库文件路径"})]}),(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"tables"})}),(0,h.jsx)(e.td,{children:"List"}),(0,h.jsx)(e.td,{children:"List of tables / 表列表"})]})]})]}),"\n",(0,h.jsx)(e.h3,{id:s[4].id,children:s[4].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,h.jsxs)(e.table,{children:[(0,h.jsx)(e.thead,{children:(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.th,{children:"Field"}),(0,h.jsx)(e.th,{children:"Type"}),(0,h.jsx)(e.th,{children:"Description"})]})}),(0,h.jsxs)(e.tbody,{children:[(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"name"})}),(0,h.jsx)(e.td,{children:"String"}),(0,h.jsx)(e.td,{children:"Table name / 表名"})]}),(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"rowCount"})}),(0,h.jsx)(e.td,{children:"int"}),(0,h.jsx)(e.td,{children:"Row count / 行数"})]})]})]}),"\n",(0,h.jsx)(e.h3,{id:s[5].id,children:s[5].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,h.jsxs)(e.table,{children:[(0,h.jsx)(e.thead,{children:(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.th,{children:"Field"}),(0,h.jsx)(e.th,{children:"Type"}),(0,h.jsx)(e.th,{children:"Description"})]})}),(0,h.jsxs)(e.tbody,{children:[(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"columns"})}),(0,h.jsx)(e.td,{children:"List"}),(0,h.jsx)(e.td,{children:"Column names / 列名"})]}),(0,h.jsxs)(e.tr,{children:[(0,h.jsx)(e.td,{children:(0,h.jsx)(e.code,{children:"rows"})}),(0,h.jsx)(e.td,{children:"List>"}),(0,h.jsx)(e.td,{children:"Row data / 行数据"})]})]})]}),"\n",(0,h.jsx)(e.h2,{id:s[6].id,children:s[6].value}),"\n",(0,h.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,h.jsxs)(e.code,{children:[(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"class"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyCustomDatabaseProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" implements"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" DatabaseProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" @override"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" String"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" get"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" name "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"=>"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'CustomDB'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]}),"\n",(0,h.jsx)(e.span,{children:" "}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" @override"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Future"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"<"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"List"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"<"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"DatabaseInfo"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:">> "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"getDatabases"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"() "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"async"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" // Return your custom databases / 返回你的自定义数据库列表"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" return"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ["})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" DatabaseInfo"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" name"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'my_database'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" path"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" '/path/to/my_database'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" tables"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ["})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" TableInfo"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(name"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'users'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", rowCount"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 100"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"),"})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" TableInfo"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(name"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'orders'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", rowCount"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 500"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"),"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ],"})}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ),"})}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ];"})}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" }"})}),"\n",(0,h.jsx)(e.span,{children:" "}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" @override"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Future"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"<"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"QueryResult"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"> "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"queryTable"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" String"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" dbPath,"})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" String"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" tableName, {"})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" int"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" limit "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 50"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" }) "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"async"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" // Execute query and return results / 执行查询并返回结果"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" return"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" QueryResult"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" columns"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ["}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'id'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'name'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'value'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"],"})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" rows"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ["})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'id'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 1"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'name'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'item1'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'value'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" '100'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"},"})]}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'id'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 2"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'name'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'item2'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'value'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" '200'"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"},"})]}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ],"})}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" );"})}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" }"})}),"\n",(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})})]})}),"\n",(0,h.jsx)(e.h2,{id:s[7].id,children:s[7].value}),"\n",(0,h.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,h.jsxs)(e.code,{children:[(0,h.jsx)(e.span,{children:(0,h.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Register your custom provider / 注册自定义提供者"})}),"\n",(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"DatabaseRegistry"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"registerProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MyCustomDatabaseProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"());"})]})]})}),"\n",(0,h.jsx)(e.h2,{id:s[8].id,children:s[8].value}),"\n",(0,h.jsxs)(e.p,{children:["The plugin includes ",(0,h.jsx)(e.code,{children:"SqliteDatabaseProvider"})," which is auto-registered when ",(0,h.jsx)(e.code,{children:"enableDatabaseScan"})," is ",(0,h.jsx)(e.code,{children:"true"})," (default)."]}),"\n",(0,h.jsxs)(e.p,{children:["插件内置 ",(0,h.jsx)(e.code,{children:"SqliteDatabaseProvider"}),",当 ",(0,h.jsx)(e.code,{children:"enableDatabaseScan"})," 为 ",(0,h.jsx)(e.code,{children:"true"}),"(默认)时自动注册。"]}),"\n",(0,h.jsx)(e.p,{children:"You can also register it manually:"}),"\n",(0,h.jsx)(e.p,{children:"也可以手动注册:"}),"\n",(0,h.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,h.jsx)(e.code,{children:(0,h.jsxs)(e.span,{children:[(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"DatabaseRegistry"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"registerProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"SqliteDatabaseProvider"}),(0,h.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"());"})]})})})]})},"/Custom-Database-Provider",{filePath:"pages/Custom-Database-Provider.md",timestamp:1786656379e3,pageMap:r.v,frontMatter:{},title:"Custom Database Provider / 自定义数据库提供者"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(i,s,e){"use strict";e.d(s,{v:function(){return h}});let h=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(i){i.O(0,[812,888,774,179],function(){return i(i.s=1346)}),_N_E=i.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Database-Viewer-5b3fb1bd8161d7e8.js b/docs/_next/static/chunks/pages/Database-Viewer-bae8bb7c99f3f2a2.js similarity index 77% rename from docs/_next/static/chunks/pages/Database-Viewer-5b3fb1bd8161d7e8.js rename to docs/_next/static/chunks/pages/Database-Viewer-bae8bb7c99f3f2a2.js index f2d4044..e316ad4 100644 --- a/docs/_next/static/chunks/pages/Database-Viewer-5b3fb1bd8161d7e8.js +++ b/docs/_next/static/chunks/pages/Database-Viewer-bae8bb7c99f3f2a2.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[395],{4452:function(e,t,a){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Database-Viewer",function(){return a(4208)}])},4208:function(e,t,a){"use strict";a.r(t),a.d(t,{useTOC:function(){return l}});var r=a(5893),n=a(7812),i=a(7080),s=a(8925);function l(e){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"Auto-Scan / 自动扫描",id:"auto-scan--自动扫描",depth:2},{value:"Two-Level Navigation / 双层导航",id:"two-level-navigation--双层导航",depth:2},{value:"Level 1: Database List (Global) / 第一层:数据库列表(全局)",id:"level-1-database-list-global--第一层数据库列表全局",depth:3},{value:"Level 2: Database Detail / 第二层:数据库详情",id:"level-2-database-detail--第二层数据库详情",depth:3},{value:"Search / 搜索",id:"search--搜索",depth:2},{value:"Search Highlights / 搜索高亮",id:"search-highlights--搜索高亮",depth:3},{value:"UI Features / UI 功能",id:"ui-features--ui-功能",depth:2},{value:"Custom Database Provider / 自定义数据库提供者",id:"custom-database-provider--自定义数据库提供者",depth:2}]}t.default=(0,n.c)(function(e){let{toc:t=l(e)}=e,a={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,s.a)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(a.h1,{children:"Database Viewer / 数据库查看器"}),"\n",(0,r.jsx)(a.h2,{id:t[0].id,children:t[0].value}),"\n",(0,r.jsx)(a.p,{children:"The Database Viewer inspects SQLite databases in your app with a two-level navigation system."}),"\n",(0,r.jsx)(a.p,{children:"数据库查看器通过双层导航系统检查应用中的 SQLite 数据库。"}),"\n",(0,r.jsx)(a.h2,{id:t[1].id,children:t[1].value}),"\n",(0,r.jsxs)(a.p,{children:["The inspector automatically scans the following directories for ",(0,r.jsx)(a.code,{children:".db"})," and ",(0,r.jsx)(a.code,{children:".sqlite"})," files:"]}),"\n",(0,r.jsxs)(a.p,{children:["检查器自动扫描以下目录中的 ",(0,r.jsx)(a.code,{children:".db"})," 和 ",(0,r.jsx)(a.code,{children:".sqlite"})," 文件:"]}),"\n",(0,r.jsxs)(a.ul,{children:["\n",(0,r.jsxs)(a.li,{children:[(0,r.jsx)(a.code,{children:"getApplicationDocumentsDirectory()"})," / 应用文档目录"]}),"\n",(0,r.jsxs)(a.li,{children:[(0,r.jsx)(a.code,{children:"getDatabasesPath()"})," / 数据库目录"]}),"\n"]}),"\n",(0,r.jsx)(a.h2,{id:t[2].id,children:t[2].value}),"\n",(0,r.jsx)(a.h3,{id:t[3].id,children:t[3].value}),"\n",(0,r.jsxs)(a.ul,{children:["\n",(0,r.jsx)(a.li,{children:"Shows all discovered databases / 显示所有发现的数据库"}),"\n",(0,r.jsx)(a.li,{children:"Each database shows name and table count / 每个数据库显示名称和表数量"}),"\n",(0,r.jsxs)(a.li,{children:[(0,r.jsx)(a.strong,{children:"Global search"}),": Search database names and table names / ",(0,r.jsx)(a.strong,{children:"全局搜索"}),":搜索数据库名和表名"]}),"\n"]}),"\n",(0,r.jsx)(a.h3,{id:t[4].id,children:t[4].value}),"\n",(0,r.jsxs)(a.ul,{children:["\n",(0,r.jsx)(a.li,{children:"Click a database to enter its detail view / 点击数据库进入详情视图"}),"\n",(0,r.jsx)(a.li,{children:"Left panel: table list with row counts / 左侧面板:表列表(含行数)"}),"\n",(0,r.jsx)(a.li,{children:"Right panel: table data in DataTable format / 右侧面板:DataTable 格式的表数据"}),"\n",(0,r.jsx)(a.li,{children:"Back button to return to database list / 返回按钮返回数据库列表"}),"\n",(0,r.jsxs)(a.li,{children:[(0,r.jsx)(a.strong,{children:"In-database search"}),": Search table names AND all column data / ",(0,r.jsx)(a.strong,{children:"数据库内搜索"}),":搜索表名和所有列数据"]}),"\n"]}),"\n",(0,r.jsx)(a.h2,{id:t[5].id,children:t[5].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(a.table,{children:[(0,r.jsx)(a.thead,{children:(0,r.jsxs)(a.tr,{children:[(0,r.jsx)(a.th,{children:"Level"}),(0,r.jsx)(a.th,{children:"Search Scope"})]})}),(0,r.jsxs)(a.tbody,{children:[(0,r.jsxs)(a.tr,{children:[(0,r.jsx)(a.td,{children:"Global (database list)"}),(0,r.jsx)(a.td,{children:"Database names, table names / 数据库名、表名"})]}),(0,r.jsxs)(a.tr,{children:[(0,r.jsx)(a.td,{children:"In-database"}),(0,r.jsx)(a.td,{children:"Table names, all column values in all tables / 表名、所有表的所有列值"})]})]})]}),"\n",(0,r.jsx)(a.h3,{id:t[6].id,children:t[6].value}),"\n",(0,r.jsx)(a.p,{children:"When searching within a database, matching cell values are highlighted in accent color."}),"\n",(0,r.jsx)(a.p,{children:"在数据库内搜索时,匹配的单元格值以强调色高亮显示。"}),"\n",(0,r.jsx)(a.h2,{id:t[7].id,children:t[7].value}),"\n",(0,r.jsxs)(a.ul,{children:["\n",(0,r.jsx)(a.li,{children:"Color-coded table icons / 带颜色的表图标"}),"\n",(0,r.jsx)(a.li,{children:"Row count badges for each table / 每个表的行数徽章"}),"\n",(0,r.jsx)(a.li,{children:"Selected table highlighted / 选中表高亮"}),"\n",(0,r.jsx)(a.li,{children:"Horizontal and vertical scrollable data table / 水平和垂直可滚动的数据表"}),"\n",(0,r.jsx)(a.li,{children:"Row count display (filtered / total) when searching / 搜索时显示行数(过滤/总数)"}),"\n"]}),"\n",(0,r.jsx)(a.h2,{id:t[8].id,children:t[8].value}),"\n",(0,r.jsxs)(a.p,{children:["For non-SQLite databases, see ",(0,r.jsx)(a.a,{href:"Custom-Database-Provider",children:"Custom Database Provider"}),"."]}),"\n",(0,r.jsxs)(a.p,{children:["对于非 SQLite 数据库,请参阅 ",(0,r.jsx)(a.a,{href:"Custom-Database-Provider",children:"自定义数据库提供者"}),"。"]})]})},"/Database-Viewer",{filePath:"pages/Database-Viewer.md",timestamp:1786656379e3,pageMap:i.v,frontMatter:{},title:"Database Viewer / 数据库查看器"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,t,a){"use strict";a.d(t,{v:function(){return r}});let r=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=4452)}),_N_E=e.O()}]); \ No newline at end of file +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[395],{4452:function(e,t,a){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Database-Viewer",function(){return a(4208)}])},4208:function(e,t,a){"use strict";a.r(t),a.d(t,{useTOC:function(){return l}});var r=a(5893),n=a(7812),i=a(7080),s=a(8925);function l(e){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"Auto-Scan / 自动扫描",id:"auto-scan--自动扫描",depth:2},{value:"Two-Level Navigation / 双层导航",id:"two-level-navigation--双层导航",depth:2},{value:"Level 1: Database List (Global) / 第一层:数据库列表(全局)",id:"level-1-database-list-global--第一层数据库列表全局",depth:3},{value:"Level 2: Database Detail / 第二层:数据库详情",id:"level-2-database-detail--第二层数据库详情",depth:3},{value:"Search / 搜索",id:"search--搜索",depth:2},{value:"Search Highlights / 搜索高亮",id:"search-highlights--搜索高亮",depth:3},{value:"UI Features / UI 功能",id:"ui-features--ui-功能",depth:2},{value:"Custom Database Provider / 自定义数据库提供者",id:"custom-database-provider--自定义数据库提供者",depth:2}]}t.default=(0,n.c)(function(e){let{toc:t=l(e)}=e,a={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,s.a)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(a.h1,{children:"Database Viewer / 数据库查看器"}),"\n",(0,r.jsx)(a.h2,{id:t[0].id,children:t[0].value}),"\n",(0,r.jsx)(a.p,{children:"The Database Viewer inspects SQLite databases in your app with a two-level navigation system."}),"\n",(0,r.jsx)(a.p,{children:"数据库查看器通过双层导航系统检查应用中的 SQLite 数据库。"}),"\n",(0,r.jsx)(a.h2,{id:t[1].id,children:t[1].value}),"\n",(0,r.jsxs)(a.p,{children:["The inspector automatically scans the following directories for ",(0,r.jsx)(a.code,{children:".db"})," and ",(0,r.jsx)(a.code,{children:".sqlite"})," files:"]}),"\n",(0,r.jsxs)(a.p,{children:["检查器自动扫描以下目录中的 ",(0,r.jsx)(a.code,{children:".db"})," 和 ",(0,r.jsx)(a.code,{children:".sqlite"})," 文件:"]}),"\n",(0,r.jsxs)(a.ul,{children:["\n",(0,r.jsxs)(a.li,{children:[(0,r.jsx)(a.code,{children:"getApplicationDocumentsDirectory()"})," / 应用文档目录"]}),"\n",(0,r.jsxs)(a.li,{children:[(0,r.jsx)(a.code,{children:"getDatabasesPath()"})," / 数据库目录"]}),"\n"]}),"\n",(0,r.jsx)(a.h2,{id:t[2].id,children:t[2].value}),"\n",(0,r.jsx)(a.h3,{id:t[3].id,children:t[3].value}),"\n",(0,r.jsxs)(a.ul,{children:["\n",(0,r.jsx)(a.li,{children:"Shows all discovered databases / 显示所有发现的数据库"}),"\n",(0,r.jsx)(a.li,{children:"Each database shows name and table count / 每个数据库显示名称和表数量"}),"\n",(0,r.jsxs)(a.li,{children:[(0,r.jsx)(a.strong,{children:"Global search"}),": Search database names and table names / ",(0,r.jsx)(a.strong,{children:"全局搜索"}),":搜索数据库名和表名"]}),"\n"]}),"\n",(0,r.jsx)(a.h3,{id:t[4].id,children:t[4].value}),"\n",(0,r.jsxs)(a.ul,{children:["\n",(0,r.jsx)(a.li,{children:"Click a database to enter its detail view / 点击数据库进入详情视图"}),"\n",(0,r.jsx)(a.li,{children:"Left panel: table list with row counts / 左侧面板:表列表(含行数)"}),"\n",(0,r.jsx)(a.li,{children:"Right panel: table data in DataTable format / 右侧面板:DataTable 格式的表数据"}),"\n",(0,r.jsx)(a.li,{children:"Back button to return to database list / 返回按钮返回数据库列表"}),"\n",(0,r.jsxs)(a.li,{children:[(0,r.jsx)(a.strong,{children:"In-database search"}),": Search table names AND all column data / ",(0,r.jsx)(a.strong,{children:"数据库内搜索"}),":搜索表名和所有列数据"]}),"\n"]}),"\n",(0,r.jsx)(a.h2,{id:t[5].id,children:t[5].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(a.table,{children:[(0,r.jsx)(a.thead,{children:(0,r.jsxs)(a.tr,{children:[(0,r.jsx)(a.th,{children:"Level"}),(0,r.jsx)(a.th,{children:"Search Scope"})]})}),(0,r.jsxs)(a.tbody,{children:[(0,r.jsxs)(a.tr,{children:[(0,r.jsx)(a.td,{children:"Global (database list)"}),(0,r.jsx)(a.td,{children:"Database names, table names / 数据库名、表名"})]}),(0,r.jsxs)(a.tr,{children:[(0,r.jsx)(a.td,{children:"In-database"}),(0,r.jsx)(a.td,{children:"Table names, all column values in all tables / 表名、所有表的所有列值"})]})]})]}),"\n",(0,r.jsx)(a.h3,{id:t[6].id,children:t[6].value}),"\n",(0,r.jsx)(a.p,{children:"When searching within a database, matching cell values are highlighted in accent color."}),"\n",(0,r.jsx)(a.p,{children:"在数据库内搜索时,匹配的单元格值以强调色高亮显示。"}),"\n",(0,r.jsx)(a.h2,{id:t[7].id,children:t[7].value}),"\n",(0,r.jsxs)(a.ul,{children:["\n",(0,r.jsx)(a.li,{children:"Color-coded table icons / 带颜色的表图标"}),"\n",(0,r.jsx)(a.li,{children:"Row count badges for each table / 每个表的行数徽章"}),"\n",(0,r.jsx)(a.li,{children:"Selected table highlighted / 选中表高亮"}),"\n",(0,r.jsx)(a.li,{children:"Horizontal and vertical scrollable data table / 水平和垂直可滚动的数据表"}),"\n",(0,r.jsx)(a.li,{children:"Row count display (filtered / total) when searching / 搜索时显示行数(过滤/总数)"}),"\n"]}),"\n",(0,r.jsx)(a.h2,{id:t[8].id,children:t[8].value}),"\n",(0,r.jsxs)(a.p,{children:["For non-SQLite databases, see ",(0,r.jsx)(a.a,{href:"Custom-Database-Provider",children:"Custom Database Provider"}),"."]}),"\n",(0,r.jsxs)(a.p,{children:["对于非 SQLite 数据库,请参阅 ",(0,r.jsx)(a.a,{href:"Custom-Database-Provider",children:"自定义数据库提供者"}),"。"]})]})},"/Database-Viewer",{filePath:"pages/Database-Viewer.md",timestamp:1786656379e3,pageMap:i.v,frontMatter:{},title:"Database Viewer / 数据库查看器"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,t,a){"use strict";a.d(t,{v:function(){return r}});let r=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=4452)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Errors-7cf1ee698701533d.js b/docs/_next/static/chunks/pages/Errors-3214b059d0e33517.js similarity index 93% rename from docs/_next/static/chunks/pages/Errors-7cf1ee698701533d.js rename to docs/_next/static/chunks/pages/Errors-3214b059d0e33517.js index 6b94f99..cadbcf9 100644 --- a/docs/_next/static/chunks/pages/Errors-7cf1ee698701533d.js +++ b/docs/_next/static/chunks/pages/Errors-3214b059d0e33517.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[302],{7031:function(e,r,s){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Errors",function(){return s(1824)}])},1824:function(e,r,s){"use strict";s.r(r),s.d(r,{useTOC:function(){return l}});var i=s(5893),n=s(7812),t=s(7080),d=s(8925);function l(e){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"What Gets Captured / 捕获来源",id:"what-gets-captured--捕获来源",depth:2},{value:"Deduplication / 去重规则",id:"deduplication--去重规则",depth:2},{value:"UI Features / UI 功能",id:"ui-features--ui-功能",depth:2},{value:"Errors Tab / Errors 标签页",id:"errors-tab--errors-标签页",depth:3},{value:"Tab Badge / 标签页红点",id:"tab-badge--标签页红点",depth:3},{value:"API / 接口",id:"api--接口",depth:2},{value:"ErrorRecord / 异常记录",id:"errorrecord--异常记录",depth:3},{value:"Session Persistence / 会话持久化",id:"session-persistence--会话持久化",depth:2},{value:"Enable / Disable / 开关",id:"enable--disable--开关",depth:2},{value:"Related / 相关",id:"related--相关",depth:2}]}r.default=(0,n.c)(function(e){let{toc:r=l(e)}=e,s={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(s.h1,{children:"Errors / 异常聚合查看器"}),"\n",(0,i.jsx)(s.h2,{id:r[0].id,children:r[0].value}),"\n",(0,i.jsxs)(s.blockquote,{children:["\n",(0,i.jsx)(s.p,{children:(0,i.jsx)(s.strong,{children:"Available since v1.9.0"})}),"\n",(0,i.jsx)(s.p,{children:(0,i.jsx)(s.strong,{children:"v1.9.0 起可用"})}),"\n"]}),"\n",(0,i.jsxs)(s.p,{children:["The Errors tab surfaces “the same crash happening repeatedly”. When the inspector is running, ",(0,i.jsx)(s.code,{children:"ErrorService"})," hooks both ",(0,i.jsx)(s.code,{children:"FlutterError.onError"})," and ",(0,i.jsx)(s.code,{children:"PlatformDispatcher.onError"})," (keeping the default red-screen / console behavior for both) and aggregates every exception by ",(0,i.jsx)(s.strong,{children:"type + stack signature"}),". Repeated instances of the same crash merge into one record that shows how many times it occurred and when it was first/last seen — instead of flooding the log with hundreds of identical stack traces."]}),"\n",(0,i.jsxs)(s.p,{children:["Errors 标签页用来快速发现”同一处崩溃反复出现”的问题。检查器运行时,",(0,i.jsx)(s.code,{children:"ErrorService"})," 会接管 ",(0,i.jsx)(s.code,{children:"FlutterError.onError"}),"(保留默认的红色报错与控制台行为),把每次异常按",(0,i.jsx)(s.strong,{children:"类型 + 堆栈签名"}),"去重聚合:同一处崩溃的多次发生会合并为一条记录,展示累计次数与首末次时间——而不是用成百上千条相同的堆栈刷屏日志。"]}),"\n",(0,i.jsx)(s.h2,{id:r[1].id,children:r[1].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(s.table,{children:[(0,i.jsx)(s.thead,{children:(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.th,{children:"Source / 来源"}),(0,i.jsx)(s.th,{children:"How / 方式"})]})}),(0,i.jsxs)(s.tbody,{children:[(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Flutter framework errors / Flutter 框架异常"}),(0,i.jsxs)(s.td,{children:["Hooks ",(0,i.jsx)(s.code,{children:"FlutterError.onError"})," (default handler kept) / 接管 ",(0,i.jsx)(s.code,{children:"FlutterError.onError"}),"(保留默认处理)"]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Framework-boundary & platform-channel errors / 框架边界外与平台通道异常"}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"PlatformDispatcher.onError"})," (with save/restore) / ",(0,i.jsx)(s.code,{children:"PlatformDispatcher.onError"}),"(接管 + 还原)"]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Uncaught async errors / 未捕获异步异常"}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"runZonedGuarded"})," inside ",(0,i.jsx)(s.code,{children:"runAppWithInspector()"})," / ",(0,i.jsx)(s.code,{children:"runAppWithInspector()"})," 内部的 ",(0,i.jsx)(s.code,{children:"runZonedGuarded"})]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Manual reports / 手动上报"}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"ErrorService.instance.report(exception, stackTrace)"})," — for gRPC / custom protocols / your own error paths / 用于 gRPC / 自定义协议或你自己的错误通道"]})]})]})]}),"\n",(0,i.jsxs)(s.p,{children:["Captured records are also ",(0,i.jsx)(s.strong,{children:"persisted to disk"})," (see ",(0,i.jsx)(s.a,{href:"#session-persistence--%E4%BC%9A%E8%AF%9D%E6%8C%81%E4%B9%85%E5%8C%96",children:"Session Persistence"})," below), so aggregated errors from previous sessions are replayed on the next launch."]}),"\n",(0,i.jsxs)(s.p,{children:["捕获到的记录也会",(0,i.jsx)(s.strong,{children:"落盘持久化"}),"(见下文",(0,i.jsx)(s.a,{href:"#session-persistence--%E4%BC%9A%E8%AF%9D%E6%8C%81%E4%B9%85%E5%8C%96",children:"会话持久化"}),"),下次启动时会回放上次会话的聚合异常。"]}),"\n",(0,i.jsx)(s.h2,{id:r[2].id,children:r[2].value}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:["Records are keyed by exception ",(0,i.jsx)(s.strong,{children:"type"})," plus a ",(0,i.jsx)(s.strong,{children:"stack signature"})," (first frames with line/address noise stripped) / 按异常",(0,i.jsx)(s.strong,{children:"类型"})," + ",(0,i.jsx)(s.strong,{children:"堆栈签名"}),"(取前若干帧并去掉行号/地址噪声)去重"]}),"\n",(0,i.jsxs)(s.li,{children:["A new occurrence of an existing signature bumps its ",(0,i.jsx)(s.code,{children:"count"})," and updates ",(0,i.jsx)(s.code,{children:"lastSeen"})," instead of adding a new row / 同签名的再次发生只累加 ",(0,i.jsx)(s.code,{children:"count"})," 并更新 ",(0,i.jsx)(s.code,{children:"lastSeen"}),",不新增行"]}),"\n",(0,i.jsxs)(s.li,{children:["Ring buffer capped at ",(0,i.jsx)(s.strong,{children:"200 aggregated records"})," (oldest dropped) / 环形缓冲上限 ",(0,i.jsx)(s.strong,{children:"200 条"}),"聚合记录(超出丢弃最旧)"]}),"\n"]}),"\n",(0,i.jsx)(s.h2,{id:r[3].id,children:r[3].value}),"\n",(0,i.jsx)(s.h3,{id:r[4].id,children:r[4].value}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:["Lists aggregated errors newest-first, showing the exception ",(0,i.jsx)(s.strong,{children:"type"}),", ",(0,i.jsx)(s.strong,{children:"message"}),", ",(0,i.jsx)(s.code,{children:"first seen"})," / ",(0,i.jsx)(s.code,{children:"last seen"})," time, and a ",(0,i.jsx)(s.strong,{children:"\xd7N"})," badge when the same crash recurred / 按最新在前列出聚合异常,展示异常",(0,i.jsx)(s.strong,{children:"类型"}),"、",(0,i.jsx)(s.strong,{children:"消息"}),"、",(0,i.jsx)(s.code,{children:"首次/末次"}),"出现时间,同崩溃复发时显示 ",(0,i.jsx)(s.strong,{children:"\xd7N"})," 徽章"]}),"\n",(0,i.jsxs)(s.li,{children:["Tap a row to expand the ",(0,i.jsx)(s.strong,{children:"full stack sample"})," (truncated at 8000 chars); tap again to collapse / 点击行展开",(0,i.jsx)(s.strong,{children:"完整堆栈样本"}),"(8000 字符截断);再点收起"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Copy stack"})," per row / 行内",(0,i.jsx)(s.strong,{children:"复制堆栈"})]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Search"})," filters by exception type or message / ",(0,i.jsx)(s.strong,{children:"搜索"}),"按异常类型或消息过滤"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Clear"})," empties the aggregated list / ",(0,i.jsx)(s.strong,{children:"清除"}),"清空聚合列表"]}),"\n"]}),"\n",(0,i.jsx)(s.h3,{id:r[5].id,children:r[5].value}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:["The ",(0,i.jsx)(s.strong,{children:"Errors tab icon"})," shows a red count badge while there are aggregated errors (capped at ",(0,i.jsx)(s.code,{children:"99+"}),"), so recurring crashes are visible at a glance / ",(0,i.jsx)(s.strong,{children:"Errors 标签页图标"}),"在有聚合异常时显示红色计数(",(0,i.jsx)(s.code,{children:"99+"})," 封顶),一眼可见反复崩溃"]}),"\n",(0,i.jsxs)(s.li,{children:["Opening the Errors tab is ",(0,i.jsx)(s.strong,{children:"not"})," required to see the badge — it reflects ",(0,i.jsx)(s.code,{children:"ErrorService.instance.errorCount"})," whenever the panel is open / 只要面板打开,标签红点即反映 ",(0,i.jsx)(s.code,{children:"ErrorService.instance.errorCount"}),",无需先进入 Errors 页"]}),"\n"]}),"\n",(0,i.jsxs)(s.blockquote,{children:["\n",(0,i.jsxs)(s.p,{children:["Note: the floating ball itself stays clean — its red count (if any) is the ",(0,i.jsx)(s.strong,{children:"Alerts"})," unread badge (",(0,i.jsx)(s.code,{children:"AlertService"}),"), not error aggregation."]}),"\n",(0,i.jsxs)(s.p,{children:["注意:悬浮球本身保持纯粹——球上的红色数字(如有)来自",(0,i.jsx)(s.strong,{children:"告警"}),"未读数(",(0,i.jsx)(s.code,{children:"AlertService"}),"),与异常聚合无关。"]}),"\n"]}),"\n",(0,i.jsx)(s.h2,{id:r[6].id,children:r[6].value}),"\n",(0,i.jsxs)(s.p,{children:["The full service is re-exported from the package root — no need to import ",(0,i.jsx)(s.code,{children:"lib/src/"}),"."]}),"\n",(0,i.jsxs)(s.p,{children:["完整服务已从包根导出,无需 import ",(0,i.jsx)(s.code,{children:"lib/src/"}),"。"]}),"\n",(0,i.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,i.jsxs)(s.code,{children:[(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"import"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'package:zero_inspector_kit/zero_inspector_kit.dart'"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]}),"\n",(0,i.jsx)(s.span,{children:" "}),"\n",(0,i.jsx)(s.span,{children:(0,i.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Report an exception manually (gRPC/custom protocol/your own error paths) / 手动上报异常"})}),"\n",(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ErrorService"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"report"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(error, stackTrace, "}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'myModule'"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,i.jsx)(s.span,{children:" "}),"\n",(0,i.jsx)(s.span,{children:(0,i.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Read the aggregated view (newest first) / 读取聚合视图(最新在前)"})}),"\n",(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ErrorRecord"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" latest "}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ErrorService"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.errors.first;"})]}),"\n",(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"print"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"${"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"latest"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"type"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" x"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"${"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"latest"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"count"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" — first "}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"${"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"latest"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"firstSeen"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" / last "}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"${"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"latest"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"lastSeen"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,i.jsx)(s.span,{children:" "}),"\n",(0,i.jsx)(s.span,{children:(0,i.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Clear all records / 清空所有记录"})}),"\n",(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ErrorService"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"clear"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(s.table,{children:[(0,i.jsx)(s.thead,{children:(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.th,{children:"Member"}),(0,i.jsx)(s.th,{children:"Description"})]})}),(0,i.jsxs)(s.tbody,{children:[(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"errors"})}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"List"})," — aggregated records, newest first / 聚合记录,最新在前"]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"errorCount"})}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"int"})," — number of aggregated records / 聚合记录条数"]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"isEnabled"})}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"bool"})," — capture toggle (programmatic) / 抓取开关(代码控制)"]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"report(exception, stack, [context])"})}),(0,i.jsx)(s.td,{children:"Manually report an exception / 手动上报异常"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"restore(records)"})}),(0,i.jsx)(s.td,{children:"Restore persisted records (replay on launch; existing dedup ids are skipped) / 恢复持久化记录(启动回放;已存在的去重 id 跳过)"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"clear()"})}),(0,i.jsx)(s.td,{children:"Clear all aggregated records / 清空全部记录"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"install()"})," / ",(0,i.jsx)(s.code,{children:"uninstall()"})]}),(0,i.jsxs)(s.td,{children:["Hook / restore ",(0,i.jsx)(s.code,{children:"FlutterError.onError"})," ",(0,i.jsx)(s.strong,{children:"and"})," ",(0,i.jsx)(s.code,{children:"PlatformDispatcher.onError"})," / 接管 / 还原 ",(0,i.jsx)(s.code,{children:"FlutterError.onError"})," 与 ",(0,i.jsx)(s.code,{children:"PlatformDispatcher.onError"})]})]})]})]}),"\n",(0,i.jsx)(s.h3,{id:r[7].id,children:r[7].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(s.table,{children:[(0,i.jsx)(s.thead,{children:(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.th,{children:"Field"}),(0,i.jsx)(s.th,{children:"Description"})]})}),(0,i.jsxs)(s.tbody,{children:[(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"type"})}),(0,i.jsxs)(s.td,{children:["Exception type name (e.g. ",(0,i.jsx)(s.code,{children:"_TypeError"}),") / 异常类型名"]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"message"})}),(0,i.jsx)(s.td,{children:"Exception message / 异常消息"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"count"})}),(0,i.jsx)(s.td,{children:"Total occurrences / 累计出现次数"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"firstSeen"})," / ",(0,i.jsx)(s.code,{children:"lastSeen"})]}),(0,i.jsx)(s.td,{children:"First / last occurrence time / 首次 / 末次出现时间"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"sampleStack"})}),(0,i.jsx)(s.td,{children:"One full stack sample (may be truncated) / 一条完整堆栈样本(可能截断)"})]})]})]}),"\n",(0,i.jsx)(s.h2,{id:r[8].id,children:r[8].value}),"\n",(0,i.jsxs)(s.p,{children:["Errors — together with logs, network requests ",(0,i.jsx)(s.strong,{children:"and alerts"})," — are asynchronously flushed to a local SQLite ",(0,i.jsx)(s.strong,{children:"ring buffer"})," (",(0,i.jsx)(s.code,{children:"zero_inspector_kit.db"}),"). On the next launch, ",(0,i.jsx)(s.strong,{children:"logs, aggregated errors and alerts replay into their tabs"}),", so a crash you saw yesterday is still inspectable today even though the panel was never opened; network requests stay archived on disk for later export. Use the ",(0,i.jsx)(s.strong,{children:"storage icon"})," in the panel header to open the ",(0,i.jsx)(s.strong,{children:"Persisted data"})," manager: see the current row counts, ",(0,i.jsx)(s.strong,{children:"export the full session archive"})," as JSON, or clear the disk. See ",(0,i.jsx)(s.a,{href:"Configuration",children:"Configuration"})," (PersistenceService section) for details and the tuning parameters."]}),"\n",(0,i.jsxs)(s.p,{children:["异常与日志、网络请求、告警一起被异步写入本地 SQLite ",(0,i.jsx)(s.strong,{children:"环形缓冲"}),"(",(0,i.jsx)(s.code,{children:"zero_inspector_kit.db"}),")。",(0,i.jsx)(s.strong,{children:"下次启动时,日志、聚合异常与告警会回放入各自标签页"}),"——即使昨天从没打开过面板,今天依然能复盘当时的崩溃现场;网络请求保留在磁盘存档,供之后导出。点击面板头部的",(0,i.jsx)(s.strong,{children:"存储图标"}),"可打开 ",(0,i.jsx)(s.strong,{children:"Persisted data"})," 管理弹层:查看当前行数、",(0,i.jsx)(s.strong,{children:"导出完整会话存档"})," JSON,或清空磁盘。详见 ",(0,i.jsx)(s.a,{href:"Configuration",children:"Configuration"}),"(PersistenceService 一节)的参数说明。"]}),"\n",(0,i.jsx)(s.h2,{id:r[9].id,children:r[9].value}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"enableErrorCapture"})," (default ",(0,i.jsx)(s.code,{children:"true"}),") in ",(0,i.jsx)(s.code,{children:"ZeroInspectorKit.init()"})," controls error aggregation / ",(0,i.jsx)(s.code,{children:"init()"})," 的 ",(0,i.jsx)(s.code,{children:"enableErrorCapture"}),"(默认 ",(0,i.jsx)(s.code,{children:"true"}),")控制异常聚合"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"enablePersistence"})," (default ",(0,i.jsx)(s.code,{children:"true"}),") controls the disk ring buffer / ",(0,i.jsx)(s.code,{children:"enablePersistence"}),"(默认 ",(0,i.jsx)(s.code,{children:"true"}),")控制磁盘环形缓冲"]}),"\n",(0,i.jsxs)(s.li,{children:["Set both to ",(0,i.jsx)(s.code,{children:"false"})," to keep everything in memory only / 两者都设为 ",(0,i.jsx)(s.code,{children:"false"})," 时数据仅保留在内存"]}),"\n"]}),"\n",(0,i.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,i.jsxs)(s.code,{children:[(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ZeroInspectorKit"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"init"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" enableErrorCapture"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" true"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" enablePersistence"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" true"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// false → memory-only / 关闭后仅内存"})]}),"\n",(0,i.jsx)(s.span,{children:(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})})]})}),"\n",(0,i.jsx)(s.h2,{id:r[10].id,children:r[10].value}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"Log-Viewer",children:"Log Viewer"})," — raw log stream including error lines / 原始日志流(含错误行)"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"Configuration",children:"Configuration"})," — init parameters and full service APIs / 初始化参数与完整服务接口"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"Usage",children:"Usage"})," — general usage guide / 使用指南"]}),"\n"]})]})},"/Errors",{filePath:"pages/Errors.md",timestamp:1788758921e3,pageMap:t.v,frontMatter:{},title:"Errors / 异常聚合查看器"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,r,s){"use strict";s.d(r,{v:function(){return i}});let i=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=7031)}),_N_E=e.O()}]); \ No newline at end of file +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[302],{7031:function(e,r,s){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Errors",function(){return s(1824)}])},1824:function(e,r,s){"use strict";s.r(r),s.d(r,{useTOC:function(){return l}});var i=s(5893),n=s(7812),t=s(7080),d=s(8925);function l(e){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"What Gets Captured / 捕获来源",id:"what-gets-captured--捕获来源",depth:2},{value:"Deduplication / 去重规则",id:"deduplication--去重规则",depth:2},{value:"UI Features / UI 功能",id:"ui-features--ui-功能",depth:2},{value:"Errors Tab / Errors 标签页",id:"errors-tab--errors-标签页",depth:3},{value:"Tab Badge / 标签页红点",id:"tab-badge--标签页红点",depth:3},{value:"API / 接口",id:"api--接口",depth:2},{value:"ErrorRecord / 异常记录",id:"errorrecord--异常记录",depth:3},{value:"Session Persistence / 会话持久化",id:"session-persistence--会话持久化",depth:2},{value:"Enable / Disable / 开关",id:"enable--disable--开关",depth:2},{value:"Related / 相关",id:"related--相关",depth:2}]}r.default=(0,n.c)(function(e){let{toc:r=l(e)}=e,s={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(s.h1,{children:"Errors / 异常聚合查看器"}),"\n",(0,i.jsx)(s.h2,{id:r[0].id,children:r[0].value}),"\n",(0,i.jsxs)(s.blockquote,{children:["\n",(0,i.jsx)(s.p,{children:(0,i.jsx)(s.strong,{children:"Available since v1.9.0"})}),"\n",(0,i.jsx)(s.p,{children:(0,i.jsx)(s.strong,{children:"v1.9.0 起可用"})}),"\n"]}),"\n",(0,i.jsxs)(s.p,{children:["The Errors tab surfaces “the same crash happening repeatedly”. When the inspector is running, ",(0,i.jsx)(s.code,{children:"ErrorService"})," hooks both ",(0,i.jsx)(s.code,{children:"FlutterError.onError"})," and ",(0,i.jsx)(s.code,{children:"PlatformDispatcher.onError"})," (keeping the default red-screen / console behavior for both) and aggregates every exception by ",(0,i.jsx)(s.strong,{children:"type + stack signature"}),". Repeated instances of the same crash merge into one record that shows how many times it occurred and when it was first/last seen — instead of flooding the log with hundreds of identical stack traces."]}),"\n",(0,i.jsxs)(s.p,{children:["Errors 标签页用来快速发现”同一处崩溃反复出现”的问题。检查器运行时,",(0,i.jsx)(s.code,{children:"ErrorService"})," 会接管 ",(0,i.jsx)(s.code,{children:"FlutterError.onError"}),"(保留默认的红色报错与控制台行为),把每次异常按",(0,i.jsx)(s.strong,{children:"类型 + 堆栈签名"}),"去重聚合:同一处崩溃的多次发生会合并为一条记录,展示累计次数与首末次时间——而不是用成百上千条相同的堆栈刷屏日志。"]}),"\n",(0,i.jsx)(s.h2,{id:r[1].id,children:r[1].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(s.table,{children:[(0,i.jsx)(s.thead,{children:(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.th,{children:"Source / 来源"}),(0,i.jsx)(s.th,{children:"How / 方式"})]})}),(0,i.jsxs)(s.tbody,{children:[(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Flutter framework errors / Flutter 框架异常"}),(0,i.jsxs)(s.td,{children:["Hooks ",(0,i.jsx)(s.code,{children:"FlutterError.onError"})," (default handler kept) / 接管 ",(0,i.jsx)(s.code,{children:"FlutterError.onError"}),"(保留默认处理)"]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Framework-boundary & platform-channel errors / 框架边界外与平台通道异常"}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"PlatformDispatcher.onError"})," (with save/restore) / ",(0,i.jsx)(s.code,{children:"PlatformDispatcher.onError"}),"(接管 + 还原)"]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Uncaught async errors / 未捕获异步异常"}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"runZonedGuarded"})," inside ",(0,i.jsx)(s.code,{children:"runAppWithInspector()"})," / ",(0,i.jsx)(s.code,{children:"runAppWithInspector()"})," 内部的 ",(0,i.jsx)(s.code,{children:"runZonedGuarded"})]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Manual reports / 手动上报"}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"ErrorService.instance.report(exception, stackTrace)"})," — for gRPC / custom protocols / your own error paths / 用于 gRPC / 自定义协议或你自己的错误通道"]})]})]})]}),"\n",(0,i.jsxs)(s.p,{children:["Captured records are also ",(0,i.jsx)(s.strong,{children:"persisted to disk"})," (see ",(0,i.jsx)(s.a,{href:"#session-persistence--%E4%BC%9A%E8%AF%9D%E6%8C%81%E4%B9%85%E5%8C%96",children:"Session Persistence"})," below), so aggregated errors from previous sessions are replayed on the next launch."]}),"\n",(0,i.jsxs)(s.p,{children:["捕获到的记录也会",(0,i.jsx)(s.strong,{children:"落盘持久化"}),"(见下文",(0,i.jsx)(s.a,{href:"#session-persistence--%E4%BC%9A%E8%AF%9D%E6%8C%81%E4%B9%85%E5%8C%96",children:"会话持久化"}),"),下次启动时会回放上次会话的聚合异常。"]}),"\n",(0,i.jsx)(s.h2,{id:r[2].id,children:r[2].value}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:["Records are keyed by exception ",(0,i.jsx)(s.strong,{children:"type"})," plus a ",(0,i.jsx)(s.strong,{children:"stack signature"})," (first frames with line/address noise stripped) / 按异常",(0,i.jsx)(s.strong,{children:"类型"})," + ",(0,i.jsx)(s.strong,{children:"堆栈签名"}),"(取前若干帧并去掉行号/地址噪声)去重"]}),"\n",(0,i.jsxs)(s.li,{children:["A new occurrence of an existing signature bumps its ",(0,i.jsx)(s.code,{children:"count"})," and updates ",(0,i.jsx)(s.code,{children:"lastSeen"})," instead of adding a new row / 同签名的再次发生只累加 ",(0,i.jsx)(s.code,{children:"count"})," 并更新 ",(0,i.jsx)(s.code,{children:"lastSeen"}),",不新增行"]}),"\n",(0,i.jsxs)(s.li,{children:["Ring buffer capped at ",(0,i.jsx)(s.strong,{children:"200 aggregated records"})," (oldest dropped) / 环形缓冲上限 ",(0,i.jsx)(s.strong,{children:"200 条"}),"聚合记录(超出丢弃最旧)"]}),"\n"]}),"\n",(0,i.jsx)(s.h2,{id:r[3].id,children:r[3].value}),"\n",(0,i.jsx)(s.h3,{id:r[4].id,children:r[4].value}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:["Lists aggregated errors newest-first, showing the exception ",(0,i.jsx)(s.strong,{children:"type"}),", ",(0,i.jsx)(s.strong,{children:"message"}),", ",(0,i.jsx)(s.code,{children:"first seen"})," / ",(0,i.jsx)(s.code,{children:"last seen"})," time, and a ",(0,i.jsx)(s.strong,{children:"\xd7N"})," badge when the same crash recurred / 按最新在前列出聚合异常,展示异常",(0,i.jsx)(s.strong,{children:"类型"}),"、",(0,i.jsx)(s.strong,{children:"消息"}),"、",(0,i.jsx)(s.code,{children:"首次/末次"}),"出现时间,同崩溃复发时显示 ",(0,i.jsx)(s.strong,{children:"\xd7N"})," 徽章"]}),"\n",(0,i.jsxs)(s.li,{children:["Tap a row to expand the ",(0,i.jsx)(s.strong,{children:"full stack sample"})," (truncated at 8000 chars); tap again to collapse / 点击行展开",(0,i.jsx)(s.strong,{children:"完整堆栈样本"}),"(8000 字符截断);再点收起"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Copy stack"})," per row / 行内",(0,i.jsx)(s.strong,{children:"复制堆栈"})]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Search"})," filters by exception type or message / ",(0,i.jsx)(s.strong,{children:"搜索"}),"按异常类型或消息过滤"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Clear"})," empties the aggregated list / ",(0,i.jsx)(s.strong,{children:"清除"}),"清空聚合列表"]}),"\n"]}),"\n",(0,i.jsx)(s.h3,{id:r[5].id,children:r[5].value}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:["The ",(0,i.jsx)(s.strong,{children:"Errors tab icon"})," shows a red count badge while there are aggregated errors (capped at ",(0,i.jsx)(s.code,{children:"99+"}),"), so recurring crashes are visible at a glance / ",(0,i.jsx)(s.strong,{children:"Errors 标签页图标"}),"在有聚合异常时显示红色计数(",(0,i.jsx)(s.code,{children:"99+"})," 封顶),一眼可见反复崩溃"]}),"\n",(0,i.jsxs)(s.li,{children:["Opening the Errors tab is ",(0,i.jsx)(s.strong,{children:"not"})," required to see the badge — it reflects ",(0,i.jsx)(s.code,{children:"ErrorService.instance.errorCount"})," whenever the panel is open / 只要面板打开,标签红点即反映 ",(0,i.jsx)(s.code,{children:"ErrorService.instance.errorCount"}),",无需先进入 Errors 页"]}),"\n"]}),"\n",(0,i.jsxs)(s.blockquote,{children:["\n",(0,i.jsxs)(s.p,{children:["Note: the floating ball itself stays clean — its red count (if any) is the ",(0,i.jsx)(s.strong,{children:"Alerts"})," unread badge (",(0,i.jsx)(s.code,{children:"AlertService"}),"), not error aggregation."]}),"\n",(0,i.jsxs)(s.p,{children:["注意:悬浮球本身保持纯粹——球上的红色数字(如有)来自",(0,i.jsx)(s.strong,{children:"告警"}),"未读数(",(0,i.jsx)(s.code,{children:"AlertService"}),"),与异常聚合无关。"]}),"\n"]}),"\n",(0,i.jsx)(s.h2,{id:r[6].id,children:r[6].value}),"\n",(0,i.jsxs)(s.p,{children:["The full service is re-exported from the package root — no need to import ",(0,i.jsx)(s.code,{children:"lib/src/"}),"."]}),"\n",(0,i.jsxs)(s.p,{children:["完整服务已从包根导出,无需 import ",(0,i.jsx)(s.code,{children:"lib/src/"}),"。"]}),"\n",(0,i.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,i.jsxs)(s.code,{children:[(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"import"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'package:zero_inspector_kit/zero_inspector_kit.dart'"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]}),"\n",(0,i.jsx)(s.span,{children:" "}),"\n",(0,i.jsx)(s.span,{children:(0,i.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Report an exception manually (gRPC/custom protocol/your own error paths) / 手动上报异常"})}),"\n",(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ErrorService"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"report"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(error, stackTrace, "}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'myModule'"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,i.jsx)(s.span,{children:" "}),"\n",(0,i.jsx)(s.span,{children:(0,i.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Read the aggregated view (newest first) / 读取聚合视图(最新在前)"})}),"\n",(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ErrorRecord"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" latest "}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ErrorService"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.errors.first;"})]}),"\n",(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"print"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"${"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"latest"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"type"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" x"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"${"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"latest"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"count"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" — first "}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"${"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"latest"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"firstSeen"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" / last "}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"${"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"latest"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"lastSeen"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,i.jsx)(s.span,{children:" "}),"\n",(0,i.jsx)(s.span,{children:(0,i.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Clear all records / 清空所有记录"})}),"\n",(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ErrorService"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"clear"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(s.table,{children:[(0,i.jsx)(s.thead,{children:(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.th,{children:"Member"}),(0,i.jsx)(s.th,{children:"Description"})]})}),(0,i.jsxs)(s.tbody,{children:[(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"errors"})}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"List"})," — aggregated records, newest first / 聚合记录,最新在前"]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"errorCount"})}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"int"})," — number of aggregated records / 聚合记录条数"]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"isEnabled"})}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"bool"})," — capture toggle (programmatic) / 抓取开关(代码控制)"]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"report(exception, stack, [context])"})}),(0,i.jsx)(s.td,{children:"Manually report an exception / 手动上报异常"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"restore(records)"})}),(0,i.jsx)(s.td,{children:"Restore persisted records (replay on launch; existing dedup ids are skipped) / 恢复持久化记录(启动回放;已存在的去重 id 跳过)"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"clear()"})}),(0,i.jsx)(s.td,{children:"Clear all aggregated records / 清空全部记录"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"install()"})," / ",(0,i.jsx)(s.code,{children:"uninstall()"})]}),(0,i.jsxs)(s.td,{children:["Hook / restore ",(0,i.jsx)(s.code,{children:"FlutterError.onError"})," ",(0,i.jsx)(s.strong,{children:"and"})," ",(0,i.jsx)(s.code,{children:"PlatformDispatcher.onError"})," / 接管 / 还原 ",(0,i.jsx)(s.code,{children:"FlutterError.onError"})," 与 ",(0,i.jsx)(s.code,{children:"PlatformDispatcher.onError"})]})]})]})]}),"\n",(0,i.jsx)(s.h3,{id:r[7].id,children:r[7].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(s.table,{children:[(0,i.jsx)(s.thead,{children:(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.th,{children:"Field"}),(0,i.jsx)(s.th,{children:"Description"})]})}),(0,i.jsxs)(s.tbody,{children:[(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"type"})}),(0,i.jsxs)(s.td,{children:["Exception type name (e.g. ",(0,i.jsx)(s.code,{children:"_TypeError"}),") / 异常类型名"]})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"message"})}),(0,i.jsx)(s.td,{children:"Exception message / 异常消息"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"count"})}),(0,i.jsx)(s.td,{children:"Total occurrences / 累计出现次数"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"firstSeen"})," / ",(0,i.jsx)(s.code,{children:"lastSeen"})]}),(0,i.jsx)(s.td,{children:"First / last occurrence time / 首次 / 末次出现时间"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:(0,i.jsx)(s.code,{children:"sampleStack"})}),(0,i.jsx)(s.td,{children:"One full stack sample (may be truncated) / 一条完整堆栈样本(可能截断)"})]})]})]}),"\n",(0,i.jsx)(s.h2,{id:r[8].id,children:r[8].value}),"\n",(0,i.jsxs)(s.p,{children:["Errors — together with logs, network requests ",(0,i.jsx)(s.strong,{children:"and alerts"})," — are asynchronously flushed to a local SQLite ",(0,i.jsx)(s.strong,{children:"ring buffer"})," (",(0,i.jsx)(s.code,{children:"zero_inspector_kit.db"}),"). On the next launch, ",(0,i.jsx)(s.strong,{children:"logs, aggregated errors and alerts replay into their tabs"}),", so a crash you saw yesterday is still inspectable today even though the panel was never opened; network requests stay archived on disk for later export. Use the ",(0,i.jsx)(s.strong,{children:"storage icon"})," in the panel header to open the ",(0,i.jsx)(s.strong,{children:"Persisted data"})," manager: see the current row counts, ",(0,i.jsx)(s.strong,{children:"export the full session archive"})," as JSON, or clear the disk. See ",(0,i.jsx)(s.a,{href:"Configuration",children:"Configuration"})," (PersistenceService section) for details and the tuning parameters."]}),"\n",(0,i.jsxs)(s.p,{children:["异常与日志、网络请求、告警一起被异步写入本地 SQLite ",(0,i.jsx)(s.strong,{children:"环形缓冲"}),"(",(0,i.jsx)(s.code,{children:"zero_inspector_kit.db"}),")。",(0,i.jsx)(s.strong,{children:"下次启动时,日志、聚合异常与告警会回放入各自标签页"}),"——即使昨天从没打开过面板,今天依然能复盘当时的崩溃现场;网络请求保留在磁盘存档,供之后导出。点击面板头部的",(0,i.jsx)(s.strong,{children:"存储图标"}),"可打开 ",(0,i.jsx)(s.strong,{children:"Persisted data"})," 管理弹层:查看当前行数、",(0,i.jsx)(s.strong,{children:"导出完整会话存档"})," JSON,或清空磁盘。详见 ",(0,i.jsx)(s.a,{href:"Configuration",children:"Configuration"}),"(PersistenceService 一节)的参数说明。"]}),"\n",(0,i.jsx)(s.h2,{id:r[9].id,children:r[9].value}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"enableErrorCapture"})," (default ",(0,i.jsx)(s.code,{children:"true"}),") in ",(0,i.jsx)(s.code,{children:"ZeroInspectorKit.init()"})," controls error aggregation / ",(0,i.jsx)(s.code,{children:"init()"})," 的 ",(0,i.jsx)(s.code,{children:"enableErrorCapture"}),"(默认 ",(0,i.jsx)(s.code,{children:"true"}),")控制异常聚合"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"enablePersistence"})," (default ",(0,i.jsx)(s.code,{children:"true"}),") controls the disk ring buffer / ",(0,i.jsx)(s.code,{children:"enablePersistence"}),"(默认 ",(0,i.jsx)(s.code,{children:"true"}),")控制磁盘环形缓冲"]}),"\n",(0,i.jsxs)(s.li,{children:["Set both to ",(0,i.jsx)(s.code,{children:"false"})," to keep everything in memory only / 两者都设为 ",(0,i.jsx)(s.code,{children:"false"})," 时数据仅保留在内存"]}),"\n"]}),"\n",(0,i.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,i.jsxs)(s.code,{children:[(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ZeroInspectorKit"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"init"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" enableErrorCapture"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" true"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,i.jsxs)(s.span,{children:[(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" enablePersistence"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" true"}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,i.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// false → memory-only / 关闭后仅内存"})]}),"\n",(0,i.jsx)(s.span,{children:(0,i.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})})]})}),"\n",(0,i.jsx)(s.h2,{id:r[10].id,children:r[10].value}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"Log-Viewer",children:"Log Viewer"})," — raw log stream including error lines / 原始日志流(含错误行)"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"Configuration",children:"Configuration"})," — init parameters and full service APIs / 初始化参数与完整服务接口"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"Usage",children:"Usage"})," — general usage guide / 使用指南"]}),"\n"]})]})},"/Errors",{filePath:"pages/Errors.md",timestamp:1788758921e3,pageMap:t.v,frontMatter:{},title:"Errors / 异常聚合查看器"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,r,s){"use strict";s.d(r,{v:function(){return i}});let i=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=7031)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/FAQ-3f5854cf7971f23b.js b/docs/_next/static/chunks/pages/FAQ-09aca322a5a031c7.js similarity index 93% rename from docs/_next/static/chunks/pages/FAQ-3f5854cf7971f23b.js rename to docs/_next/static/chunks/pages/FAQ-09aca322a5a031c7.js index f6cab42..61ba3e9 100644 --- a/docs/_next/static/chunks/pages/FAQ-3f5854cf7971f23b.js +++ b/docs/_next/static/chunks/pages/FAQ-09aca322a5a031c7.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[210],{9238:function(e,r,i){(window.__NEXT_P=window.__NEXT_P||[]).push(["/FAQ",function(){return i(5797)}])},5797:function(e,r,i){"use strict";i.r(r),i.d(r,{useTOC:function(){return o}});var n=i(5893),s=i(7812),t=i(7080),d=i(8925);function o(e){return[{value:"General / 通用",id:"general--通用",depth:2},{value:"Q: Does the inspector affect production builds? / 检查器会影响生产构建吗?",id:"q-does-the-inspector-affect-production-builds--检查器会影响生产构建吗",depth:3},{value:"Q: What platforms are supported? / 支持哪些平台?",id:"q-what-platforms-are-supported--支持哪些平台",depth:3},{value:"Q: What Flutter/Dart versions are required? / 需要什么 Flutter/Dart 版本?",id:"q-what-flutterdart-versions-are-required--需要什么-flutterdart-版本",depth:3},{value:"Network / 网络",id:"network--网络",depth:2},{value:"Q: Do I need to manually add interceptors for Dio? / 需要为 Dio 手动添加拦截器吗?",id:"q-do-i-need-to-manually-add-interceptors-for-dio--需要为-dio-手动添加拦截器吗",depth:3},{value:"Q: Why do I see duplicate Dio requests? / 为什么看到重复的 Dio 请求?",id:"q-why-do-i-see-duplicate-dio-requests--为什么看到重复的-dio-请求",depth:3},{value:"Q: Can I modify requests during testing? / 测试时可以修改请求吗?",id:"q-can-i-modify-requests-during-testing--测试时可以修改请求吗",depth:3},{value:"Logging / 日志",id:"logging--日志",depth:2},{value:"Q: Can I use my existing logging library? / 可以使用现有的日志库吗?",id:"q-can-i-use-my-existing-logging-library--可以使用现有的日志库吗",depth:3},{value:"Q: How to sync inspector logs to my logger? / 如何将检查器日志同步到我的日志库?",id:"q-how-to-sync-inspector-logs-to-my-logger--如何将检查器日志同步到我的日志库",depth:3},{value:"Database / 数据库",id:"database--数据库",depth:2},{value:"Q: Why don’t I see any databases? / 为什么看不到数据库?",id:"q-why-dont-i-see-any-databases--为什么看不到数据库",depth:3},{value:"Memory / 内存",id:"memory--内存",depth:2},{value:"Q: Why does Dart Heap show “N/A” when debugging via PC? / 通过 PC 调试时为什么 Dart Heap 显示 “N/A”?",id:"q-why-does-dart-heap-show-na-when-debugging-via-pc--通过-pc-调试时为什么-dart-heap-显示-na",depth:3},{value:"Q: Does memory monitoring affect performance? / 内存监控会影响性能吗?",id:"q-does-memory-monitoring-affect-performance--内存监控会影响性能吗",depth:3},{value:"Q: Does leak detection require code modification? / 泄漏检测需要侵入代码吗?",id:"q-does-leak-detection-require-code-modification--泄漏检测需要侵入代码吗",depth:3},{value:"FPS / 帧率",id:"fps--帧率",depth:2},{value:"Q: Why does FPS show a very low value (e.g. 9-10 FPS)? / 为什么 FPS 显示很低(如 9-10)?",id:"q-why-does-fps-show-a-very-low-value-eg-9-10-fps--为什么-fps-显示很低如-9-10",depth:3},{value:"Q: Does FPS monitoring affect performance? / FPS 监控会影响性能吗?",id:"q-does-fps-monitoring-affect-performance--fps-监控会影响性能吗",depth:3},{value:"UI / 界面",id:"ui--界面",depth:2},{value:"Q: The inspector panel gets pushed up when keyboard appears. / 键盘弹出时检查器面板被顶起来了。",id:"q-the-inspector-panel-gets-pushed-up-when-keyboard-appears--键盘弹出时检查器面板被顶起来了",depth:3},{value:"Q: How to use search? / 如何使用搜索?",id:"q-how-to-use-search--如何使用搜索",depth:3},{value:"Q: The floating button disappeared / can’t be opened after enabling FPS. / 开启 FPS 后悬浮按钮消失/无法打开。",id:"q-the-floating-button-disappeared--cant-be-opened-after-enabling-fps--开启-fps-后悬浮按钮消失无法打开",depth:3},{value:"Q: How does the floating button edge docking work? / 悬浮按钮的边缘吸附怎么用?",id:"q-how-does-the-floating-button-edge-docking-work--悬浮按钮的边缘吸附怎么用",depth:3},{value:"License / 许可证",id:"license--许可证",depth:2},{value:"Q: Can I use this in a commercial project? / 可以在商业项目中使用吗?",id:"q-can-i-use-this-in-a-commercial-project--可以在商业项目中使用吗",depth:3},{value:"Q: Are you liable for issues in modified versions? / 修改版出问题你们负责吗?",id:"q-are-you-liable-for-issues-in-modified-versions--修改版出问题你们负责吗",depth:3}]}r.default=(0,s.c)(function(e){let{toc:r=o(e)}=e,i={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",hr:"hr",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",ul:"ul",...(0,d.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(i.h1,{children:"FAQ / 常见问题"}),"\n",(0,n.jsx)(i.h2,{id:r[0].id,children:r[0].value}),"\n",(0,n.jsx)(i.h3,{id:r[1].id,children:r[1].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," No. The inspector is automatically disabled in release mode via ",(0,n.jsx)(i.code,{children:"kReleaseMode"}),". Flutter’s tree-shaking removes all inspector-related code from production builds. You don’t need to remove any code."]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"不会。"})," 检查器在 release 模式下通过 ",(0,n.jsx)(i.code,{children:"kReleaseMode"})," 自动禁用。Flutter 的 tree-shaking 会移除所有检查器相关代码,无需手动移除。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[2].id,children:r[2].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Android and iOS."]}),"\n",(0,n.jsx)(i.p,{children:"支持 Android 和 iOS。"}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[3].id,children:r[3].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Flutter >= 3.3.0, Dart SDK >= 3.11.0 < 4.0.0."]}),"\n",(0,n.jsx)(i.p,{children:"Flutter >= 3.3.0,Dart SDK >= 3.11.0 < 4.0.0。"}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[4].id,children:r[4].value}),"\n",(0,n.jsx)(i.h3,{id:r[5].id,children:r[5].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," No. Dio uses ",(0,n.jsx)(i.code,{children:"IOHttpClientAdapter"})," internally, which uses ",(0,n.jsx)(i.code,{children:"dart:io"}),"’s ",(0,n.jsx)(i.code,{children:"HttpClient"}),". The inspector captures all requests via ",(0,n.jsx)(i.code,{children:"HttpOverrides"})," automatically, making it truly zero-invasion for both http package and Dio."]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"不需要。"})," Dio 内部使用 ",(0,n.jsx)(i.code,{children:"IOHttpClientAdapter"}),",底层使用 ",(0,n.jsx)(i.code,{children:"dart:io"})," 的 ",(0,n.jsx)(i.code,{children:"HttpClient"}),"。检查器通过 ",(0,n.jsx)(i.code,{children:"HttpOverrides"})," 自动捕获所有请求,对 http 包和 Dio 都是真正的零侵入。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[6].id,children:r[6].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," If you use both ",(0,n.jsx)(i.code,{children:"InspectorDioInterceptor"})," and the auto-capture (HttpOverrides), Dio requests will be recorded twice. Simply remove the manual ",(0,n.jsx)(i.code,{children:"InspectorDioInterceptor"})," — auto-capture handles everything."]}),"\n",(0,n.jsxs)(i.p,{children:["如果同时使用了 ",(0,n.jsx)(i.code,{children:"InspectorDioInterceptor"})," 和自动捕获(HttpOverrides),Dio 请求会被记录两次。移除手动 ",(0,n.jsx)(i.code,{children:"InspectorDioInterceptor"})," 即可,自动捕获会处理一切。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[7].id,children:r[7].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Yes (since v1.0.7). The inspector supports intercepting and modifying requests via rules. Open a request detail and tap the Interceptor icon to configure a rule. You can modify the ",(0,n.jsx)(i.strong,{children:"request body"})," and ",(0,n.jsx)(i.strong,{children:"request headers"})," only — response fields (status code, response body) are read-only (since v1.0.8). GET requests cannot be modified (no request body). The master toggle is on the Network list page; rules only apply when modification mode is enabled."]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"可以"}),"(v1.0.7 起)。检查器支持通过规则拦截并修改请求。打开请求详情,点击拦截器图标配置规则。仅可修改",(0,n.jsx)(i.strong,{children:"请求体"}),"和",(0,n.jsx)(i.strong,{children:"请求头"}),"——响应字段(状态码、响应体)只读(v1.0.8 起)。GET 请求不可修改(无请求体)。总开关在 Network 列表页,规则仅在启用修改模式时生效。"]}),"\n",(0,n.jsxs)(i.p,{children:["See ",(0,n.jsx)(i.a,{href:"Network-Inspector#request-interceptor--%E8%AF%B7%E6%B1%82%E6%8B%A6%E6%88%AA%E4%BF%AE%E6%94%B9",children:"Network Inspector > Request Interceptor"})," for details."]}),"\n",(0,n.jsxs)(i.p,{children:["详见 ",(0,n.jsx)(i.a,{href:"Network-Inspector#request-interceptor--%E8%AF%B7%E6%B1%82%E6%8B%A6%E6%88%AA%E4%BF%AE%E6%94%B9",children:"Network Inspector > 请求拦截修改"}),"。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[8].id,children:r[8].value}),"\n",(0,n.jsx)(i.h3,{id:r[9].id,children:r[9].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Yes! The inspector automatically captures logs from any library that uses ",(0,n.jsx)(i.code,{children:"print()"})," or ",(0,n.jsx)(i.code,{children:"debugPrint()"}),". No configuration needed. Third-party logs are categorized as ",(0,n.jsx)(i.strong,{children:"Info level"}),"."]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"可以!"})," 检查器会自动捕获任何使用 ",(0,n.jsx)(i.code,{children:"print()"})," 或 ",(0,n.jsx)(i.code,{children:"debugPrint()"})," 的日志库的日志,无需配置。第三方日志统一归类为 ",(0,n.jsx)(i.strong,{children:"Info 级别"}),"。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[10].id,children:r[10].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Use the ",(0,n.jsx)(i.code,{children:"onLogCaptured"})," callback:"]}),"\n",(0,n.jsxs)(i.p,{children:["使用 ",(0,n.jsx)(i.code,{children:"onLogCaptured"})," 回调:"]}),"\n",(0,n.jsx)(i.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(i.code,{children:[(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.onLogCaptured "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" (entry) {"})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" yourLogger."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"log"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(entry.message);"})]}),"\n",(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"};"})})]})}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"Warning"}),": Do NOT call ",(0,n.jsx)(i.code,{children:"print()"})," or logging methods inside this callback, as it will cause infinite recursion.\n",(0,n.jsx)(i.strong,{children:"警告"}),":不要在此回调中调用 ",(0,n.jsx)(i.code,{children:"print()"})," 或日志方法,否则会导致无限递归。"]}),"\n"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[11].id,children:r[11].value}),"\n",(0,n.jsx)(i.h3,{id:r[12].id,children:r[12].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," The inspector scans ",(0,n.jsx)(i.code,{children:"getApplicationDocumentsDirectory()"})," and ",(0,n.jsx)(i.code,{children:"getDatabasesPath()"})," for ",(0,n.jsx)(i.code,{children:".db"})," and ",(0,n.jsx)(i.code,{children:".sqlite"})," files. If your database is stored elsewhere, you can implement a custom ",(0,n.jsx)(i.code,{children:"DatabaseProvider"}),"."]}),"\n",(0,n.jsxs)(i.p,{children:["检查器扫描 ",(0,n.jsx)(i.code,{children:"getApplicationDocumentsDirectory()"})," 和 ",(0,n.jsx)(i.code,{children:"getDatabasesPath()"})," 目录中的 ",(0,n.jsx)(i.code,{children:".db"})," 和 ",(0,n.jsx)(i.code,{children:".sqlite"})," 文件。如果你的数据库存储在其他位置,可以实现自定义 ",(0,n.jsx)(i.code,{children:"DatabaseProvider"}),"。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[13].id,children:r[13].value}),"\n",(0,n.jsx)(i.h3,{id:r[14].id,children:r[14].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," This is an expected behavior. When using ",(0,n.jsx)(i.code,{children:"flutter run"})," to debug via PC, the flutter tool sets up port forwarding between PC and device via ",(0,n.jsx)(i.code,{children:"adb reverse"}),", allowing PC-side DevTools to access the device’s VM Service. However, ",(0,n.jsx)(i.code,{children:"Service.getInfo()"})," returns a ",(0,n.jsx)(i.code,{children:"serverUri"})," from the PC’s perspective; when the app process internally accesses ",(0,n.jsx)(i.code,{children:"127.0.0.1:PC_port"}),", the device doesn’t have that port listening locally, resulting in ",(0,n.jsx)(i.code,{children:"Connection refused"})," and VM Service showing OFF."]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"这是预期行为。"})," 使用 ",(0,n.jsx)(i.code,{children:"flutter run"})," 连接 PC 调试时,flutter tool 会通过 ",(0,n.jsx)(i.code,{children:"adb reverse"})," 在 PC 和设备之间做端口转发,让 PC 上的 DevTools 能访问设备的 VM Service。但应用进程内部 ",(0,n.jsx)(i.code,{children:"Service.getInfo()"})," 返回的 ",(0,n.jsx)(i.code,{children:"serverUri"})," 是 PC 视角的端口,应用进程访问 ",(0,n.jsx)(i.code,{children:"127.0.0.1:PC端口"})," 时设备本地并没有监听该端口,导致 Connection refused,VM Service 显示 OFF。"]}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"Workarounds / 解决方案:"})}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Open the debug app directly without PC connection — VM Service works normally / 直接打开 debug 应用(不连 PC),VM Service 正常工作"}),"\n",(0,n.jsx)(i.li,{children:"Use Native memory data (Android PSS / iOS physicalFootprint) as fallback — always available / 使用 Native 内存数据(Android PSS / iOS physicalFootprint)作为降级,始终可用"}),"\n",(0,n.jsx)(i.li,{children:"Process RSS is always available regardless of VM Service / 进程 RSS 无论 VM Service 状态都可用"}),"\n"]}),"\n",(0,n.jsxs)(i.p,{children:["See ",(0,n.jsx)(i.a,{href:"Memory-Viewer#vm-service-availability--vm-service-%E5%8F%AF%E7%94%A8%E6%80%A7",children:"Memory Viewer > VM Service Availability"})," for details."]}),"\n",(0,n.jsxs)(i.p,{children:["详见 ",(0,n.jsx)(i.a,{href:"Memory-Viewer#vm-service-availability--vm-service-%E5%8F%AF%E7%94%A8%E6%80%A7",children:"Memory Viewer > VM Service 可用性"}),"。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[15].id,children:r[15].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Memory monitoring is ",(0,n.jsx)(i.strong,{children:"off by default"})," (since v1.1.0). When disabled, all timers are stopped and VM Service connection is cleared, leaving zero overhead. When enabled, refresh intervals are optimized:"]}),"\n",(0,n.jsxs)(i.p,{children:["内存监控",(0,n.jsx)(i.strong,{children:"默认关闭"}),"(v1.1.0 起)。关闭时所有定时器停止、VM Service 连接清空,零开销。开启时刷新间隔已优化:"]}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Process RSS / Dart Heap: 500ms"}),"\n",(0,n.jsx)(i.li,{children:"Native Memory: 3000ms"}),"\n",(0,n.jsx)(i.li,{children:"Storage Stats: 3000ms"}),"\n",(0,n.jsx)(i.li,{children:"Leak Detection: 2000ms"}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"You can toggle the switch at the top of the Memory panel anytime."}),"\n",(0,n.jsx)(i.p,{children:"可随时在 Memory 面板顶部切换开关。"}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[16].id,children:r[16].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," The current implementation uses ",(0,n.jsx)(i.code,{children:"WeakReference"})," + ",(0,n.jsx)(i.code,{children:"Finalizer"}),", which requires calling ",(0,n.jsx)(i.code,{children:"trackObject()"})," to register objects for tracking. This is a mild invasion (one line of code per tracked object)."]}),"\n",(0,n.jsxs)(i.p,{children:["当前实现使用 ",(0,n.jsx)(i.code,{children:"WeakReference"})," + ",(0,n.jsx)(i.code,{children:"Finalizer"}),",需要调用 ",(0,n.jsx)(i.code,{children:"trackObject()"})," 注册要追踪的对象。这是轻度侵入(每个追踪对象一行代码)。"]}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"Trade-offs / 取舍:"})}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Pros"}),": 100% reliable, doesn’t depend on VM Service, works in release mode / 100% 可靠,不依赖 VM Service,release 模式也可用"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Cons"}),": Requires user to know which objects to track / 需要用户知道要追踪哪些对象"]}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"A zero-invasion Heap Snapshot comparison feature (based on VM Service) is technically possible but would strongly depend on VM Service availability (unavailable when debugging via PC), with significant performance overhead. Not currently implemented."}),"\n",(0,n.jsx)(i.p,{children:"零侵入的 Heap Snapshot 对比功能(基于 VM Service)技术上可行,但会强依赖 VM Service 可用性(PC 调试时不可用),且有显著性能开销。目前未实现。"}),"\n",(0,n.jsxs)(i.p,{children:["See ",(0,n.jsx)(i.a,{href:"Memory-Viewer#6-memory-leak-detection--%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E6%A3%80%E6%B5%8B",children:"Memory Viewer > Memory Leak Detection"})," for API details."]}),"\n",(0,n.jsxs)(i.p,{children:["详见 ",(0,n.jsx)(i.a,{href:"Memory-Viewer#6-memory-leak-detection--%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E6%A3%80%E6%B5%8B",children:"Memory Viewer > 内存泄漏检测"})," 了解 API 详情。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[17].id,children:r[17].value}),"\n",(0,n.jsx)(i.h3,{id:r[18].id,children:r[18].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," This was a bug fixed in v1.2.0. The root cause was that ",(0,n.jsx)(i.code,{children:"_recentFrameTimestamps.add(now)"})," was placed outside the for-loop in ",(0,n.jsx)(i.code,{children:"_onFrameTimings"}),"; since Flutter engine’s ",(0,n.jsx)(i.code,{children:"addTimingsCallback"})," is ",(0,n.jsx)(i.strong,{children:"batched"})," (may return multiple frames per call), only one timestamp was recorded per batch, undercounting FPS by 6-10x. Upgrade to ",(0,n.jsx)(i.code,{children:"^1.2.0"})," to fix this."]}),"\n",(0,n.jsxs)(i.p,{children:["这是 v1.2.0 已修复的 bug。根因是 ",(0,n.jsx)(i.code,{children:"_onFrameTimings"})," 中 ",(0,n.jsx)(i.code,{children:"_recentFrameTimestamps.add(now)"})," 在 for 循环外;Flutter 引擎的 ",(0,n.jsx)(i.code,{children:"addTimingsCallback"})," 是",(0,n.jsx)(i.strong,{children:"批量回调"}),"(一次可能返回多帧),但每批只记录 1 个时间戳,导致 FPS 计算偏低 6-10 倍。升级到 ",(0,n.jsx)(i.code,{children:"^1.2.0"})," 即可修复。"]}),"\n",(0,n.jsxs)(i.p,{children:["See ",(0,n.jsx)(i.a,{href:"FPS-Viewer",children:"FPS Viewer"})," for details."]}),"\n",(0,n.jsxs)(i.p,{children:["详见 ",(0,n.jsx)(i.a,{href:"FPS-Viewer",children:"FPS Viewer"}),"。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[19].id,children:r[19].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," FPS monitoring is ",(0,n.jsx)(i.strong,{children:"off by default"})," (since v1.2.0). When disabled, no frame timings callbacks and no timers — zero overhead. When enabled, it only processes lightweight frame timing data with bounded history (60 trend points, up to 3600 frame records). You can toggle the switch at the top of the FPS panel anytime."]}),"\n",(0,n.jsxs)(i.p,{children:["FPS 监控",(0,n.jsx)(i.strong,{children:"默认关闭"}),"(v1.2.0 起)。关闭时无帧回调、无定时器——零开销。开启时仅处理轻量帧时序数据,历史有界(趋势 60 个点,帧记录最多 3600 条)。可随时在 FPS 面板顶部切换开关。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[20].id,children:r[20].value}),"\n",(0,n.jsx)(i.h3,{id:r[21].id,children:r[21].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," This was fixed in v1.0.6. The floating button and panel are rendered via ",(0,n.jsx)(i.code,{children:"Overlay"}),", which is independent of the page layout and not affected by keyboard."]}),"\n",(0,n.jsxs)(i.p,{children:["此问题已在 v1.0.6 修复。悬浮按钮和面板通过 ",(0,n.jsx)(i.code,{children:"Overlay"})," 渲染,独立于页面布局,不受键盘影响。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[22].id,children:r[22].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Each viewer has its own search bar at the top. Database viewer has two-level search: global search (database list) and in-database search (table names + all column data)."]}),"\n",(0,n.jsx)(i.p,{children:"每个查看器顶部都有搜索栏。数据库查看器有双层搜索:全局搜索(数据库列表)和数据库内搜索(表名 + 所有列数据)。"}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[23].id,children:r[23].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," This was a bug fixed in v1.2.0. The root cause was that ",(0,n.jsx)(i.code,{children:"FpsService.notifyListeners()"})," triggered Overlay rebuild; combined with the original Overlay lookup failure (using ",(0,n.jsx)(i.code,{children:"Overlay.of(context, rootOverlay: true)"})," which returned ",(0,n.jsx)(i.code,{children:"null"}),"), the button State was destroyed and could not recover. Upgrade to ",(0,n.jsx)(i.code,{children:"^1.2.0"})," which uses ",(0,n.jsx)(i.code,{children:"navigatorState.overlay"})," instead."]}),"\n",(0,n.jsxs)(i.p,{children:["这是 v1.2.0 已修复的 bug。根因是 ",(0,n.jsx)(i.code,{children:"FpsService.notifyListeners()"})," 触发 Overlay 重建,叠加原 Overlay 查找失败(使用 ",(0,n.jsx)(i.code,{children:"Overlay.of(context, rootOverlay: true)"})," 返回 ",(0,n.jsx)(i.code,{children:"null"}),"),导致按钮 State 被销毁且无法恢复。升级到 ",(0,n.jsx)(i.code,{children:"^1.2.0"}),",改用 ",(0,n.jsx)(i.code,{children:"navigatorState.overlay"})," 即可。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[24].id,children:r[24].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Since v1.2.0, when you drag the button near a screen edge and release, it auto-docks and tucks into the edge, leaving only a 24px peek visible. The icon becomes a directional chevron hinting you can tap to pull it out:"]}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Tap the peek"})," → smoothly pulls out to fully visible (panel NOT opened, avoids accidental open) / 平滑拉出到完全可见(不打开面板,避免误触)"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Tap when fully visible"})," → opens the inspector panel / 打开检查器面板"]}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"This design avoids conflicts with system back gestures (Android/iOS edge swipe to go back) when pulling out from the docked state."}),"\n",(0,n.jsx)(i.p,{children:"v1.2.0 起,拖动按钮靠近屏幕边缘松手即自动吸附并”收入”边缘,仅露 24px。图标变为方向箭头,提示可点击拉出:"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"点击露出部分"})," → 平滑拉出到完全可见(不打开面板,避免误触)"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"完全可见时点击"})," → 打开检查器面板"]}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"此设计避免了从吸附态拖出时与系统返回手势(Android/iOS 边缘右滑退出)的冲突。"}),"\n",(0,n.jsxs)(i.p,{children:["See ",(0,n.jsx)(i.a,{href:"Usage#edge-docking-since-v120--%E8%BE%B9%E7%BC%98%E5%90%B8%E9%99%84v120-%E8%B5%B7",children:"Usage > Edge Docking"})," for details."]}),"\n",(0,n.jsxs)(i.p,{children:["详见 ",(0,n.jsx)(i.a,{href:"Usage#edge-docking-since-v120--%E8%BE%B9%E7%BC%98%E5%90%B8%E9%99%84v120-%E8%B5%B7",children:"Usage > 边缘吸附"}),"。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[25].id,children:r[25].value}),"\n",(0,n.jsx)(i.h3,{id:r[26].id,children:r[26].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Yes. This project is licensed under ",(0,n.jsx)(i.strong,{children:"MPL-2.0"}),". You can use it in closed-source commercial apps — if you use it ",(0,n.jsx)(i.strong,{children:"unmodified"}),", no source disclosure is required. If you ",(0,n.jsx)(i.strong,{children:"modify"})," the plugin’s source files, those modified files must be published in source form under MPL-2.0 (your own app still does not need to be open-sourced)."]}),"\n",(0,n.jsxs)(i.p,{children:["可以。本项目采用 ",(0,n.jsx)(i.strong,{children:"MPL-2.0"})," 许可证,可用于闭源商业 App——",(0,n.jsx)(i.strong,{children:"未修改"}),"直接使用时无需公开任何源码;若",(0,n.jsx)(i.strong,{children:"修改"}),"了插件源文件,被修改的文件必须以 MPL-2.0 公开源码(你自己的 App 仍无需开源)。"]}),"\n",(0,n.jsx)(i.h3,{id:r[27].id,children:r[27].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," This plugin is provided “as is”, without warranty of any kind. The author assumes no responsibility or liability for the functionality, security, or any consequences arising from the use of modified versions or derivative projects."]}),"\n",(0,n.jsx)(i.p,{children:"本插件按”原样”提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及任何使用后果承担责任。"})]})},"/FAQ",{filePath:"pages/FAQ.md",timestamp:1787178796e3,pageMap:t.v,frontMatter:{},title:"FAQ / 常见问题"},"undefined"==typeof RemoteContent?o:RemoteContent.useTOC)},7080:function(e,r,i){"use strict";i.d(r,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=9238)}),_N_E=e.O()}]); \ No newline at end of file +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[210],{9238:function(e,r,i){(window.__NEXT_P=window.__NEXT_P||[]).push(["/FAQ",function(){return i(5797)}])},5797:function(e,r,i){"use strict";i.r(r),i.d(r,{useTOC:function(){return o}});var n=i(5893),s=i(7812),t=i(7080),d=i(8925);function o(e){return[{value:"General / 通用",id:"general--通用",depth:2},{value:"Q: Does the inspector affect production builds? / 检查器会影响生产构建吗?",id:"q-does-the-inspector-affect-production-builds--检查器会影响生产构建吗",depth:3},{value:"Q: What platforms are supported? / 支持哪些平台?",id:"q-what-platforms-are-supported--支持哪些平台",depth:3},{value:"Q: What Flutter/Dart versions are required? / 需要什么 Flutter/Dart 版本?",id:"q-what-flutterdart-versions-are-required--需要什么-flutterdart-版本",depth:3},{value:"Network / 网络",id:"network--网络",depth:2},{value:"Q: Do I need to manually add interceptors for Dio? / 需要为 Dio 手动添加拦截器吗?",id:"q-do-i-need-to-manually-add-interceptors-for-dio--需要为-dio-手动添加拦截器吗",depth:3},{value:"Q: Why do I see duplicate Dio requests? / 为什么看到重复的 Dio 请求?",id:"q-why-do-i-see-duplicate-dio-requests--为什么看到重复的-dio-请求",depth:3},{value:"Q: Can I modify requests during testing? / 测试时可以修改请求吗?",id:"q-can-i-modify-requests-during-testing--测试时可以修改请求吗",depth:3},{value:"Logging / 日志",id:"logging--日志",depth:2},{value:"Q: Can I use my existing logging library? / 可以使用现有的日志库吗?",id:"q-can-i-use-my-existing-logging-library--可以使用现有的日志库吗",depth:3},{value:"Q: How to sync inspector logs to my logger? / 如何将检查器日志同步到我的日志库?",id:"q-how-to-sync-inspector-logs-to-my-logger--如何将检查器日志同步到我的日志库",depth:3},{value:"Database / 数据库",id:"database--数据库",depth:2},{value:"Q: Why don’t I see any databases? / 为什么看不到数据库?",id:"q-why-dont-i-see-any-databases--为什么看不到数据库",depth:3},{value:"Memory / 内存",id:"memory--内存",depth:2},{value:"Q: Why does Dart Heap show “N/A” when debugging via PC? / 通过 PC 调试时为什么 Dart Heap 显示 “N/A”?",id:"q-why-does-dart-heap-show-na-when-debugging-via-pc--通过-pc-调试时为什么-dart-heap-显示-na",depth:3},{value:"Q: Does memory monitoring affect performance? / 内存监控会影响性能吗?",id:"q-does-memory-monitoring-affect-performance--内存监控会影响性能吗",depth:3},{value:"Q: Does leak detection require code modification? / 泄漏检测需要侵入代码吗?",id:"q-does-leak-detection-require-code-modification--泄漏检测需要侵入代码吗",depth:3},{value:"FPS / 帧率",id:"fps--帧率",depth:2},{value:"Q: Why does FPS show a very low value (e.g. 9-10 FPS)? / 为什么 FPS 显示很低(如 9-10)?",id:"q-why-does-fps-show-a-very-low-value-eg-9-10-fps--为什么-fps-显示很低如-9-10",depth:3},{value:"Q: Does FPS monitoring affect performance? / FPS 监控会影响性能吗?",id:"q-does-fps-monitoring-affect-performance--fps-监控会影响性能吗",depth:3},{value:"UI / 界面",id:"ui--界面",depth:2},{value:"Q: The inspector panel gets pushed up when keyboard appears. / 键盘弹出时检查器面板被顶起来了。",id:"q-the-inspector-panel-gets-pushed-up-when-keyboard-appears--键盘弹出时检查器面板被顶起来了",depth:3},{value:"Q: How to use search? / 如何使用搜索?",id:"q-how-to-use-search--如何使用搜索",depth:3},{value:"Q: The floating button disappeared / can’t be opened after enabling FPS. / 开启 FPS 后悬浮按钮消失/无法打开。",id:"q-the-floating-button-disappeared--cant-be-opened-after-enabling-fps--开启-fps-后悬浮按钮消失无法打开",depth:3},{value:"Q: How does the floating button edge docking work? / 悬浮按钮的边缘吸附怎么用?",id:"q-how-does-the-floating-button-edge-docking-work--悬浮按钮的边缘吸附怎么用",depth:3},{value:"License / 许可证",id:"license--许可证",depth:2},{value:"Q: Can I use this in a commercial project? / 可以在商业项目中使用吗?",id:"q-can-i-use-this-in-a-commercial-project--可以在商业项目中使用吗",depth:3},{value:"Q: Are you liable for issues in modified versions? / 修改版出问题你们负责吗?",id:"q-are-you-liable-for-issues-in-modified-versions--修改版出问题你们负责吗",depth:3}]}r.default=(0,s.c)(function(e){let{toc:r=o(e)}=e,i={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",hr:"hr",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",ul:"ul",...(0,d.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(i.h1,{children:"FAQ / 常见问题"}),"\n",(0,n.jsx)(i.h2,{id:r[0].id,children:r[0].value}),"\n",(0,n.jsx)(i.h3,{id:r[1].id,children:r[1].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," No. The inspector is automatically disabled in release mode via ",(0,n.jsx)(i.code,{children:"kReleaseMode"}),". Flutter’s tree-shaking removes all inspector-related code from production builds. You don’t need to remove any code."]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"不会。"})," 检查器在 release 模式下通过 ",(0,n.jsx)(i.code,{children:"kReleaseMode"})," 自动禁用。Flutter 的 tree-shaking 会移除所有检查器相关代码,无需手动移除。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[2].id,children:r[2].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Android and iOS."]}),"\n",(0,n.jsx)(i.p,{children:"支持 Android 和 iOS。"}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[3].id,children:r[3].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Flutter >= 3.3.0, Dart SDK >= 3.11.0 < 4.0.0."]}),"\n",(0,n.jsx)(i.p,{children:"Flutter >= 3.3.0,Dart SDK >= 3.11.0 < 4.0.0。"}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[4].id,children:r[4].value}),"\n",(0,n.jsx)(i.h3,{id:r[5].id,children:r[5].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," No. Dio uses ",(0,n.jsx)(i.code,{children:"IOHttpClientAdapter"})," internally, which uses ",(0,n.jsx)(i.code,{children:"dart:io"}),"’s ",(0,n.jsx)(i.code,{children:"HttpClient"}),". The inspector captures all requests via ",(0,n.jsx)(i.code,{children:"HttpOverrides"})," automatically, making it truly zero-invasion for both http package and Dio."]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"不需要。"})," Dio 内部使用 ",(0,n.jsx)(i.code,{children:"IOHttpClientAdapter"}),",底层使用 ",(0,n.jsx)(i.code,{children:"dart:io"})," 的 ",(0,n.jsx)(i.code,{children:"HttpClient"}),"。检查器通过 ",(0,n.jsx)(i.code,{children:"HttpOverrides"})," 自动捕获所有请求,对 http 包和 Dio 都是真正的零侵入。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[6].id,children:r[6].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," If you use both ",(0,n.jsx)(i.code,{children:"InspectorDioInterceptor"})," and the auto-capture (HttpOverrides), Dio requests will be recorded twice. Simply remove the manual ",(0,n.jsx)(i.code,{children:"InspectorDioInterceptor"})," — auto-capture handles everything."]}),"\n",(0,n.jsxs)(i.p,{children:["如果同时使用了 ",(0,n.jsx)(i.code,{children:"InspectorDioInterceptor"})," 和自动捕获(HttpOverrides),Dio 请求会被记录两次。移除手动 ",(0,n.jsx)(i.code,{children:"InspectorDioInterceptor"})," 即可,自动捕获会处理一切。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[7].id,children:r[7].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Yes (since v1.0.7). The inspector supports intercepting and modifying requests via rules. Open a request detail and tap the Interceptor icon to configure a rule. You can modify the ",(0,n.jsx)(i.strong,{children:"request body"})," and ",(0,n.jsx)(i.strong,{children:"request headers"})," only — response fields (status code, response body) are read-only (since v1.0.8). GET requests cannot be modified (no request body). The master toggle is on the Network list page; rules only apply when modification mode is enabled."]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"可以"}),"(v1.0.7 起)。检查器支持通过规则拦截并修改请求。打开请求详情,点击拦截器图标配置规则。仅可修改",(0,n.jsx)(i.strong,{children:"请求体"}),"和",(0,n.jsx)(i.strong,{children:"请求头"}),"——响应字段(状态码、响应体)只读(v1.0.8 起)。GET 请求不可修改(无请求体)。总开关在 Network 列表页,规则仅在启用修改模式时生效。"]}),"\n",(0,n.jsxs)(i.p,{children:["See ",(0,n.jsx)(i.a,{href:"Network-Inspector#request-interceptor--%E8%AF%B7%E6%B1%82%E6%8B%A6%E6%88%AA%E4%BF%AE%E6%94%B9",children:"Network Inspector > Request Interceptor"})," for details."]}),"\n",(0,n.jsxs)(i.p,{children:["详见 ",(0,n.jsx)(i.a,{href:"Network-Inspector#request-interceptor--%E8%AF%B7%E6%B1%82%E6%8B%A6%E6%88%AA%E4%BF%AE%E6%94%B9",children:"Network Inspector > 请求拦截修改"}),"。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[8].id,children:r[8].value}),"\n",(0,n.jsx)(i.h3,{id:r[9].id,children:r[9].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Yes! The inspector automatically captures logs from any library that uses ",(0,n.jsx)(i.code,{children:"print()"})," or ",(0,n.jsx)(i.code,{children:"debugPrint()"}),". No configuration needed. Third-party logs are categorized as ",(0,n.jsx)(i.strong,{children:"Info level"}),"."]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"可以!"})," 检查器会自动捕获任何使用 ",(0,n.jsx)(i.code,{children:"print()"})," 或 ",(0,n.jsx)(i.code,{children:"debugPrint()"})," 的日志库的日志,无需配置。第三方日志统一归类为 ",(0,n.jsx)(i.strong,{children:"Info 级别"}),"。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[10].id,children:r[10].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Use the ",(0,n.jsx)(i.code,{children:"onLogCaptured"})," callback:"]}),"\n",(0,n.jsxs)(i.p,{children:["使用 ",(0,n.jsx)(i.code,{children:"onLogCaptured"})," 回调:"]}),"\n",(0,n.jsx)(i.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(i.code,{children:[(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.onLogCaptured "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" (entry) {"})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" yourLogger."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"log"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(entry.message);"})]}),"\n",(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"};"})})]})}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"Warning"}),": Do NOT call ",(0,n.jsx)(i.code,{children:"print()"})," or logging methods inside this callback, as it will cause infinite recursion.\n",(0,n.jsx)(i.strong,{children:"警告"}),":不要在此回调中调用 ",(0,n.jsx)(i.code,{children:"print()"})," 或日志方法,否则会导致无限递归。"]}),"\n"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[11].id,children:r[11].value}),"\n",(0,n.jsx)(i.h3,{id:r[12].id,children:r[12].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," The inspector scans ",(0,n.jsx)(i.code,{children:"getApplicationDocumentsDirectory()"})," and ",(0,n.jsx)(i.code,{children:"getDatabasesPath()"})," for ",(0,n.jsx)(i.code,{children:".db"})," and ",(0,n.jsx)(i.code,{children:".sqlite"})," files. If your database is stored elsewhere, you can implement a custom ",(0,n.jsx)(i.code,{children:"DatabaseProvider"}),"."]}),"\n",(0,n.jsxs)(i.p,{children:["检查器扫描 ",(0,n.jsx)(i.code,{children:"getApplicationDocumentsDirectory()"})," 和 ",(0,n.jsx)(i.code,{children:"getDatabasesPath()"})," 目录中的 ",(0,n.jsx)(i.code,{children:".db"})," 和 ",(0,n.jsx)(i.code,{children:".sqlite"})," 文件。如果你的数据库存储在其他位置,可以实现自定义 ",(0,n.jsx)(i.code,{children:"DatabaseProvider"}),"。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[13].id,children:r[13].value}),"\n",(0,n.jsx)(i.h3,{id:r[14].id,children:r[14].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," This is an expected behavior. When using ",(0,n.jsx)(i.code,{children:"flutter run"})," to debug via PC, the flutter tool sets up port forwarding between PC and device via ",(0,n.jsx)(i.code,{children:"adb reverse"}),", allowing PC-side DevTools to access the device’s VM Service. However, ",(0,n.jsx)(i.code,{children:"Service.getInfo()"})," returns a ",(0,n.jsx)(i.code,{children:"serverUri"})," from the PC’s perspective; when the app process internally accesses ",(0,n.jsx)(i.code,{children:"127.0.0.1:PC_port"}),", the device doesn’t have that port listening locally, resulting in ",(0,n.jsx)(i.code,{children:"Connection refused"})," and VM Service showing OFF."]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"这是预期行为。"})," 使用 ",(0,n.jsx)(i.code,{children:"flutter run"})," 连接 PC 调试时,flutter tool 会通过 ",(0,n.jsx)(i.code,{children:"adb reverse"})," 在 PC 和设备之间做端口转发,让 PC 上的 DevTools 能访问设备的 VM Service。但应用进程内部 ",(0,n.jsx)(i.code,{children:"Service.getInfo()"})," 返回的 ",(0,n.jsx)(i.code,{children:"serverUri"})," 是 PC 视角的端口,应用进程访问 ",(0,n.jsx)(i.code,{children:"127.0.0.1:PC端口"})," 时设备本地并没有监听该端口,导致 Connection refused,VM Service 显示 OFF。"]}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"Workarounds / 解决方案:"})}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Open the debug app directly without PC connection — VM Service works normally / 直接打开 debug 应用(不连 PC),VM Service 正常工作"}),"\n",(0,n.jsx)(i.li,{children:"Use Native memory data (Android PSS / iOS physicalFootprint) as fallback — always available / 使用 Native 内存数据(Android PSS / iOS physicalFootprint)作为降级,始终可用"}),"\n",(0,n.jsx)(i.li,{children:"Process RSS is always available regardless of VM Service / 进程 RSS 无论 VM Service 状态都可用"}),"\n"]}),"\n",(0,n.jsxs)(i.p,{children:["See ",(0,n.jsx)(i.a,{href:"Memory-Viewer#vm-service-availability--vm-service-%E5%8F%AF%E7%94%A8%E6%80%A7",children:"Memory Viewer > VM Service Availability"})," for details."]}),"\n",(0,n.jsxs)(i.p,{children:["详见 ",(0,n.jsx)(i.a,{href:"Memory-Viewer#vm-service-availability--vm-service-%E5%8F%AF%E7%94%A8%E6%80%A7",children:"Memory Viewer > VM Service 可用性"}),"。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[15].id,children:r[15].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Memory monitoring is ",(0,n.jsx)(i.strong,{children:"off by default"})," (since v1.1.0). When disabled, all timers are stopped and VM Service connection is cleared, leaving zero overhead. When enabled, refresh intervals are optimized:"]}),"\n",(0,n.jsxs)(i.p,{children:["内存监控",(0,n.jsx)(i.strong,{children:"默认关闭"}),"(v1.1.0 起)。关闭时所有定时器停止、VM Service 连接清空,零开销。开启时刷新间隔已优化:"]}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Process RSS / Dart Heap: 500ms"}),"\n",(0,n.jsx)(i.li,{children:"Native Memory: 3000ms"}),"\n",(0,n.jsx)(i.li,{children:"Storage Stats: 3000ms"}),"\n",(0,n.jsx)(i.li,{children:"Leak Detection: 2000ms"}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"You can toggle the switch at the top of the Memory panel anytime."}),"\n",(0,n.jsx)(i.p,{children:"可随时在 Memory 面板顶部切换开关。"}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[16].id,children:r[16].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," The current implementation uses ",(0,n.jsx)(i.code,{children:"WeakReference"})," + ",(0,n.jsx)(i.code,{children:"Finalizer"}),", which requires calling ",(0,n.jsx)(i.code,{children:"trackObject()"})," to register objects for tracking. This is a mild invasion (one line of code per tracked object)."]}),"\n",(0,n.jsxs)(i.p,{children:["当前实现使用 ",(0,n.jsx)(i.code,{children:"WeakReference"})," + ",(0,n.jsx)(i.code,{children:"Finalizer"}),",需要调用 ",(0,n.jsx)(i.code,{children:"trackObject()"})," 注册要追踪的对象。这是轻度侵入(每个追踪对象一行代码)。"]}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"Trade-offs / 取舍:"})}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Pros"}),": 100% reliable, doesn’t depend on VM Service, works in release mode / 100% 可靠,不依赖 VM Service,release 模式也可用"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Cons"}),": Requires user to know which objects to track / 需要用户知道要追踪哪些对象"]}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"A zero-invasion Heap Snapshot comparison feature (based on VM Service) is technically possible but would strongly depend on VM Service availability (unavailable when debugging via PC), with significant performance overhead. Not currently implemented."}),"\n",(0,n.jsx)(i.p,{children:"零侵入的 Heap Snapshot 对比功能(基于 VM Service)技术上可行,但会强依赖 VM Service 可用性(PC 调试时不可用),且有显著性能开销。目前未实现。"}),"\n",(0,n.jsxs)(i.p,{children:["See ",(0,n.jsx)(i.a,{href:"Memory-Viewer#6-memory-leak-detection--%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E6%A3%80%E6%B5%8B",children:"Memory Viewer > Memory Leak Detection"})," for API details."]}),"\n",(0,n.jsxs)(i.p,{children:["详见 ",(0,n.jsx)(i.a,{href:"Memory-Viewer#6-memory-leak-detection--%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E6%A3%80%E6%B5%8B",children:"Memory Viewer > 内存泄漏检测"})," 了解 API 详情。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[17].id,children:r[17].value}),"\n",(0,n.jsx)(i.h3,{id:r[18].id,children:r[18].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," This was a bug fixed in v1.2.0. The root cause was that ",(0,n.jsx)(i.code,{children:"_recentFrameTimestamps.add(now)"})," was placed outside the for-loop in ",(0,n.jsx)(i.code,{children:"_onFrameTimings"}),"; since Flutter engine’s ",(0,n.jsx)(i.code,{children:"addTimingsCallback"})," is ",(0,n.jsx)(i.strong,{children:"batched"})," (may return multiple frames per call), only one timestamp was recorded per batch, undercounting FPS by 6-10x. Upgrade to ",(0,n.jsx)(i.code,{children:"^1.2.0"})," to fix this."]}),"\n",(0,n.jsxs)(i.p,{children:["这是 v1.2.0 已修复的 bug。根因是 ",(0,n.jsx)(i.code,{children:"_onFrameTimings"})," 中 ",(0,n.jsx)(i.code,{children:"_recentFrameTimestamps.add(now)"})," 在 for 循环外;Flutter 引擎的 ",(0,n.jsx)(i.code,{children:"addTimingsCallback"})," 是",(0,n.jsx)(i.strong,{children:"批量回调"}),"(一次可能返回多帧),但每批只记录 1 个时间戳,导致 FPS 计算偏低 6-10 倍。升级到 ",(0,n.jsx)(i.code,{children:"^1.2.0"})," 即可修复。"]}),"\n",(0,n.jsxs)(i.p,{children:["See ",(0,n.jsx)(i.a,{href:"FPS-Viewer",children:"FPS Viewer"})," for details."]}),"\n",(0,n.jsxs)(i.p,{children:["详见 ",(0,n.jsx)(i.a,{href:"FPS-Viewer",children:"FPS Viewer"}),"。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[19].id,children:r[19].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," FPS monitoring is ",(0,n.jsx)(i.strong,{children:"off by default"})," (since v1.2.0). When disabled, no frame timings callbacks and no timers — zero overhead. When enabled, it only processes lightweight frame timing data with bounded history (60 trend points, up to 3600 frame records). You can toggle the switch at the top of the FPS panel anytime."]}),"\n",(0,n.jsxs)(i.p,{children:["FPS 监控",(0,n.jsx)(i.strong,{children:"默认关闭"}),"(v1.2.0 起)。关闭时无帧回调、无定时器——零开销。开启时仅处理轻量帧时序数据,历史有界(趋势 60 个点,帧记录最多 3600 条)。可随时在 FPS 面板顶部切换开关。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[20].id,children:r[20].value}),"\n",(0,n.jsx)(i.h3,{id:r[21].id,children:r[21].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," This was fixed in v1.0.6. The floating button and panel are rendered via ",(0,n.jsx)(i.code,{children:"Overlay"}),", which is independent of the page layout and not affected by keyboard."]}),"\n",(0,n.jsxs)(i.p,{children:["此问题已在 v1.0.6 修复。悬浮按钮和面板通过 ",(0,n.jsx)(i.code,{children:"Overlay"})," 渲染,独立于页面布局,不受键盘影响。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[22].id,children:r[22].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Each viewer has its own search bar at the top. Database viewer has two-level search: global search (database list) and in-database search (table names + all column data)."]}),"\n",(0,n.jsx)(i.p,{children:"每个查看器顶部都有搜索栏。数据库查看器有双层搜索:全局搜索(数据库列表)和数据库内搜索(表名 + 所有列数据)。"}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[23].id,children:r[23].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," This was a bug fixed in v1.2.0. The root cause was that ",(0,n.jsx)(i.code,{children:"FpsService.notifyListeners()"})," triggered Overlay rebuild; combined with the original Overlay lookup failure (using ",(0,n.jsx)(i.code,{children:"Overlay.of(context, rootOverlay: true)"})," which returned ",(0,n.jsx)(i.code,{children:"null"}),"), the button State was destroyed and could not recover. Upgrade to ",(0,n.jsx)(i.code,{children:"^1.2.0"})," which uses ",(0,n.jsx)(i.code,{children:"navigatorState.overlay"})," instead."]}),"\n",(0,n.jsxs)(i.p,{children:["这是 v1.2.0 已修复的 bug。根因是 ",(0,n.jsx)(i.code,{children:"FpsService.notifyListeners()"})," 触发 Overlay 重建,叠加原 Overlay 查找失败(使用 ",(0,n.jsx)(i.code,{children:"Overlay.of(context, rootOverlay: true)"})," 返回 ",(0,n.jsx)(i.code,{children:"null"}),"),导致按钮 State 被销毁且无法恢复。升级到 ",(0,n.jsx)(i.code,{children:"^1.2.0"}),",改用 ",(0,n.jsx)(i.code,{children:"navigatorState.overlay"})," 即可。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h3,{id:r[24].id,children:r[24].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Since v1.2.0, when you drag the button near a screen edge and release, it auto-docks and tucks into the edge, leaving only a 24px peek visible. The icon becomes a directional chevron hinting you can tap to pull it out:"]}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Tap the peek"})," → smoothly pulls out to fully visible (panel NOT opened, avoids accidental open) / 平滑拉出到完全可见(不打开面板,避免误触)"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Tap when fully visible"})," → opens the inspector panel / 打开检查器面板"]}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"This design avoids conflicts with system back gestures (Android/iOS edge swipe to go back) when pulling out from the docked state."}),"\n",(0,n.jsx)(i.p,{children:"v1.2.0 起,拖动按钮靠近屏幕边缘松手即自动吸附并”收入”边缘,仅露 24px。图标变为方向箭头,提示可点击拉出:"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"点击露出部分"})," → 平滑拉出到完全可见(不打开面板,避免误触)"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"完全可见时点击"})," → 打开检查器面板"]}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"此设计避免了从吸附态拖出时与系统返回手势(Android/iOS 边缘右滑退出)的冲突。"}),"\n",(0,n.jsxs)(i.p,{children:["See ",(0,n.jsx)(i.a,{href:"Usage#edge-docking-since-v120--%E8%BE%B9%E7%BC%98%E5%90%B8%E9%99%84v120-%E8%B5%B7",children:"Usage > Edge Docking"})," for details."]}),"\n",(0,n.jsxs)(i.p,{children:["详见 ",(0,n.jsx)(i.a,{href:"Usage#edge-docking-since-v120--%E8%BE%B9%E7%BC%98%E5%90%B8%E9%99%84v120-%E8%B5%B7",children:"Usage > 边缘吸附"}),"。"]}),"\n",(0,n.jsx)(i.hr,{}),"\n",(0,n.jsx)(i.h2,{id:r[25].id,children:r[25].value}),"\n",(0,n.jsx)(i.h3,{id:r[26].id,children:r[26].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," Yes. This project is licensed under ",(0,n.jsx)(i.strong,{children:"MPL-2.0"}),". You can use it in closed-source commercial apps — if you use it ",(0,n.jsx)(i.strong,{children:"unmodified"}),", no source disclosure is required. If you ",(0,n.jsx)(i.strong,{children:"modify"})," the plugin’s source files, those modified files must be published in source form under MPL-2.0 (your own app still does not need to be open-sourced)."]}),"\n",(0,n.jsxs)(i.p,{children:["可以。本项目采用 ",(0,n.jsx)(i.strong,{children:"MPL-2.0"})," 许可证,可用于闭源商业 App——",(0,n.jsx)(i.strong,{children:"未修改"}),"直接使用时无需公开任何源码;若",(0,n.jsx)(i.strong,{children:"修改"}),"了插件源文件,被修改的文件必须以 MPL-2.0 公开源码(你自己的 App 仍无需开源)。"]}),"\n",(0,n.jsx)(i.h3,{id:r[27].id,children:r[27].value}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"A:"})," This plugin is provided “as is”, without warranty of any kind. The author assumes no responsibility or liability for the functionality, security, or any consequences arising from the use of modified versions or derivative projects."]}),"\n",(0,n.jsx)(i.p,{children:"本插件按”原样”提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及任何使用后果承担责任。"})]})},"/FAQ",{filePath:"pages/FAQ.md",timestamp:1787178796e3,pageMap:t.v,frontMatter:{},title:"FAQ / 常见问题"},"undefined"==typeof RemoteContent?o:RemoteContent.useTOC)},7080:function(e,r,i){"use strict";i.d(r,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=9238)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/FPS-Viewer-954be0ba8ec208c8.js b/docs/_next/static/chunks/pages/FPS-Viewer-954be0ba8ec208c8.js new file mode 100644 index 0000000..eea1fb1 --- /dev/null +++ b/docs/_next/static/chunks/pages/FPS-Viewer-954be0ba8ec208c8.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[229],{7839:function(e,n,i){(window.__NEXT_P=window.__NEXT_P||[]).push(["/FPS-Viewer",function(){return i(7548)}])},7548:function(e,n,i){"use strict";i.r(n),i.d(n,{useTOC:function(){return l}});var s=i(5893),r=i(7812),t=i(7080),d=i(8925);function l(e){return[{value:"Overview / 概览",id:"overview--概览",depth:2},{value:"Master Switch / 总开关",id:"master-switch--总开关",depth:2},{value:"Features / 功能",id:"features--功能",depth:2},{value:"1. Current Stats / 当前统计",id:"1-current-stats--当前统计",depth:3},{value:"2. FPS Trend Chart / FPS 趋势图",id:"2-fps-trend-chart--fps-趋势图",depth:3},{value:"3. Janky Frame List / 卡顿帧列表",id:"3-janky-frame-list--卡顿帧列表",depth:3},{value:"4. Reset / 重置",id:"4-reset--重置",depth:3},{value:"Main-thread Blocking Watchdog / 主线程阻塞看门狗",id:"main-thread-blocking-watchdog--主线程阻塞看门狗",depth:2},{value:"How It Works / 工作原理",id:"how-it-works--工作原理",depth:2},{value:"Programmatic Control (Optional) / 编程式控制(可选)",id:"programmatic-control-optional--编程式控制可选",depth:2},{value:"FpsService API / FpsService 接口",id:"fpsservice-api--fpsservice-接口",depth:2},{value:"Performance Considerations / 性能说明",id:"performance-considerations--性能说明",depth:2},{value:"Platform Support / 平台支持",id:"platform-support--平台支持",depth:2},{value:"Demo / 演示",id:"demo--演示",depth:2},{value:"Related / 相关",id:"related--相关",depth:2}]}n.default=(0,r.c)(function(e){let{toc:n=l(e)}=e,i={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(i.h1,{children:"FPS Viewer / FPS 监控面板"}),"\n",(0,s.jsx)(i.p,{children:"The FPS Viewer provides real-time frame performance analysis, including current FPS, jank rate, frame duration stats, and an FPS trend chart."}),"\n",(0,s.jsx)(i.p,{children:"FPS 监控面板提供实时帧性能分析,包括当前 FPS、卡顿率、帧耗时统计和 FPS 趋势图。"}),"\n",(0,s.jsxs)(i.blockquote,{children:["\n",(0,s.jsx)(i.p,{children:(0,s.jsx)(i.strong,{children:"Available since v1.2.0"})}),"\n",(0,s.jsx)(i.p,{children:(0,s.jsx)(i.strong,{children:"v1.2.0 起可用"})}),"\n"]}),"\n",(0,s.jsx)(i.h2,{id:n[0].id,children:n[0].value}),"\n",(0,s.jsx)(i.p,{children:"The FPS Viewer is integrated into the inspector panel. Tap the floating inspector button → “FPS” tab to access."}),"\n",(0,s.jsx)(i.p,{children:"FPS 监控面板集成在检查器面板中。点击悬浮检查器按钮 → “FPS” 标签即可访问。"}),"\n",(0,s.jsxs)(i.p,{children:[(0,s.jsx)(i.strong,{children:"⚠️ Important"}),": FPS monitoring is ",(0,s.jsx)(i.strong,{children:"off by default"}),". You must toggle on the switch at the top of the panel to start collecting data."]}),"\n",(0,s.jsxs)(i.p,{children:[(0,s.jsx)(i.strong,{children:"⚠️ 重要"}),":FPS 监控",(0,s.jsx)(i.strong,{children:"默认关闭"}),"。必须在面板顶部打开开关才会开始采集数据。"]}),"\n",(0,s.jsx)(i.h2,{id:n[1].id,children:n[1].value}),"\n",(0,s.jsx)(i.p,{children:"A switch at the top of the FPS panel controls whether monitoring is enabled."}),"\n",(0,s.jsx)(i.p,{children:"FPS 面板顶部有一个开关,控制是否启用监控。"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,s.jsxs)(i.table,{children:[(0,s.jsx)(i.thead,{children:(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.th,{children:"State"}),(0,s.jsx)(i.th,{children:"Behavior"})]})}),(0,s.jsxs)(i.tbody,{children:[(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.strong,{children:"OFF (default)"})}),(0,s.jsx)(i.td,{children:"No frame timings callbacks, no timer, zero overhead / 无帧回调、无定时器、零开销"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.strong,{children:"ON"})}),(0,s.jsxs)(i.td,{children:["Starts collecting frame data via ",(0,s.jsx)(i.code,{children:"WidgetsBinding.instance.addTimingsCallback"})," / 通过 ",(0,s.jsx)(i.code,{children:"addTimingsCallback"})," 开始采集帧数据"]})]})]})]}),"\n",(0,s.jsx)(i.p,{children:"When turned off, all callbacks and timers are cancelled, leaving no residual overhead."}),"\n",(0,s.jsx)(i.p,{children:"关闭时所有回调和定时器会被取消,不会残留开销。"}),"\n",(0,s.jsx)(i.h2,{id:n[2].id,children:n[2].value}),"\n",(0,s.jsx)(i.h3,{id:n[3].id,children:n[3].value}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Current FPS"})," — Updated every 500ms / 每 500ms 更新"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Jank Rate"})," — Percentage of janky frames (duration exceeds the per-frame budget, derived from the display refresh rate) / 卡顿帧占比(帧耗时超过由屏幕刷新率换算的逐帧预算)"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Total Frame Count"})," — All frames captured since start / 自启动以来的总帧数"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Total Janky Count"})," — All janky frames captured / 自启动以来的总卡顿帧数"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Last Frame Janky"})," — Whether the most recent frame was janky / 最近一帧是否卡顿"]}),"\n"]}),"\n",(0,s.jsx)(i.p,{children:"显示当前 FPS、卡顿率、总帧数、总卡顿帧数、最近一帧是否卡顿。"}),"\n",(0,s.jsx)(i.h3,{id:n[4].id,children:n[4].value}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"Real-time line chart with 30-second history window (60 data points \xd7 500ms)"}),"\n",(0,s.jsxs)(i.li,{children:["Y-axis dynamically scales to ",(0,s.jsx)(i.code,{children:"max(60, maxFps)"})," to avoid overflow when FPS spikes"]}),"\n",(0,s.jsx)(i.li,{children:"Janky frame region marked in red"}),"\n",(0,s.jsxs)(i.li,{children:["Time axis labels: ",(0,s.jsx)(i.code,{children:"-30s"})," / ",(0,s.jsx)(i.code,{children:"-15s"})," / ",(0,s.jsx)(i.code,{children:"Now"})]}),"\n"]}),"\n",(0,s.jsxs)(i.p,{children:["实时折线图,30 秒历史窗口(60 个数据点 \xd7 500ms)。Y 轴动态缩放为 ",(0,s.jsx)(i.code,{children:"max(60, maxFps)"})," 避免 FPS 飙升时溢出。卡顿区域以红色标记。"]}),"\n",(0,s.jsx)(i.h3,{id:n[5].id,children:n[5].value}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"Lists janky frames — duration exceeds the per-frame budget, derived from the display refresh rate (~16.7ms at 60Hz, ~8.3ms at 120Hz) / 列出卡顿帧——帧耗时超过由屏幕刷新率换算的逐帧预算(60Hz≈16.7ms、120Hz≈8.3ms)"}),"\n",(0,s.jsxs)(i.li,{children:["Each item shows frame duration and timestamp, plus the ",(0,s.jsx)(i.strong,{children:"build / raster split"})," (",(0,s.jsx)(i.code,{children:"buildDurationUs"})," + ",(0,s.jsx)(i.code,{children:"rasterDurationUs"}),") so you can tell whether build or GPU raster is the bottleneck / 每项显示帧耗时、时间戳,以及 ",(0,s.jsx)(i.strong,{children:"build / raster 分项"}),"(",(0,s.jsx)(i.code,{children:"buildDurationUs"})," + ",(0,s.jsx)(i.code,{children:"rasterDurationUs"}),"),便于判断是构建还是 GPU 光栅化卡顿"]}),"\n",(0,s.jsx)(i.li,{children:"Helps identify specific jank spikes / 帮助定位具体卡顿点"}),"\n"]}),"\n",(0,s.jsx)(i.p,{children:"列出卡顿帧(帧耗时超过由屏幕刷新率换算的逐帧预算,60Hz≈16.7ms、120Hz≈8.3ms),每项显示帧耗时、时间戳与 build / raster 分项,帮助定位具体卡顿点。"}),"\n",(0,s.jsx)(i.h3,{id:n[6].id,children:n[6].value}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsx)(i.li,{children:"“Reset” button clears all statistics and historical data / “Reset” 按钮清空所有统计和历史数据"}),"\n",(0,s.jsx)(i.li,{children:"Useful for measuring a specific interaction scenario / 适合测量特定交互场景"}),"\n"]}),"\n",(0,s.jsx)(i.p,{children:"“Reset” 按钮清空所有统计和历史数据,适合测量特定交互场景。"}),"\n",(0,s.jsx)(i.h2,{id:n[7].id,children:n[7].value}),"\n",(0,s.jsxs)(i.p,{children:["The FPS panel also includes a ",(0,s.jsx)(i.strong,{children:"main-thread blocking watchdog"})," that catches stalls FPS cannot see."]}),"\n",(0,s.jsxs)(i.p,{children:["FPS 面板还内置",(0,s.jsx)(i.strong,{children:"主线程阻塞看门狗"}),",专门捕捉 FPS 看不见的卡死。"]}),"\n",(0,s.jsxs)(i.blockquote,{children:["\n",(0,s.jsx)(i.p,{children:(0,s.jsx)(i.strong,{children:"Available since v1.12.0"})}),"\n",(0,s.jsx)(i.p,{children:(0,s.jsx)(i.strong,{children:"v1.12.0 起可用"})}),"\n"]}),"\n",(0,s.jsxs)(i.p,{children:[(0,s.jsx)(i.strong,{children:"Why it’s needed / 为什么需要"}),": FPS only measures frames. When the UI isolate truly stalls (e.g. a synchronous heavy computation), it produces ",(0,s.jsx)(i.strong,{children:"zero frames"}),", so FPS reads as idle while the app is actually frozen. The watchdog closes that blind spot."]}),"\n",(0,s.jsxs)(i.p,{children:["FPS 只测帧。当 UI isolate 真正卡死(如同步重计算)时,它",(0,s.jsx)(i.strong,{children:"零帧产出"}),",于是 FPS 显示空闲,而 App 其实已冻住。看门狗补上这个盲区。"]}),"\n",(0,s.jsxs)(i.p,{children:[(0,s.jsx)(i.strong,{children:"How it works / 工作原理"}),":"]}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsxs)(i.li,{children:["A low-frequency ",(0,s.jsx)(i.strong,{children:"100ms heartbeat"})," measures the UI isolate’s responsiveness directly / 用 ",(0,s.jsx)(i.strong,{children:"100ms 低频心跳"})," 直接测量 UI isolate 的响应间隔"]}),"\n",(0,s.jsxs)(i.li,{children:["If a gap exceeds ",(0,s.jsx)(i.strong,{children:"300ms"})," with no response, it records a blocking event (duration / time / nearby logs) / 若超过 ",(0,s.jsx)(i.strong,{children:"300ms"})," 无响应,记录一条阻塞事件(时长 / 时间 / 附近日志)"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Off by default"})," with its ",(0,s.jsx)(i.strong,{children:"own switch"})," (not tied to the FPS master switch) — zero overhead unless you opt in / ",(0,s.jsx)(i.strong,{children:"默认关闭"}),"、有",(0,s.jsx)(i.strong,{children:"独立开关"}),"(不随 FPS 总开关联动),不开启则零开销"]}),"\n"]}),"\n",(0,s.jsx)(i.p,{children:"开启后,真正卡死(不产帧)也会被记成阻塞事件,配合 FPS 一起看因果。"}),"\n",(0,s.jsx)(i.h2,{id:n[8].id,children:n[8].value}),"\n",(0,s.jsxs)(i.p,{children:["FPS monitoring uses Flutter’s ",(0,s.jsx)(i.code,{children:"WidgetsBinding.instance.addTimingsCallback"})," to receive frame timing information from the engine. The callback is ",(0,s.jsx)(i.strong,{children:"batched"})," — it may return multiple ",(0,s.jsx)(i.code,{children:"FrameTiming"})," objects per call, so each frame is recorded individually inside the loop to ensure accurate FPS calculation."]}),"\n",(0,s.jsxs)(i.p,{children:["FPS 监控使用 Flutter 的 ",(0,s.jsx)(i.code,{children:"WidgetsBinding.instance.addTimingsCallback"})," 接收引擎的帧时序信息。该回调是",(0,s.jsx)(i.strong,{children:"批量"}),"的——每次调用可能返回多个 ",(0,s.jsx)(i.code,{children:"FrameTiming"})," 对象,因此在循环内部为每帧单独记录时间戳,确保 FPS 计算准确。"]}),"\n",(0,s.jsxs)(i.p,{children:[(0,s.jsx)(i.strong,{children:"Jank threshold / 卡顿阈值"}),": A frame is janky when its ",(0,s.jsx)(i.strong,{children:"total"})," duration (build + raster) exceeds the per-frame budget — ",(0,s.jsx)(i.code,{children:"1000ms \xf7 refreshRate"})," (≈16.7ms at 60Hz, ≈8.3ms at 120Hz). Since v1.11.0 the budget is ",(0,s.jsx)(i.strong,{children:"derived from the device’s refresh rate"}),", so high-refresh (120Hz) screens are held to a tight ≈8.3ms instead of being forgiven by a fixed 16ms."]}),"\n",(0,s.jsxs)(i.p,{children:[(0,s.jsx)(i.strong,{children:"卡顿阈值"}),":帧的",(0,s.jsx)(i.strong,{children:"总"}),"耗时(build + raster)超过逐帧预算即视为卡顿——",(0,s.jsx)(i.code,{children:"1000ms \xf7 刷新率"}),"(60Hz≈16.7ms、120Hz≈8.3ms)。自 v1.11.0 起预算按",(0,s.jsx)(i.strong,{children:"设备刷新率"}),"换算,高刷(120Hz)屏会被严格约束在 ≈8.3ms,而非被固定 16ms 放过。"]}),"\n",(0,s.jsxs)(i.p,{children:[(0,s.jsx)(i.strong,{children:"Phased timing / 分阶段耗时"}),": Each ",(0,s.jsx)(i.code,{children:"FrameRecord"})," carries ",(0,s.jsx)(i.code,{children:"buildDurationUs"})," and ",(0,s.jsx)(i.code,{children:"rasterDurationUs"})," (GPU raster); the janky-frame list shows the build / raster split per row."]}),"\n",(0,s.jsxs)(i.p,{children:[(0,s.jsx)(i.strong,{children:"分阶段耗时"}),":每条 ",(0,s.jsx)(i.code,{children:"FrameRecord"})," 都带有 ",(0,s.jsx)(i.code,{children:"buildDurationUs"})," 与 ",(0,s.jsx)(i.code,{children:"rasterDurationUs"}),"(GPU 光栅化),掉帧列表项会展示 build / raster 分项。"]}),"\n",(0,s.jsx)(i.h2,{id:n[9].id,children:n[9].value}),"\n",(0,s.jsxs)(i.p,{children:["If you need to start/stop monitoring from code (e.g., for automated testing), use ",(0,s.jsx)(i.code,{children:"FpsService"}),":"]}),"\n",(0,s.jsxs)(i.p,{children:["如需从代码控制监控(如自动化测试),使用 ",(0,s.jsx)(i.code,{children:"FpsService"}),":"]}),"\n",(0,s.jsx)(i.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,s.jsxs)(i.code,{children:[(0,s.jsx)(i.span,{children:(0,s.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Start / Stop FPS monitoring / 开始 / 停止 FPS 监控"})}),"\n",(0,s.jsxs)(i.span,{children:[(0,s.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"start"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,s.jsxs)(i.span,{children:[(0,s.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"stop"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,s.jsx)(i.span,{children:" "}),"\n",(0,s.jsx)(i.span,{children:(0,s.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Read current stats / 读取当前统计"})}),"\n",(0,s.jsxs)(i.span,{children:[(0,s.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" fps "}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.currentFps;"})]}),"\n",(0,s.jsxs)(i.span,{children:[(0,s.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" jankRate "}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.jankRate;"})]}),"\n",(0,s.jsxs)(i.span,{children:[(0,s.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" totalFrames "}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.totalFrameCount;"})]}),"\n",(0,s.jsxs)(i.span,{children:[(0,s.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" jankyFrames "}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.totalJankyCount;"})]}),"\n",(0,s.jsx)(i.span,{children:" "}),"\n",(0,s.jsx)(i.span,{children:(0,s.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Clear historical data / 清空历史数据"})}),"\n",(0,s.jsxs)(i.span,{children:[(0,s.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"clear"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,s.jsx)(i.span,{children:" "}),"\n",(0,s.jsx)(i.span,{children:(0,s.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Listen to updates / 监听更新"})}),"\n",(0,s.jsxs)(i.span,{children:[(0,s.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"addListener"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(() {"})]}),"\n",(0,s.jsx)(i.span,{children:(0,s.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" // Update your own UI / 更新你自己的 UI"})}),"\n",(0,s.jsx)(i.span,{children:(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"});"})}),"\n",(0,s.jsx)(i.span,{children:" "}),"\n",(0,s.jsx)(i.span,{children:(0,s.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Historical data access / 历史数据访问"})}),"\n",(0,s.jsxs)(i.span,{children:[(0,s.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" history "}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.fpsHistory; "}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// List, 60 entries / 60 条"})]}),"\n",(0,s.jsxs)(i.span,{children:[(0,s.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" records "}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.frameRecords; "}),(0,s.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// List, unmodifiable / 不可变,最多 3600 条"})]})]})}),"\n",(0,s.jsx)(i.h2,{id:n[10].id,children:n[10].value}),"\n",(0,s.jsxs)(i.p,{children:["Singleton service extending ",(0,s.jsx)(i.code,{children:"ChangeNotifier"}),"."]}),"\n",(0,s.jsxs)(i.p,{children:["继承 ",(0,s.jsx)(i.code,{children:"ChangeNotifier"})," 的单例服务。"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,s.jsxs)(i.table,{children:[(0,s.jsx)(i.thead,{children:(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.th,{children:"Method"}),(0,s.jsx)(i.th,{children:"Description"})]})}),(0,s.jsxs)(i.tbody,{children:[(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"start()"})}),(0,s.jsx)(i.td,{children:"Start FPS monitoring / 开始 FPS 监控"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"stop()"})}),(0,s.jsx)(i.td,{children:"Stop FPS monitoring / 停止 FPS 监控"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"clear()"})}),(0,s.jsx)(i.td,{children:"Clear all historical data and counters / 清空所有历史数据和计数器"})]})]})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,s.jsxs)(i.table,{children:[(0,s.jsx)(i.thead,{children:(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.th,{children:"Property"}),(0,s.jsx)(i.th,{children:"Type"}),(0,s.jsx)(i.th,{children:"Description"})]})}),(0,s.jsxs)(i.tbody,{children:[(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"isRunning"})}),(0,s.jsx)(i.td,{children:"bool"}),(0,s.jsx)(i.td,{children:"Whether monitoring is currently active / 是否正在监控"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"currentFps"})}),(0,s.jsx)(i.td,{children:"double"}),(0,s.jsx)(i.td,{children:"Current FPS (updated every 500ms) / 当前 FPS(每 500ms 更新)"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"jankRate"})}),(0,s.jsx)(i.td,{children:"double"}),(0,s.jsx)(i.td,{children:"Jank rate as percentage / 卡顿率(百分比)"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"totalFrameCount"})}),(0,s.jsx)(i.td,{children:"int"}),(0,s.jsx)(i.td,{children:"Total frames captured / 总帧数"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"totalJankyCount"})}),(0,s.jsx)(i.td,{children:"int"}),(0,s.jsx)(i.td,{children:"Total janky frames captured (per-frame budget) / 总卡顿帧数(按逐帧预算)"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"lastFrameJanky"})}),(0,s.jsx)(i.td,{children:"bool"}),(0,s.jsx)(i.td,{children:"Whether the most recent frame was janky / 最近一帧是否卡顿"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"fpsHistory"})}),(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"List"})}),(0,s.jsx)(i.td,{children:"Recent 60 FPS values (unmodifiable) / 最近 60 个 FPS 值(不可变)"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"frameRecords"})}),(0,s.jsx)(i.td,{children:(0,s.jsx)(i.code,{children:"List"})}),(0,s.jsx)(i.td,{children:"Recent frame records (unmodifiable, up to 3600) / 最近帧记录(不可变,最多 3600 条)"})]})]})]}),"\n",(0,s.jsx)(i.h2,{id:n[11].id,children:n[11].value}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Off by default / 默认关闭"}),": Zero overhead when disabled / 关闭时零开销"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Lightweight callbacks / 轻量回调"}),": Only processes frame timing data, no heavy computation / 仅处理帧时序数据,无重计算"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Bounded history / 有界历史"}),": Trend chart keeps only 60 points, frame records capped at 3600 / 趋势图仅保留 60 个点,帧记录上限 3600 条"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.strong,{children:"Safe with other features / 与其他功能兼容"}),": Can run alongside Memory Viewer monitoring / 可与 Memory Viewer 监控同时运行"]}),"\n"]}),"\n",(0,s.jsx)(i.h2,{id:n[12].id,children:n[12].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,s.jsxs)(i.table,{children:[(0,s.jsx)(i.thead,{children:(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.th,{children:"Feature"}),(0,s.jsx)(i.th,{children:"Android"}),(0,s.jsx)(i.th,{children:"iOS"})]})}),(0,s.jsxs)(i.tbody,{children:[(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:"FPS Monitoring"}),(0,s.jsx)(i.td,{children:"✅"}),(0,s.jsx)(i.td,{children:"✅"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:"Jank Detection"}),(0,s.jsx)(i.td,{children:"✅"}),(0,s.jsx)(i.td,{children:"✅"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:"Trend Chart"}),(0,s.jsx)(i.td,{children:"✅"}),(0,s.jsx)(i.td,{children:"✅"})]})]})]}),"\n",(0,s.jsxs)(i.blockquote,{children:["\n",(0,s.jsx)(i.p,{children:"FPS monitoring uses Flutter engine APIs and works on Android and iOS (the two platforms this plugin targets)."}),"\n",(0,s.jsx)(i.p,{children:"FPS 监控使用 Flutter 引擎 API,在本插件支持的两个平台 Android 与 iOS 上均可用。"}),"\n"]}),"\n",(0,s.jsx)(i.h2,{id:n[13].id,children:n[13].value}),"\n",(0,s.jsx)(i.p,{children:"The Example App includes an FPS demo module with three scenarios:"}),"\n",(0,s.jsx)(i.p,{children:"Example App 包含 FPS 演示模块,提供三种场景:"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,s.jsxs)(i.table,{children:[(0,s.jsx)(i.thead,{children:(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.th,{children:"Demo"}),(0,s.jsx)(i.th,{children:"Description"})]})}),(0,s.jsxs)(i.tbody,{children:[(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.strong,{children:"Trigger Jank"})}),(0,s.jsx)(i.td,{children:"Blocks the main thread for 100-500ms to simulate jank / 阻塞主线程 100-500ms 模拟卡顿"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.strong,{children:"Heavy Animations"})}),(0,s.jsx)(i.td,{children:"80 simultaneously rotating+scaling widgets to intentionally trigger jank / 80 个同时旋转+缩放的 widget 故意触发卡顿"})]}),(0,s.jsxs)(i.tr,{children:[(0,s.jsx)(i.td,{children:(0,s.jsx)(i.strong,{children:"Smooth Animation"})}),(0,s.jsx)(i.td,{children:"Single lightweight compound animation, demonstrates stable 60 FPS / 单个轻量复合动画,演示稳定 60 FPS"})]})]})]}),"\n",(0,s.jsx)(i.h2,{id:n[14].id,children:n[14].value}),"\n",(0,s.jsxs)(i.ul,{children:["\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.a,{href:"Usage",children:"Usage"})," — General usage guide / 通用使用指南"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.a,{href:"FAQ",children:"FAQ"})," — Common questions / 常见问题"]}),"\n",(0,s.jsxs)(i.li,{children:[(0,s.jsx)(i.a,{href:"Configuration",children:"Configuration"})," — Configuration options / 配置选项"]}),"\n"]})]})},"/FPS-Viewer",{filePath:"pages/FPS-Viewer.md",timestamp:1789063287e3,pageMap:t.v,frontMatter:{},title:"FPS Viewer / FPS 监控面板"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,n,i){"use strict";i.d(n,{v:function(){return s}});let s=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=7839)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/FPS-Viewer-b850e87e45780b99.js b/docs/_next/static/chunks/pages/FPS-Viewer-b850e87e45780b99.js deleted file mode 100644 index d44360a..0000000 --- a/docs/_next/static/chunks/pages/FPS-Viewer-b850e87e45780b99.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[229],{7839:function(e,i,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/FPS-Viewer",function(){return n(7548)}])},7548:function(e,i,n){"use strict";n.r(i),n.d(i,{useTOC:function(){return l}});var s=n(5893),r=n(7812),t=n(7080),d=n(8925);function l(e){return[{value:"Overview / 概览",id:"overview--概览",depth:2},{value:"Master Switch / 总开关",id:"master-switch--总开关",depth:2},{value:"Features / 功能",id:"features--功能",depth:2},{value:"1. Current Stats / 当前统计",id:"1-current-stats--当前统计",depth:3},{value:"2. FPS Trend Chart / FPS 趋势图",id:"2-fps-trend-chart--fps-趋势图",depth:3},{value:"3. Janky Frame List / 卡顿帧列表",id:"3-janky-frame-list--卡顿帧列表",depth:3},{value:"4. Reset / 重置",id:"4-reset--重置",depth:3},{value:"How It Works / 工作原理",id:"how-it-works--工作原理",depth:2},{value:"Programmatic Control (Optional) / 编程式控制(可选)",id:"programmatic-control-optional--编程式控制可选",depth:2},{value:"FpsService API / FpsService 接口",id:"fpsservice-api--fpsservice-接口",depth:2},{value:"Performance Considerations / 性能说明",id:"performance-considerations--性能说明",depth:2},{value:"Platform Support / 平台支持",id:"platform-support--平台支持",depth:2},{value:"Demo / 演示",id:"demo--演示",depth:2},{value:"Related / 相关",id:"related--相关",depth:2}]}i.default=(0,r.c)(function(e){let{toc:i=l(e)}=e,n={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{children:"FPS Viewer / FPS 监控面板"}),"\n",(0,s.jsx)(n.p,{children:"The FPS Viewer provides real-time frame performance analysis, including current FPS, jank rate, frame duration stats, and an FPS trend chart."}),"\n",(0,s.jsx)(n.p,{children:"FPS 监控面板提供实时帧性能分析,包括当前 FPS、卡顿率、帧耗时统计和 FPS 趋势图。"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Available since v1.2.0"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"v1.2.0 起可用"})}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:i[0].id,children:i[0].value}),"\n",(0,s.jsx)(n.p,{children:"The FPS Viewer is integrated into the inspector panel. Tap the floating inspector button → “FPS” tab to access."}),"\n",(0,s.jsx)(n.p,{children:"FPS 监控面板集成在检查器面板中。点击悬浮检查器按钮 → “FPS” 标签即可访问。"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"⚠️ Important"}),": FPS monitoring is ",(0,s.jsx)(n.strong,{children:"off by default"}),". You must toggle on the switch at the top of the panel to start collecting data."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"⚠️ 重要"}),":FPS 监控",(0,s.jsx)(n.strong,{children:"默认关闭"}),"。必须在面板顶部打开开关才会开始采集数据。"]}),"\n",(0,s.jsx)(n.h2,{id:i[1].id,children:i[1].value}),"\n",(0,s.jsx)(n.p,{children:"A switch at the top of the FPS panel controls whether monitoring is enabled."}),"\n",(0,s.jsx)(n.p,{children:"FPS 面板顶部有一个开关,控制是否启用监控。"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"State"}),(0,s.jsx)(n.th,{children:"Behavior"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"OFF (default)"})}),(0,s.jsx)(n.td,{children:"No frame timings callbacks, no timer, zero overhead / 无帧回调、无定时器、零开销"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"ON"})}),(0,s.jsxs)(n.td,{children:["Starts collecting frame data via ",(0,s.jsx)(n.code,{children:"WidgetsBinding.instance.addTimingsCallback"})," / 通过 ",(0,s.jsx)(n.code,{children:"addTimingsCallback"})," 开始采集帧数据"]})]})]})]}),"\n",(0,s.jsx)(n.p,{children:"When turned off, all callbacks and timers are cancelled, leaving no residual overhead."}),"\n",(0,s.jsx)(n.p,{children:"关闭时所有回调和定时器会被取消,不会残留开销。"}),"\n",(0,s.jsx)(n.h2,{id:i[2].id,children:i[2].value}),"\n",(0,s.jsx)(n.h3,{id:i[3].id,children:i[3].value}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Current FPS"})," — Updated every 500ms / 每 500ms 更新"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Jank Rate"})," — Percentage of janky frames (duration exceeds the per-frame budget, derived from the display refresh rate) / 卡顿帧占比(帧耗时超过由屏幕刷新率换算的逐帧预算)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Total Frame Count"})," — All frames captured since start / 自启动以来的总帧数"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Total Janky Count"})," — All janky frames captured / 自启动以来的总卡顿帧数"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Last Frame Janky"})," — Whether the most recent frame was janky / 最近一帧是否卡顿"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"显示当前 FPS、卡顿率、总帧数、总卡顿帧数、最近一帧是否卡顿。"}),"\n",(0,s.jsx)(n.h3,{id:i[4].id,children:i[4].value}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Real-time line chart with 30-second history window (60 data points \xd7 500ms)"}),"\n",(0,s.jsxs)(n.li,{children:["Y-axis dynamically scales to ",(0,s.jsx)(n.code,{children:"max(60, maxFps)"})," to avoid overflow when FPS spikes"]}),"\n",(0,s.jsx)(n.li,{children:"Janky frame region marked in red"}),"\n",(0,s.jsxs)(n.li,{children:["Time axis labels: ",(0,s.jsx)(n.code,{children:"-30s"})," / ",(0,s.jsx)(n.code,{children:"-15s"})," / ",(0,s.jsx)(n.code,{children:"Now"})]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["实时折线图,30 秒历史窗口(60 个数据点 \xd7 500ms)。Y 轴动态缩放为 ",(0,s.jsx)(n.code,{children:"max(60, maxFps)"})," 避免 FPS 飙升时溢出。卡顿区域以红色标记。"]}),"\n",(0,s.jsx)(n.h3,{id:i[5].id,children:i[5].value}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Lists janky frames — duration exceeds the per-frame budget, derived from the display refresh rate (~16.7ms at 60Hz, ~8.3ms at 120Hz) / 列出卡顿帧——帧耗时超过由屏幕刷新率换算的逐帧预算(60Hz≈16.7ms、120Hz≈8.3ms)"}),"\n",(0,s.jsxs)(n.li,{children:["Each item shows frame duration and timestamp, plus the ",(0,s.jsx)(n.strong,{children:"build / raster split"})," (",(0,s.jsx)(n.code,{children:"buildDurationUs"})," + ",(0,s.jsx)(n.code,{children:"rasterDurationUs"}),") so you can tell whether build or GPU raster is the bottleneck / 每项显示帧耗时、时间戳,以及 ",(0,s.jsx)(n.strong,{children:"build / raster 分项"}),"(",(0,s.jsx)(n.code,{children:"buildDurationUs"})," + ",(0,s.jsx)(n.code,{children:"rasterDurationUs"}),"),便于判断是构建还是 GPU 光栅化卡顿"]}),"\n",(0,s.jsx)(n.li,{children:"Helps identify specific jank spikes / 帮助定位具体卡顿点"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"列出卡顿帧(帧耗时超过由屏幕刷新率换算的逐帧预算,60Hz≈16.7ms、120Hz≈8.3ms),每项显示帧耗时、时间戳与 build / raster 分项,帮助定位具体卡顿点。"}),"\n",(0,s.jsx)(n.h3,{id:i[6].id,children:i[6].value}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"“Reset” button clears all statistics and historical data / “Reset” 按钮清空所有统计和历史数据"}),"\n",(0,s.jsx)(n.li,{children:"Useful for measuring a specific interaction scenario / 适合测量特定交互场景"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"“Reset” 按钮清空所有统计和历史数据,适合测量特定交互场景。"}),"\n",(0,s.jsx)(n.h2,{id:i[7].id,children:i[7].value}),"\n",(0,s.jsxs)(n.p,{children:["FPS monitoring uses Flutter’s ",(0,s.jsx)(n.code,{children:"WidgetsBinding.instance.addTimingsCallback"})," to receive frame timing information from the engine. The callback is ",(0,s.jsx)(n.strong,{children:"batched"})," — it may return multiple ",(0,s.jsx)(n.code,{children:"FrameTiming"})," objects per call, so each frame is recorded individually inside the loop to ensure accurate FPS calculation."]}),"\n",(0,s.jsxs)(n.p,{children:["FPS 监控使用 Flutter 的 ",(0,s.jsx)(n.code,{children:"WidgetsBinding.instance.addTimingsCallback"})," 接收引擎的帧时序信息。该回调是",(0,s.jsx)(n.strong,{children:"批量"}),"的——每次调用可能返回多个 ",(0,s.jsx)(n.code,{children:"FrameTiming"})," 对象,因此在循环内部为每帧单独记录时间戳,确保 FPS 计算准确。"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Jank threshold / 卡顿阈值"}),": A frame is janky when its ",(0,s.jsx)(n.strong,{children:"total"})," duration (build + raster) exceeds the per-frame budget — ",(0,s.jsx)(n.code,{children:"1000ms \xf7 refreshRate"})," (≈16.7ms at 60Hz, ≈8.3ms at 120Hz). Since v1.11.0 the budget is ",(0,s.jsx)(n.strong,{children:"derived from the device’s refresh rate"}),", so high-refresh (120Hz) screens are held to a tight ≈8.3ms instead of being forgiven by a fixed 16ms."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"卡顿阈值"}),":帧的",(0,s.jsx)(n.strong,{children:"总"}),"耗时(build + raster)超过逐帧预算即视为卡顿——",(0,s.jsx)(n.code,{children:"1000ms \xf7 刷新率"}),"(60Hz≈16.7ms、120Hz≈8.3ms)。自 v1.11.0 起预算按",(0,s.jsx)(n.strong,{children:"设备刷新率"}),"换算,高刷(120Hz)屏会被严格约束在 ≈8.3ms,而非被固定 16ms 放过。"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Phased timing / 分阶段耗时"}),": Each ",(0,s.jsx)(n.code,{children:"FrameRecord"})," carries ",(0,s.jsx)(n.code,{children:"buildDurationUs"})," and ",(0,s.jsx)(n.code,{children:"rasterDurationUs"})," (GPU raster); the janky-frame list shows the build / raster split per row."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"分阶段耗时"}),":每条 ",(0,s.jsx)(n.code,{children:"FrameRecord"})," 都带有 ",(0,s.jsx)(n.code,{children:"buildDurationUs"})," 与 ",(0,s.jsx)(n.code,{children:"rasterDurationUs"}),"(GPU 光栅化),掉帧列表项会展示 build / raster 分项。"]}),"\n",(0,s.jsx)(n.h2,{id:i[8].id,children:i[8].value}),"\n",(0,s.jsxs)(n.p,{children:["If you need to start/stop monitoring from code (e.g., for automated testing), use ",(0,s.jsx)(n.code,{children:"FpsService"}),":"]}),"\n",(0,s.jsxs)(n.p,{children:["如需从代码控制监控(如自动化测试),使用 ",(0,s.jsx)(n.code,{children:"FpsService"}),":"]}),"\n",(0,s.jsx)(n.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,s.jsxs)(n.code,{children:[(0,s.jsx)(n.span,{children:(0,s.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Start / Stop FPS monitoring / 开始 / 停止 FPS 监控"})}),"\n",(0,s.jsxs)(n.span,{children:[(0,s.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"start"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,s.jsxs)(n.span,{children:[(0,s.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"stop"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,s.jsx)(n.span,{children:" "}),"\n",(0,s.jsx)(n.span,{children:(0,s.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Read current stats / 读取当前统计"})}),"\n",(0,s.jsxs)(n.span,{children:[(0,s.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" fps "}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.currentFps;"})]}),"\n",(0,s.jsxs)(n.span,{children:[(0,s.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" jankRate "}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.jankRate;"})]}),"\n",(0,s.jsxs)(n.span,{children:[(0,s.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" totalFrames "}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.totalFrameCount;"})]}),"\n",(0,s.jsxs)(n.span,{children:[(0,s.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" jankyFrames "}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.totalJankyCount;"})]}),"\n",(0,s.jsx)(n.span,{children:" "}),"\n",(0,s.jsx)(n.span,{children:(0,s.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Clear historical data / 清空历史数据"})}),"\n",(0,s.jsxs)(n.span,{children:[(0,s.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"clear"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,s.jsx)(n.span,{children:" "}),"\n",(0,s.jsx)(n.span,{children:(0,s.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Listen to updates / 监听更新"})}),"\n",(0,s.jsxs)(n.span,{children:[(0,s.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"FpsService"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"addListener"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(() {"})]}),"\n",(0,s.jsx)(n.span,{children:(0,s.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" // Update your own UI / 更新你自己的 UI"})}),"\n",(0,s.jsx)(n.span,{children:(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"});"})}),"\n",(0,s.jsx)(n.span,{children:" "}),"\n",(0,s.jsx)(n.span,{children:(0,s.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Historical data access / 历史数据访问"})}),"\n",(0,s.jsxs)(n.span,{children:[(0,s.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" history "}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.fpsHistory; "}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// List, 60 entries / 60 条"})]}),"\n",(0,s.jsxs)(n.span,{children:[(0,s.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" records "}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" FpsService"}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.frameRecords; "}),(0,s.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// List, unmodifiable / 不可变,最多 3600 条"})]})]})}),"\n",(0,s.jsx)(n.h2,{id:i[9].id,children:i[9].value}),"\n",(0,s.jsxs)(n.p,{children:["Singleton service extending ",(0,s.jsx)(n.code,{children:"ChangeNotifier"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["继承 ",(0,s.jsx)(n.code,{children:"ChangeNotifier"})," 的单例服务。"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Method"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"start()"})}),(0,s.jsx)(n.td,{children:"Start FPS monitoring / 开始 FPS 监控"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"stop()"})}),(0,s.jsx)(n.td,{children:"Stop FPS monitoring / 停止 FPS 监控"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"clear()"})}),(0,s.jsx)(n.td,{children:"Clear all historical data and counters / 清空所有历史数据和计数器"})]})]})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Property"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"isRunning"})}),(0,s.jsx)(n.td,{children:"bool"}),(0,s.jsx)(n.td,{children:"Whether monitoring is currently active / 是否正在监控"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"currentFps"})}),(0,s.jsx)(n.td,{children:"double"}),(0,s.jsx)(n.td,{children:"Current FPS (updated every 500ms) / 当前 FPS(每 500ms 更新)"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"jankRate"})}),(0,s.jsx)(n.td,{children:"double"}),(0,s.jsx)(n.td,{children:"Jank rate as percentage / 卡顿率(百分比)"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"totalFrameCount"})}),(0,s.jsx)(n.td,{children:"int"}),(0,s.jsx)(n.td,{children:"Total frames captured / 总帧数"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"totalJankyCount"})}),(0,s.jsx)(n.td,{children:"int"}),(0,s.jsx)(n.td,{children:"Total janky frames captured (per-frame budget) / 总卡顿帧数(按逐帧预算)"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"lastFrameJanky"})}),(0,s.jsx)(n.td,{children:"bool"}),(0,s.jsx)(n.td,{children:"Whether the most recent frame was janky / 最近一帧是否卡顿"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"fpsHistory"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"List"})}),(0,s.jsx)(n.td,{children:"Recent 60 FPS values (unmodifiable) / 最近 60 个 FPS 值(不可变)"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"frameRecords"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"List"})}),(0,s.jsx)(n.td,{children:"Recent frame records (unmodifiable, up to 3600) / 最近帧记录(不可变,最多 3600 条)"})]})]})]}),"\n",(0,s.jsx)(n.h2,{id:i[10].id,children:i[10].value}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Off by default / 默认关闭"}),": Zero overhead when disabled / 关闭时零开销"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Lightweight callbacks / 轻量回调"}),": Only processes frame timing data, no heavy computation / 仅处理帧时序数据,无重计算"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Bounded history / 有界历史"}),": Trend chart keeps only 60 points, frame records capped at 3600 / 趋势图仅保留 60 个点,帧记录上限 3600 条"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Safe with other features / 与其他功能兼容"}),": Can run alongside Memory Viewer monitoring / 可与 Memory Viewer 监控同时运行"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:i[11].id,children:i[11].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Feature"}),(0,s.jsx)(n.th,{children:"Android"}),(0,s.jsx)(n.th,{children:"iOS"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"FPS Monitoring"}),(0,s.jsx)(n.td,{children:"✅"}),(0,s.jsx)(n.td,{children:"✅"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Jank Detection"}),(0,s.jsx)(n.td,{children:"✅"}),(0,s.jsx)(n.td,{children:"✅"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Trend Chart"}),(0,s.jsx)(n.td,{children:"✅"}),(0,s.jsx)(n.td,{children:"✅"})]})]})]}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"FPS monitoring uses Flutter engine APIs and works on Android and iOS (the two platforms this plugin targets)."}),"\n",(0,s.jsx)(n.p,{children:"FPS 监控使用 Flutter 引擎 API,在本插件支持的两个平台 Android 与 iOS 上均可用。"}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:i[12].id,children:i[12].value}),"\n",(0,s.jsx)(n.p,{children:"The Example App includes an FPS demo module with three scenarios:"}),"\n",(0,s.jsx)(n.p,{children:"Example App 包含 FPS 演示模块,提供三种场景:"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Demo"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Trigger Jank"})}),(0,s.jsx)(n.td,{children:"Blocks the main thread for 100-500ms to simulate jank / 阻塞主线程 100-500ms 模拟卡顿"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Heavy Animations"})}),(0,s.jsx)(n.td,{children:"80 simultaneously rotating+scaling widgets to intentionally trigger jank / 80 个同时旋转+缩放的 widget 故意触发卡顿"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Smooth Animation"})}),(0,s.jsx)(n.td,{children:"Single lightweight compound animation, demonstrates stable 60 FPS / 单个轻量复合动画,演示稳定 60 FPS"})]})]})]}),"\n",(0,s.jsx)(n.h2,{id:i[13].id,children:i[13].value}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"Usage",children:"Usage"})," — General usage guide / 通用使用指南"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"FAQ",children:"FAQ"})," — Common questions / 常见问题"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"Configuration",children:"Configuration"})," — Configuration options / 配置选项"]}),"\n"]})]})},"/FPS-Viewer",{filePath:"pages/FPS-Viewer.md",timestamp:1786753645e3,pageMap:t.v,frontMatter:{},title:"FPS Viewer / FPS 监控面板"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,i,n){"use strict";n.d(i,{v:function(){return s}});let s=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=7839)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Getting-Started-8e9a8ab5ed31839b.js b/docs/_next/static/chunks/pages/Getting-Started-8e9a8ab5ed31839b.js new file mode 100644 index 0000000..190e8be --- /dev/null +++ b/docs/_next/static/chunks/pages/Getting-Started-8e9a8ab5ed31839b.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[796],{9364:function(i,e,s){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Getting-Started",function(){return s(1028)}])},1028:function(i,e,s){"use strict";s.r(e),s.d(e,{useTOC:function(){return a}});var n=s(5893),r=s(7812),t=s(7080),l=s(8925);function a(i){return[{value:"Quick Start / 快速开始",id:"quick-start--快速开始",depth:2},{value:"What Happens Automatically / 自动完成的工作",id:"what-happens-automatically--自动完成的工作",depth:2},{value:"Production Build / 生产构建",id:"production-build--生产构建",depth:2},{value:"Alternative Integration / 替代集成方式",id:"alternative-integration--替代集成方式",depth:2},{value:"Next Steps / 下一步",id:"next-steps--下一步",depth:2}]}e.default=(0,r.c)(function(i){let{toc:e=a(i)}=i,s={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.a)(),...i.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.h1,{children:"Getting Started / 快速开始"}),"\n",(0,n.jsx)(s.h2,{id:e[0].id,children:e[0].value}),"\n",(0,n.jsxs)(s.p,{children:["Integrate with just ",(0,n.jsx)(s.strong,{children:"1 line of code"}),":"]}),"\n",(0,n.jsxs)(s.p,{children:["仅需 ",(0,n.jsx)(s.strong,{children:"1 行代码"})," 即可完成集成:"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"import"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'package:flutter/material.dart'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"import"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'package:zero_inspector_kit/zero_inspector_kit.dart'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"void"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" main"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"() {"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" // Single line: Initialize inspector, capture print() via Zone, and display floating button"})}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" // 一行代码:初始化检查器、通过 Zone 捕获 print()、自动显示悬浮按钮"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"runAppWithInspector"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"());"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"class"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" extends"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" StatelessWidget"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"({"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"super"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".key});"})]}),"\n",(0,n.jsx)(s.span,{children:" "}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" @override"})}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Widget"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" build"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"BuildContext"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" context) {"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" return"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MaterialApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" home"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Scaffold"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" appBar"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" AppBar"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(title"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Text"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'App'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:")),"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" body"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Center"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(child"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Text"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Hello World'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:")),"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ),"})}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" );"})}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" }"})}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})})]})}),"\n",(0,n.jsx)(s.h2,{id:e[1].id,children:e[1].value}),"\n",(0,n.jsxs)(s.p,{children:["After integration, the inspector automatically does the following ",(0,n.jsx)(s.strong,{children:"without modifying any other project code"}),":"]}),"\n",(0,n.jsxs)(s.p,{children:["集成后,检查器会自动完成以下工作,",(0,n.jsx)(s.strong,{children:"无需修改项目其他代码"}),":"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Feature"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:["✅ ",(0,n.jsx)(s.strong,{children:"Log Capture"})]}),(0,n.jsxs)(s.td,{children:["Auto-capture all ",(0,n.jsx)(s.code,{children:"print()"}),", ",(0,n.jsx)(s.code,{children:"debugPrint()"})," via Zone / 通过 Zone 自动捕获所有日志"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:["✅ ",(0,n.jsx)(s.strong,{children:"Network Interception"})]}),(0,n.jsxs)(s.td,{children:["Auto-intercept all ",(0,n.jsx)(s.strong,{children:"http"})," and ",(0,n.jsx)(s.strong,{children:"Dio"})," requests via HttpOverrides / 自动拦截所有网络请求"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:["✅ ",(0,n.jsx)(s.strong,{children:"Error Capture"})]}),(0,n.jsx)(s.td,{children:"Aggregate Flutter framework errors & unhandled exceptions, deduped by type + stack (Errors tab) / 聚合 Flutter 框架异常与未捕获异常,按类型+堆栈去重"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:["✅ ",(0,n.jsx)(s.strong,{children:"Session Persistence"})]}),(0,n.jsx)(s.td,{children:"Flush logs/network/errors to a disk ring buffer; logs & errors replay on next launch / 日志/网络/异常落盘环形缓冲,启动时回放日志与异常"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:["✅ ",(0,n.jsx)(s.strong,{children:"Database Scan"})]}),(0,n.jsx)(s.td,{children:"Auto-scan and register SQLite databases / 自动扫描注册数据库"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:["✅ ",(0,n.jsx)(s.strong,{children:"Floating Button"})]}),(0,n.jsx)(s.td,{children:"Auto-displayed via Overlay, not affected by keyboard / 通过 Overlay 自动显示"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:["✅ ",(0,n.jsx)(s.strong,{children:"Route Tracking"})]}),(0,n.jsxs)(s.td,{children:["Auto-inject ",(0,n.jsx)(s.code,{children:"InspectorRouteObserver"})," into MaterialApp / 自动注入路由观察者"]})]})]})]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"Note / 说明"}),": The ",(0,n.jsx)(s.strong,{children:"Memory"})," and ",(0,n.jsx)(s.strong,{children:"FPS"})," panels are available as tabs but are ",(0,n.jsx)(s.strong,{children:"off by default"})," to avoid performance overhead. Toggle the switch at the top of each panel to start collecting data. See ",(0,n.jsx)(s.a,{href:"Memory-Viewer",children:"Memory Viewer"})," and ",(0,n.jsx)(s.a,{href:"FPS-Viewer",children:"FPS Viewer"})," for details."]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"Memory"})," 和 ",(0,n.jsx)(s.strong,{children:"FPS"})," 面板作为标签页可用,但",(0,n.jsx)(s.strong,{children:"默认关闭"}),"以避免性能开销。在各自面板顶部打开开关才会开始采集数据。详见 ",(0,n.jsx)(s.a,{href:"Memory-Viewer",children:"Memory Viewer"})," 和 ",(0,n.jsx)(s.a,{href:"FPS-Viewer",children:"FPS Viewer"}),"。"]}),"\n"]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"Pre-initialized binding / 提前初始化的 binding"}),": If you call ",(0,n.jsx)(s.code,{children:"WidgetsFlutterBinding.ensureInitialized()"})," or ",(0,n.jsx)(s.code,{children:"await"})," a platform-channel plugin (e.g. ",(0,n.jsx)(s.code,{children:"SharedPreferences"}),") ",(0,n.jsx)(s.strong,{children:"before"})," ",(0,n.jsx)(s.code,{children:"runAppWithInspector()"}),", the binding is already initialized. In that case ",(0,n.jsx)(s.code,{children:"runAppWithInspector()"})," degrades gracefully to a plain ",(0,n.jsx)(s.code,{children:"runApp()"})," (capturing ",(0,n.jsx)(s.code,{children:"debugPrint"})," logs, but not raw ",(0,n.jsx)(s.code,{children:"print()"}),") instead of throwing a “Zone mismatch” assertion. No extra setup is needed."]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"binding 已提前初始化"}),":如果你在 ",(0,n.jsx)(s.code,{children:"runAppWithInspector()"})," ",(0,n.jsx)(s.strong,{children:"之前"}),"调用了 ",(0,n.jsx)(s.code,{children:"WidgetsFlutterBinding.ensureInitialized()"})," 或 ",(0,n.jsx)(s.code,{children:"await"})," 了会触发 platform channel 的插件(如 ",(0,n.jsx)(s.code,{children:"SharedPreferences"}),"),binding 已被初始化。此时 ",(0,n.jsx)(s.code,{children:"runAppWithInspector()"})," 会优雅降级为普通 ",(0,n.jsx)(s.code,{children:"runApp()"}),"(仍捕获 ",(0,n.jsx)(s.code,{children:"debugPrint"})," 日志,但不捕获原始 ",(0,n.jsx)(s.code,{children:"print()"}),"),而非抛出 “Zone mismatch” 断言。无需额外处理。"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:e[2].id,children:e[2].value}),"\n",(0,n.jsxs)(s.p,{children:["The inspector is ",(0,n.jsx)(s.strong,{children:"automatically disabled"})," in release mode. You don’t need to remove any code — Flutter’s tree-shaking will remove all inspector-related code from production builds."]}),"\n",(0,n.jsxs)(s.p,{children:["检查器在 release 模式下会",(0,n.jsx)(s.strong,{children:"自动禁用"}),",无需移除任何代码,Flutter 的 tree-shaking 会自动移除所有检查器相关代码。"]}),"\n",(0,n.jsx)(s.h2,{id:e[3].id,children:e[3].value}),"\n",(0,n.jsx)(s.p,{children:"If you prefer more control, use the two-line approach:"}),"\n",(0,n.jsx)(s.p,{children:"如果需要更多控制权,可以使用两行代码方式:"}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"void"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" main"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"() {"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"init"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" runApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"wrapApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"()));"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})})]})}),"\n",(0,n.jsx)(s.h2,{id:e[4].id,children:e[4].value}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Installation",children:"Installation"})," — Detailed installation methods / 详细安装方式"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Usage",children:"Usage"})," — Full usage guide / 完整使用指南"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Configuration",children:"Configuration"})," — Configuration options / 配置选项"]}),"\n"]})]})},"/Getting-Started",{filePath:"pages/Getting-Started.md",timestamp:1788758921e3,pageMap:t.v,frontMatter:{},title:"Getting Started / 快速开始"},"undefined"==typeof RemoteContent?a:RemoteContent.useTOC)},7080:function(i,e,s){"use strict";s.d(e,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(i){i.O(0,[812,888,774,179],function(){return i(i.s=9364)}),_N_E=i.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Getting-Started-cb7caccb4a04217f.js b/docs/_next/static/chunks/pages/Getting-Started-cb7caccb4a04217f.js deleted file mode 100644 index e66082a..0000000 --- a/docs/_next/static/chunks/pages/Getting-Started-cb7caccb4a04217f.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[796],{9364:function(i,s,e){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Getting-Started",function(){return e(1028)}])},1028:function(i,s,e){"use strict";e.r(s),e.d(s,{useTOC:function(){return l}});var t=e(5893),r=e(7812),n=e(7080),a=e(8925);function l(i){return[{value:"Quick Start / 快速开始",id:"quick-start--快速开始",depth:2},{value:"What Happens Automatically / 自动完成的工作",id:"what-happens-automatically--自动完成的工作",depth:2},{value:"Production Build / 生产构建",id:"production-build--生产构建",depth:2},{value:"Alternative Integration / 替代集成方式",id:"alternative-integration--替代集成方式",depth:2},{value:"Next Steps / 下一步",id:"next-steps--下一步",depth:2}]}s.default=(0,r.c)(function(i){let{toc:s=l(i)}=i,e={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.a)(),...i.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(e.h1,{children:"Getting Started / 快速开始"}),"\n",(0,t.jsx)(e.h2,{id:s[0].id,children:s[0].value}),"\n",(0,t.jsxs)(e.p,{children:["Integrate with just ",(0,t.jsx)(e.strong,{children:"1 line of code"}),":"]}),"\n",(0,t.jsxs)(e.p,{children:["仅需 ",(0,t.jsx)(e.strong,{children:"1 行代码"})," 即可完成集成:"]}),"\n",(0,t.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,t.jsxs)(e.code,{children:[(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"import"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'package:flutter/material.dart'"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]}),"\n",(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"import"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'package:zero_inspector_kit/zero_inspector_kit.dart'"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]}),"\n",(0,t.jsx)(e.span,{children:" "}),"\n",(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"void"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" main"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"() {"})]}),"\n",(0,t.jsx)(e.span,{children:(0,t.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" // Single line: Initialize inspector, capture print() via Zone, and display floating button"})}),"\n",(0,t.jsx)(e.span,{children:(0,t.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:" // 一行代码:初始化检查器、通过 Zone 捕获 print()、自动显示悬浮按钮"})}),"\n",(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ZeroInspectorKit"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"runAppWithInspector"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"const"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyApp"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"());"})]}),"\n",(0,t.jsx)(e.span,{children:(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})}),"\n",(0,t.jsx)(e.span,{children:" "}),"\n",(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"class"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyApp"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" extends"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" StatelessWidget"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"})]}),"\n",(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyApp"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"({"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"super"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".key});"})]}),"\n",(0,t.jsx)(e.span,{children:" "}),"\n",(0,t.jsx)(e.span,{children:(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" @override"})}),"\n",(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Widget"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" build"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"BuildContext"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" context) {"})]}),"\n",(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" return"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MaterialApp"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" home"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Scaffold"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" appBar"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" AppBar"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(title"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Text"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'App'"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:")),"})]}),"\n",(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" body"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Center"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(child"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Text"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Hello World'"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:")),"})]}),"\n",(0,t.jsx)(e.span,{children:(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ),"})}),"\n",(0,t.jsx)(e.span,{children:(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" );"})}),"\n",(0,t.jsx)(e.span,{children:(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" }"})}),"\n",(0,t.jsx)(e.span,{children:(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})})]})}),"\n",(0,t.jsx)(e.h2,{id:s[1].id,children:s[1].value}),"\n",(0,t.jsxs)(e.p,{children:["After integration, the inspector automatically does the following ",(0,t.jsx)(e.strong,{children:"without modifying any other project code"}),":"]}),"\n",(0,t.jsxs)(e.p,{children:["集成后,检查器会自动完成以下工作,",(0,t.jsx)(e.strong,{children:"无需修改项目其他代码"}),":"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,t.jsxs)(e.table,{children:[(0,t.jsx)(e.thead,{children:(0,t.jsxs)(e.tr,{children:[(0,t.jsx)(e.th,{children:"Feature"}),(0,t.jsx)(e.th,{children:"Description"})]})}),(0,t.jsxs)(e.tbody,{children:[(0,t.jsxs)(e.tr,{children:[(0,t.jsxs)(e.td,{children:["✅ ",(0,t.jsx)(e.strong,{children:"Log Capture"})]}),(0,t.jsxs)(e.td,{children:["Auto-capture all ",(0,t.jsx)(e.code,{children:"print()"}),", ",(0,t.jsx)(e.code,{children:"debugPrint()"})," via Zone / 通过 Zone 自动捕获所有日志"]})]}),(0,t.jsxs)(e.tr,{children:[(0,t.jsxs)(e.td,{children:["✅ ",(0,t.jsx)(e.strong,{children:"Network Interception"})]}),(0,t.jsxs)(e.td,{children:["Auto-intercept all ",(0,t.jsx)(e.strong,{children:"http"})," and ",(0,t.jsx)(e.strong,{children:"Dio"})," requests via HttpOverrides / 自动拦截所有网络请求"]})]}),(0,t.jsxs)(e.tr,{children:[(0,t.jsxs)(e.td,{children:["✅ ",(0,t.jsx)(e.strong,{children:"Error Capture"})]}),(0,t.jsx)(e.td,{children:"Aggregate Flutter framework errors & unhandled exceptions, deduped by type + stack (Errors tab) / 聚合 Flutter 框架异常与未捕获异常,按类型+堆栈去重"})]}),(0,t.jsxs)(e.tr,{children:[(0,t.jsxs)(e.td,{children:["✅ ",(0,t.jsx)(e.strong,{children:"Session Persistence"})]}),(0,t.jsx)(e.td,{children:"Flush logs/network/errors to a disk ring buffer; logs & errors replay on next launch / 日志/网络/异常落盘环形缓冲,启动时回放日志与异常"})]}),(0,t.jsxs)(e.tr,{children:[(0,t.jsxs)(e.td,{children:["✅ ",(0,t.jsx)(e.strong,{children:"Database Scan"})]}),(0,t.jsx)(e.td,{children:"Auto-scan and register SQLite databases / 自动扫描注册数据库"})]}),(0,t.jsxs)(e.tr,{children:[(0,t.jsxs)(e.td,{children:["✅ ",(0,t.jsx)(e.strong,{children:"Floating Button"})]}),(0,t.jsx)(e.td,{children:"Auto-displayed via Overlay, not affected by keyboard / 通过 Overlay 自动显示"})]}),(0,t.jsxs)(e.tr,{children:[(0,t.jsxs)(e.td,{children:["✅ ",(0,t.jsx)(e.strong,{children:"Route Tracking"})]}),(0,t.jsxs)(e.td,{children:["Auto-inject ",(0,t.jsx)(e.code,{children:"InspectorRouteObserver"})," into MaterialApp / 自动注入路由观察者"]})]})]})]}),"\n",(0,t.jsxs)(e.blockquote,{children:["\n",(0,t.jsxs)(e.p,{children:[(0,t.jsx)(e.strong,{children:"Note / 说明"}),": The ",(0,t.jsx)(e.strong,{children:"Memory"})," and ",(0,t.jsx)(e.strong,{children:"FPS"})," panels are available as tabs but are ",(0,t.jsx)(e.strong,{children:"off by default"})," to avoid performance overhead. Toggle the switch at the top of each panel to start collecting data. See ",(0,t.jsx)(e.a,{href:"Memory-Viewer",children:"Memory Viewer"})," and ",(0,t.jsx)(e.a,{href:"FPS-Viewer",children:"FPS Viewer"})," for details."]}),"\n",(0,t.jsxs)(e.p,{children:[(0,t.jsx)(e.strong,{children:"Memory"})," 和 ",(0,t.jsx)(e.strong,{children:"FPS"})," 面板作为标签页可用,但",(0,t.jsx)(e.strong,{children:"默认关闭"}),"以避免性能开销。在各自面板顶部打开开关才会开始采集数据。详见 ",(0,t.jsx)(e.a,{href:"Memory-Viewer",children:"Memory Viewer"})," 和 ",(0,t.jsx)(e.a,{href:"FPS-Viewer",children:"FPS Viewer"}),"。"]}),"\n"]}),"\n",(0,t.jsx)(e.h2,{id:s[2].id,children:s[2].value}),"\n",(0,t.jsxs)(e.p,{children:["The inspector is ",(0,t.jsx)(e.strong,{children:"automatically disabled"})," in release mode. You don’t need to remove any code — Flutter’s tree-shaking will remove all inspector-related code from production builds."]}),"\n",(0,t.jsxs)(e.p,{children:["检查器在 release 模式下会",(0,t.jsx)(e.strong,{children:"自动禁用"}),",无需移除任何代码,Flutter 的 tree-shaking 会自动移除所有检查器相关代码。"]}),"\n",(0,t.jsx)(e.h2,{id:s[3].id,children:s[3].value}),"\n",(0,t.jsx)(e.p,{children:"If you prefer more control, use the two-line approach:"}),"\n",(0,t.jsx)(e.p,{children:"如果需要更多控制权,可以使用两行代码方式:"}),"\n",(0,t.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,t.jsxs)(e.code,{children:[(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"void"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" main"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"() {"})]}),"\n",(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ZeroInspectorKit"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"init"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,t.jsxs)(e.span,{children:[(0,t.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" runApp"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ZeroInspectorKit"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"wrapApp"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"const"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyApp"}),(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"()));"})]}),"\n",(0,t.jsx)(e.span,{children:(0,t.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})})]})}),"\n",(0,t.jsx)(e.h2,{id:s[4].id,children:s[4].value}),"\n",(0,t.jsxs)(e.ul,{children:["\n",(0,t.jsxs)(e.li,{children:[(0,t.jsx)(e.a,{href:"Installation",children:"Installation"})," — Detailed installation methods / 详细安装方式"]}),"\n",(0,t.jsxs)(e.li,{children:[(0,t.jsx)(e.a,{href:"Usage",children:"Usage"})," — Full usage guide / 完整使用指南"]}),"\n",(0,t.jsxs)(e.li,{children:[(0,t.jsx)(e.a,{href:"Configuration",children:"Configuration"})," — Configuration options / 配置选项"]}),"\n"]})]})},"/Getting-Started",{filePath:"pages/Getting-Started.md",timestamp:1786656379e3,pageMap:n.v,frontMatter:{},title:"Getting Started / 快速开始"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(i,s,e){"use strict";e.d(s,{v:function(){return t}});let t=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(i){i.O(0,[812,888,774,179],function(){return i(i.s=9364)}),_N_E=i.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Installation-5edd28387f9ec116.js b/docs/_next/static/chunks/pages/Installation-ef9aacdf09d13218.js similarity index 77% rename from docs/_next/static/chunks/pages/Installation-5edd28387f9ec116.js rename to docs/_next/static/chunks/pages/Installation-ef9aacdf09d13218.js index 7fc272f..1f23faa 100644 --- a/docs/_next/static/chunks/pages/Installation-5edd28387f9ec116.js +++ b/docs/_next/static/chunks/pages/Installation-ef9aacdf09d13218.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[139],{9720:function(e,i,t){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Installation",function(){return t(1220)}])},1220:function(e,i,t){"use strict";t.r(i),t.d(i,{useTOC:function(){return l}});var r=t(5893),n=t(7812),s=t(7080),a=t(8925),d=t(5192);function l(e){return[{value:"From pub.dev (Recommended) / 从 pub.dev 安装(推荐)",id:"from-pubdev-recommended--从-pubdev-安装推荐",depth:2},{value:"From GitHub / 从 GitHub 安装",id:"from-github--从-github-安装",depth:2},{value:"Platform Setup / 平台配置",id:"platform-setup--平台配置",depth:2},{value:"Android",id:"android",depth:3},{value:"iOS",id:"ios",depth:3},{value:"Import / 导入",id:"import--导入",depth:2},{value:"Requirements / 环境要求",id:"requirements--环境要求",depth:2},{value:"Next Steps / 下一步",id:"next-steps--下一步",depth:2}]}i.default=(0,n.c)(function(e){let{toc:i=l(e)}=e,t={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.a)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.h1,{children:"Installation / 安装"}),"\n",(0,r.jsx)(t.h2,{id:i[0].id,children:i[0].value}),"\n",(0,r.jsxs)(t.p,{children:["Add the following to your ",(0,r.jsx)(t.code,{children:"pubspec.yaml"}),":"]}),"\n",(0,r.jsxs)(t.p,{children:["在 ",(0,r.jsx)(t.code,{children:"pubspec.yaml"})," 中添加以下依赖:"]}),"\n",(0,r.jsx)(t.pre,{tabIndex:"0","data-language":"yaml","data-word-wrap":"","data-copy":"",children:(0,r.jsxs)(t.code,{children:[(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:"dependencies"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" zero_inspector_kit"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:": "}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"^1.11.1"})]})]})}),"\n",(0,r.jsx)(t.p,{children:"Then run:"}),"\n",(0,r.jsx)(t.p,{children:"然后运行:"}),"\n",(0,r.jsx)(t.pre,{icon:d.Fx,tabIndex:"0","data-language":"bash","data-word-wrap":"","data-copy":"",children:(0,r.jsx)(t.code,{children:(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"flutter"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" pub"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" get"})]})})}),"\n",(0,r.jsx)(t.h2,{id:i[1].id,children:i[1].value}),"\n",(0,r.jsx)(t.p,{children:"Alternatively, install from GitHub:"}),"\n",(0,r.jsx)(t.p,{children:"或者从 GitHub 安装:"}),"\n",(0,r.jsx)(t.pre,{tabIndex:"0","data-language":"yaml","data-word-wrap":"","data-copy":"",children:(0,r.jsxs)(t.code,{children:[(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:"dependencies"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" zero_inspector_kit"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" git"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" url"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:": "}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"https://github.com/zero-labsco/zero_inspector_kit.git"})]}),"\n",(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" ref"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:": "}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"release/v1.11.1"})]})]})}),"\n",(0,r.jsx)(t.h2,{id:i[2].id,children:i[2].value}),"\n",(0,r.jsx)(t.h3,{id:i[3].id,children:i[3].value}),"\n",(0,r.jsx)(t.p,{children:"No additional configuration needed."}),"\n",(0,r.jsx)(t.p,{children:"无需额外配置。"}),"\n",(0,r.jsx)(t.h3,{id:i[4].id,children:i[4].value}),"\n",(0,r.jsx)(t.p,{children:"No additional configuration needed."}),"\n",(0,r.jsx)(t.p,{children:"无需额外配置。"}),"\n",(0,r.jsx)(t.h2,{id:i[5].id,children:i[5].value}),"\n",(0,r.jsx)(t.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,r.jsx)(t.code,{children:(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"import"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'package:zero_inspector_kit/zero_inspector_kit.dart'"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]})})}),"\n",(0,r.jsx)(t.h2,{id:i[6].id,children:i[6].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(t.table,{children:[(0,r.jsx)(t.thead,{children:(0,r.jsxs)(t.tr,{children:[(0,r.jsx)(t.th,{children:"Requirement"}),(0,r.jsx)(t.th,{children:"Version"})]})}),(0,r.jsxs)(t.tbody,{children:[(0,r.jsxs)(t.tr,{children:[(0,r.jsx)(t.td,{children:"Flutter"}),(0,r.jsx)(t.td,{children:">= 3.3.0"})]}),(0,r.jsxs)(t.tr,{children:[(0,r.jsx)(t.td,{children:"Dart SDK"}),(0,r.jsx)(t.td,{children:">= 3.11.0 < 4.0.0"})]})]})]}),"\n",(0,r.jsx)(t.h2,{id:i[7].id,children:i[7].value}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.a,{href:"Getting-Started",children:"Getting Started"})," — Quick start guide / 快速开始"]}),"\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.a,{href:"Usage",children:"Usage"})," — Full usage guide / 完整使用指南"]}),"\n"]})]})},"/Installation",{filePath:"pages/Installation.md",timestamp:178793305e4,pageMap:s.v,frontMatter:{},title:"Installation / 安装"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,i,t){"use strict";t.d(i,{v:function(){return r}});let r=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=9720)}),_N_E=e.O()}]); \ No newline at end of file +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[139],{9720:function(e,i,t){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Installation",function(){return t(1220)}])},1220:function(e,i,t){"use strict";t.r(i),t.d(i,{useTOC:function(){return l}});var r=t(5893),n=t(7812),s=t(7080),a=t(8925),d=t(5192);function l(e){return[{value:"From pub.dev (Recommended) / 从 pub.dev 安装(推荐)",id:"from-pubdev-recommended--从-pubdev-安装推荐",depth:2},{value:"From GitHub / 从 GitHub 安装",id:"from-github--从-github-安装",depth:2},{value:"Platform Setup / 平台配置",id:"platform-setup--平台配置",depth:2},{value:"Android",id:"android",depth:3},{value:"iOS",id:"ios",depth:3},{value:"Import / 导入",id:"import--导入",depth:2},{value:"Requirements / 环境要求",id:"requirements--环境要求",depth:2},{value:"Next Steps / 下一步",id:"next-steps--下一步",depth:2}]}i.default=(0,n.c)(function(e){let{toc:i=l(e)}=e,t={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.a)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.h1,{children:"Installation / 安装"}),"\n",(0,r.jsx)(t.h2,{id:i[0].id,children:i[0].value}),"\n",(0,r.jsxs)(t.p,{children:["Add the following to your ",(0,r.jsx)(t.code,{children:"pubspec.yaml"}),":"]}),"\n",(0,r.jsxs)(t.p,{children:["在 ",(0,r.jsx)(t.code,{children:"pubspec.yaml"})," 中添加以下依赖:"]}),"\n",(0,r.jsx)(t.pre,{tabIndex:"0","data-language":"yaml","data-word-wrap":"","data-copy":"",children:(0,r.jsxs)(t.code,{children:[(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:"dependencies"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" zero_inspector_kit"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:": "}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"^1.12.0"})]})]})}),"\n",(0,r.jsx)(t.p,{children:"Then run:"}),"\n",(0,r.jsx)(t.p,{children:"然后运行:"}),"\n",(0,r.jsx)(t.pre,{icon:d.Fx,tabIndex:"0","data-language":"bash","data-word-wrap":"","data-copy":"",children:(0,r.jsx)(t.code,{children:(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"flutter"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" pub"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" get"})]})})}),"\n",(0,r.jsx)(t.h2,{id:i[1].id,children:i[1].value}),"\n",(0,r.jsx)(t.p,{children:"Alternatively, install from GitHub:"}),"\n",(0,r.jsx)(t.p,{children:"或者从 GitHub 安装:"}),"\n",(0,r.jsx)(t.pre,{tabIndex:"0","data-language":"yaml","data-word-wrap":"","data-copy":"",children:(0,r.jsxs)(t.code,{children:[(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:"dependencies"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" zero_inspector_kit"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" git"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" url"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:": "}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"https://github.com/zero-labsco/zero_inspector_kit.git"})]}),"\n",(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" ref"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:": "}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"release/v1.12.0"})]})]})}),"\n",(0,r.jsx)(t.h2,{id:i[2].id,children:i[2].value}),"\n",(0,r.jsx)(t.h3,{id:i[3].id,children:i[3].value}),"\n",(0,r.jsx)(t.p,{children:"No additional configuration needed."}),"\n",(0,r.jsx)(t.p,{children:"无需额外配置。"}),"\n",(0,r.jsx)(t.h3,{id:i[4].id,children:i[4].value}),"\n",(0,r.jsx)(t.p,{children:"No additional configuration needed."}),"\n",(0,r.jsx)(t.p,{children:"无需额外配置。"}),"\n",(0,r.jsx)(t.h2,{id:i[5].id,children:i[5].value}),"\n",(0,r.jsx)(t.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,r.jsx)(t.code,{children:(0,r.jsxs)(t.span,{children:[(0,r.jsx)(t.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"import"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'package:zero_inspector_kit/zero_inspector_kit.dart'"}),(0,r.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]})})}),"\n",(0,r.jsx)(t.h2,{id:i[6].id,children:i[6].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(t.table,{children:[(0,r.jsx)(t.thead,{children:(0,r.jsxs)(t.tr,{children:[(0,r.jsx)(t.th,{children:"Requirement"}),(0,r.jsx)(t.th,{children:"Version"})]})}),(0,r.jsxs)(t.tbody,{children:[(0,r.jsxs)(t.tr,{children:[(0,r.jsx)(t.td,{children:"Flutter"}),(0,r.jsx)(t.td,{children:">= 3.3.0"})]}),(0,r.jsxs)(t.tr,{children:[(0,r.jsx)(t.td,{children:"Dart SDK"}),(0,r.jsx)(t.td,{children:">= 3.11.0 < 4.0.0"})]})]})]}),"\n",(0,r.jsx)(t.h2,{id:i[7].id,children:i[7].value}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.a,{href:"Getting-Started",children:"Getting Started"})," — Quick start guide / 快速开始"]}),"\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.a,{href:"Usage",children:"Usage"})," — Full usage guide / 完整使用指南"]}),"\n"]})]})},"/Installation",{filePath:"pages/Installation.md",timestamp:178793305e4,pageMap:s.v,frontMatter:{},title:"Installation / 安装"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,i,t){"use strict";t.d(i,{v:function(){return r}});let r=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=9720)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Log-Viewer-f91ed29d9fff37fb.js b/docs/_next/static/chunks/pages/Log-Viewer-458840c22be40d80.js similarity index 93% rename from docs/_next/static/chunks/pages/Log-Viewer-f91ed29d9fff37fb.js rename to docs/_next/static/chunks/pages/Log-Viewer-458840c22be40d80.js index 055e375..fc49f28 100644 --- a/docs/_next/static/chunks/pages/Log-Viewer-f91ed29d9fff37fb.js +++ b/docs/_next/static/chunks/pages/Log-Viewer-458840c22be40d80.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[284],{3541:function(i,s,e){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Log-Viewer",function(){return e(9541)}])},9541:function(i,s,e){"use strict";e.r(s),e.d(s,{useTOC:function(){return h}});var n=e(5893),r=e(7812),t=e(7080),l=e(8925);function h(i){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"Log Sources / 日志来源",id:"log-sources--日志来源",depth:2},{value:"Log Levels / 日志级别",id:"log-levels--日志级别",depth:2},{value:"UI Features / UI 功能",id:"ui-features--ui-功能",depth:2},{value:"Filter Bar / 过滤栏",id:"filter-bar--过滤栏",depth:3},{value:"Toolbar / 工具栏",id:"toolbar--工具栏",depth:3},{value:"Log List / 日志列表",id:"log-list--日志列表",depth:3},{value:"Search / 搜索",id:"search--搜索",depth:3},{value:"Manual Logging / 手动记录日志",id:"manual-logging--手动记录日志",depth:2},{value:"Third-Party Library Integration / 第三方日志库集成",id:"third-party-library-integration--第三方日志库集成",depth:2},{value:"Auto-Capture (Inbound) / 自动捕获(入站)",id:"auto-capture-inbound--自动捕获入站",depth:3},{value:"Bidirectional Sync (Optional) / 双向同步(可选)",id:"bidirectional-sync-optional--双向同步可选",depth:3},{value:"Starting the Log Interceptor / 启动日志拦截器",id:"starting-the-log-interceptor--启动日志拦截器",depth:2}]}s.default=(0,r.c)(function(i){let{toc:s=h(i)}=i,e={a:"a",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.a)(),...i.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(e.h1,{children:"Log Viewer / 日志查看器"}),"\n",(0,n.jsx)(e.h2,{id:s[0].id,children:s[0].value}),"\n",(0,n.jsx)(e.p,{children:"The Log Viewer automatically captures logs from multiple sources with zero configuration."}),"\n",(0,n.jsx)(e.p,{children:"日志查看器自动从多个来源捕获日志,无需配置。"}),"\n",(0,n.jsx)(e.h2,{id:s[1].id,children:s[1].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(e.table,{children:[(0,n.jsx)(e.thead,{children:(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.th,{children:"Source"}),(0,n.jsx)(e.th,{children:"Capture Method"})]})}),(0,n.jsxs)(e.tbody,{children:[(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:(0,n.jsx)(e.code,{children:"print()"})}),(0,n.jsx)(e.td,{children:"Zone specification override / Zone 规范覆盖"})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:(0,n.jsx)(e.code,{children:"debugPrint()"})}),(0,n.jsx)(e.td,{children:"debugPrint override / debugPrint 覆盖"})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Flutter errors"}),(0,n.jsxs)(e.td,{children:[(0,n.jsx)(e.code,{children:"FlutterError.onError"})," hook / 接管 ",(0,n.jsx)(e.code,{children:"FlutterError.onError"})]})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Unhandled exceptions"}),(0,n.jsxs)(e.td,{children:[(0,n.jsx)(e.code,{children:"runZonedGuarded"})," / runZonedGuarded 捕获"]})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Third-party libraries"}),(0,n.jsx)(e.td,{children:"Via print() capture / 通过 print() 捕获"})]})]})]}),"\n",(0,n.jsxs)(e.blockquote,{children:["\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.strong,{children:"Since v1.9.0"}),", Flutter framework errors and unhandled exceptions are ",(0,n.jsx)(e.em,{children:"also"})," aggregated & deduplicated in the dedicated ",(0,n.jsx)(e.a,{href:"Errors",children:"Errors"})," tab (keyed by type + stack signature) — while this Log Viewer keeps showing them as error-level lines in the chronological stream. Use Errors for “is this crash repeating?”; use Log Viewer for the raw timeline."]}),"\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.strong,{children:"自 v1.9.0 起"}),",Flutter 框架异常与未捕获异常会",(0,n.jsx)(e.strong,{children:"同时"}),"进入独立的 ",(0,n.jsx)(e.a,{href:"Errors",children:"Errors"})," 标签页",(0,n.jsx)(e.strong,{children:"去重聚合"}),"(按类型 + 堆栈签名归并);本 Log Viewer 仍会在原始时间流中把它们显示为错误级日志。回答”该崩溃是否反复出现”请用 Errors;查看原始时间线请用 Log Viewer。"]}),"\n"]}),"\n",(0,n.jsx)(e.h2,{id:s[2].id,children:s[2].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(e.table,{children:[(0,n.jsx)(e.thead,{children:(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.th,{children:"Level"}),(0,n.jsx)(e.th,{children:"Abbreviation"}),(0,n.jsx)(e.th,{children:"Color"}),(0,n.jsx)(e.th,{children:"Description"})]})}),(0,n.jsxs)(e.tbody,{children:[(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Verbose"}),(0,n.jsx)(e.td,{children:"V"}),(0,n.jsx)(e.td,{children:"Gray"}),(0,n.jsx)(e.td,{children:"Detailed information / 详细信息"})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Debug"}),(0,n.jsx)(e.td,{children:"D"}),(0,n.jsx)(e.td,{children:"Blue"}),(0,n.jsx)(e.td,{children:"Debug information / 调试信息"})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Info"}),(0,n.jsx)(e.td,{children:"I"}),(0,n.jsx)(e.td,{children:"Green"}),(0,n.jsx)(e.td,{children:"General information / 一般信息"})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Warning"}),(0,n.jsx)(e.td,{children:"W"}),(0,n.jsx)(e.td,{children:"Orange"}),(0,n.jsx)(e.td,{children:"Warning messages / 警告信息"})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Error"}),(0,n.jsx)(e.td,{children:"E"}),(0,n.jsx)(e.td,{children:"Red"}),(0,n.jsx)(e.td,{children:"Error messages / 错误信息"})]})]})]}),"\n",(0,n.jsxs)(e.blockquote,{children:["\n",(0,n.jsxs)(e.p,{children:["Third-party library logs are categorized as ",(0,n.jsx)(e.strong,{children:"Info"})," level."]}),"\n"]}),"\n",(0,n.jsxs)(e.blockquote,{children:["\n",(0,n.jsxs)(e.p,{children:["第三方日志库的日志统一归类为 ",(0,n.jsx)(e.strong,{children:"Info"})," 级别。"]}),"\n"]}),"\n",(0,n.jsx)(e.h2,{id:s[3].id,children:s[3].value}),"\n",(0,n.jsx)(e.h3,{id:s[4].id,children:s[4].value}),"\n",(0,n.jsxs)(e.ul,{children:["\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"All"}),": Show all logs / 使用 All 显示所有日志"]}),"\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"V / D / I / W / E"}),": Filter by level / 按级别过滤"]}),"\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"Tag dropdown"}),": Filter by any captured tag / 按任意已捕获标签过滤"]}),"\n",(0,n.jsx)(e.li,{children:"Single-select mode / 单选模式"}),"\n"]}),"\n",(0,n.jsx)(e.h3,{id:s[5].id,children:s[5].value}),"\n",(0,n.jsxs)(e.ul,{children:["\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"Auto-scroll toggle"})," (default on): Jump to the newest log as new entries arrive; pause to keep history still for inspection / ",(0,n.jsx)(e.strong,{children:"自动滚动开关"}),"(默认开启):新日志到达时自动跳到最新;暂停可稳定查看历史"]}),"\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"Copy as JSON"}),": Copy all currently filtered logs as JSON / ",(0,n.jsx)(e.strong,{children:"复制为 JSON"}),":将当前过滤后的全部日志复制为 JSON"]}),"\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"Share as Text"}),": Share filtered logs as plain text / ",(0,n.jsx)(e.strong,{children:"分享为文本"}),":将过滤后的日志以纯文本分享"]}),"\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"Clear"}),": Clear all logs / ",(0,n.jsx)(e.strong,{children:"清除"}),":清空全部日志"]}),"\n"]}),"\n",(0,n.jsx)(e.h3,{id:s[6].id,children:s[6].value}),"\n",(0,n.jsxs)(e.ul,{children:["\n",(0,n.jsx)(e.li,{children:"Level badge with color / 带颜色的级别徽章"}),"\n",(0,n.jsx)(e.li,{children:"Timestamp display / 时间戳显示"}),"\n",(0,n.jsx)(e.li,{children:"Tag display (if available) / 标签显示(如有)"}),"\n",(0,n.jsx)(e.li,{children:"Error/warning rows have subtle background tint / 错误/警告行有淡色背景"}),"\n",(0,n.jsx)(e.li,{children:"Left border color indicates level / 左侧边框颜色表示级别"}),"\n",(0,n.jsx)(e.li,{children:"Tap a row to open the in-view detail page with full message and copy; use the back button to return / 点击行进入详情页(主视图内切换),可查看完整消息并复制,点返回按钮回到列表"}),"\n",(0,n.jsx)(e.li,{children:"Per-row copy button copies that single log as JSON / 行内复制按钮将该条日志以 JSON 复制"}),"\n"]}),"\n",(0,n.jsx)(e.h3,{id:s[7].id,children:s[7].value}),"\n",(0,n.jsxs)(e.ul,{children:["\n",(0,n.jsx)(e.li,{children:"Fuzzy search by message content or tag / 按消息内容或标签模糊搜索"}),"\n",(0,n.jsxs)(e.li,{children:["Toggle ",(0,n.jsx)(e.strong,{children:"regex mode"})," (",(0,n.jsx)(e.code,{children:". *"})," button) to search with ",(0,n.jsx)(e.code,{children:"RegExp"})," (case-insensitive); invalid patterns degrade gracefully instead of crashing / 点击 ",(0,n.jsx)(e.strong,{children:"正则模式"}),"(",(0,n.jsx)(e.code,{children:". *"})," 按钮)使用 ",(0,n.jsx)(e.code,{children:"RegExp"})," 搜索(不区分大小写);非法图案优雅降级,不会崩溃"]}),"\n",(0,n.jsx)(e.li,{children:"Combined with level and tag filters / 可与级别、标签过滤组合使用"}),"\n"]}),"\n",(0,n.jsx)(e.h2,{id:s[8].id,children:s[8].value}),"\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.strong,{children:"Quick shorthand (recommended) / 简化写法(推荐)"})," — available since v1.1.2 / v1.1.2 起可用:"]}),"\n",(0,n.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(e.code,{children:[(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"v"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Verbose message / 详细消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"d"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Debug message / 调试消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"i"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Info message / 信息消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"w"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Warning message / 警告消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"e"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Error message / 错误消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsx)(e.span,{children:" "}),"\n",(0,n.jsx)(e.span,{children:(0,n.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// With tag / 带标签"})}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"i"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'User logged in'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", tag"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'Auth'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]})]})}),"\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.strong,{children:"Full form / 完整写法"}),":"]}),"\n",(0,n.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(e.code,{children:[(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"verbose"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Verbose message / 详细消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"debug"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Debug message / 调试消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"info"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Info message / 信息消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"warning"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Warning message / 警告消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"error"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Error message / 错误消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsx)(e.span,{children:" "}),"\n",(0,n.jsx)(e.span,{children:(0,n.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// With tag / 带标签"})}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"info"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'User logged in'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", tag"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'Auth'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]})]})}),"\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.code,{children:"InspectorLog"})," is a static wrapper around ",(0,n.jsx)(e.code,{children:"InspectorLogInterceptor.instance"})," for shorter log calls."]}),"\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.code,{children:"InspectorLog"})," 是 ",(0,n.jsx)(e.code,{children:"InspectorLogInterceptor.instance"})," 的静态包装,用于更简短的日志调用。"]}),"\n",(0,n.jsx)(e.h2,{id:s[9].id,children:s[9].value}),"\n",(0,n.jsx)(e.h3,{id:s[10].id,children:s[10].value}),"\n",(0,n.jsxs)(e.p,{children:["No configuration needed. Any library using ",(0,n.jsx)(e.code,{children:"print()"})," or ",(0,n.jsx)(e.code,{children:"debugPrint()"})," is automatically captured."]}),"\n",(0,n.jsxs)(e.p,{children:["无需配置。任何使用 ",(0,n.jsx)(e.code,{children:"print()"})," 或 ",(0,n.jsx)(e.code,{children:"debugPrint()"})," 的库都会被自动捕获。"]}),"\n",(0,n.jsx)(e.h3,{id:s[11].id,children:s[11].value}),"\n",(0,n.jsx)(e.p,{children:"To forward inspector logs to your third-party logger:"}),"\n",(0,n.jsx)(e.p,{children:"将检查器日志转发到第三方日志库:"}),"\n",(0,n.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(e.code,{children:[(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"import"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'package:logger/logger.dart'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]}),"\n",(0,n.jsx)(e.span,{children:" "}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" logger "}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Logger"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsx)(e.span,{children:" "}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.onLogCaptured "}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" (entry) {"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" logger."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"log"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" _mapLogLevel"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(entry.level),"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" '"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"${"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"entry"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"tag"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" != "}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"null"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" ? "}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'["}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"${"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"entry"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"tag"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"] '"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" : "}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"''"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}${"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"entry"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"message"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsx)(e.span,{children:(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" );"})}),"\n",(0,n.jsx)(e.span,{children:(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"};"})})]})}),"\n",(0,n.jsxs)(e.blockquote,{children:["\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.strong,{children:"Note"}),": Do NOT call logging methods inside ",(0,n.jsx)(e.code,{children:"onLogCaptured"}),", as this will cause infinite recursion.\n",(0,n.jsx)(e.strong,{children:"注意"}),":不要在 ",(0,n.jsx)(e.code,{children:"onLogCaptured"})," 内部调用日志方法,否则会导致无限递归。"]}),"\n"]}),"\n",(0,n.jsx)(e.h2,{id:s[12].id,children:s[12].value}),"\n",(0,n.jsxs)(e.p,{children:["If using ",(0,n.jsx)(e.code,{children:"runAppWithInspector()"}),", the log interceptor starts automatically. Otherwise:"]}),"\n",(0,n.jsxs)(e.p,{children:["如果使用 ",(0,n.jsx)(e.code,{children:"runAppWithInspector()"}),",日志拦截器会自动启动。否则:"]}),"\n",(0,n.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsx)(e.code,{children:(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"start"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})})})]})},"/Log-Viewer",{filePath:"pages/Log-Viewer.md",timestamp:1787265563e3,pageMap:t.v,frontMatter:{},title:"Log Viewer / 日志查看器"},"undefined"==typeof RemoteContent?h:RemoteContent.useTOC)},7080:function(i,s,e){"use strict";e.d(s,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(i){i.O(0,[812,888,774,179],function(){return i(i.s=3541)}),_N_E=i.O()}]); \ No newline at end of file +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[284],{3541:function(i,s,e){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Log-Viewer",function(){return e(9541)}])},9541:function(i,s,e){"use strict";e.r(s),e.d(s,{useTOC:function(){return h}});var n=e(5893),r=e(7812),t=e(7080),l=e(8925);function h(i){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"Log Sources / 日志来源",id:"log-sources--日志来源",depth:2},{value:"Log Levels / 日志级别",id:"log-levels--日志级别",depth:2},{value:"UI Features / UI 功能",id:"ui-features--ui-功能",depth:2},{value:"Filter Bar / 过滤栏",id:"filter-bar--过滤栏",depth:3},{value:"Toolbar / 工具栏",id:"toolbar--工具栏",depth:3},{value:"Log List / 日志列表",id:"log-list--日志列表",depth:3},{value:"Search / 搜索",id:"search--搜索",depth:3},{value:"Manual Logging / 手动记录日志",id:"manual-logging--手动记录日志",depth:2},{value:"Third-Party Library Integration / 第三方日志库集成",id:"third-party-library-integration--第三方日志库集成",depth:2},{value:"Auto-Capture (Inbound) / 自动捕获(入站)",id:"auto-capture-inbound--自动捕获入站",depth:3},{value:"Bidirectional Sync (Optional) / 双向同步(可选)",id:"bidirectional-sync-optional--双向同步可选",depth:3},{value:"Starting the Log Interceptor / 启动日志拦截器",id:"starting-the-log-interceptor--启动日志拦截器",depth:2}]}s.default=(0,r.c)(function(i){let{toc:s=h(i)}=i,e={a:"a",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.a)(),...i.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(e.h1,{children:"Log Viewer / 日志查看器"}),"\n",(0,n.jsx)(e.h2,{id:s[0].id,children:s[0].value}),"\n",(0,n.jsx)(e.p,{children:"The Log Viewer automatically captures logs from multiple sources with zero configuration."}),"\n",(0,n.jsx)(e.p,{children:"日志查看器自动从多个来源捕获日志,无需配置。"}),"\n",(0,n.jsx)(e.h2,{id:s[1].id,children:s[1].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(e.table,{children:[(0,n.jsx)(e.thead,{children:(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.th,{children:"Source"}),(0,n.jsx)(e.th,{children:"Capture Method"})]})}),(0,n.jsxs)(e.tbody,{children:[(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:(0,n.jsx)(e.code,{children:"print()"})}),(0,n.jsx)(e.td,{children:"Zone specification override / Zone 规范覆盖"})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:(0,n.jsx)(e.code,{children:"debugPrint()"})}),(0,n.jsx)(e.td,{children:"debugPrint override / debugPrint 覆盖"})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Flutter errors"}),(0,n.jsxs)(e.td,{children:[(0,n.jsx)(e.code,{children:"FlutterError.onError"})," hook / 接管 ",(0,n.jsx)(e.code,{children:"FlutterError.onError"})]})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Unhandled exceptions"}),(0,n.jsxs)(e.td,{children:[(0,n.jsx)(e.code,{children:"runZonedGuarded"})," / runZonedGuarded 捕获"]})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Third-party libraries"}),(0,n.jsx)(e.td,{children:"Via print() capture / 通过 print() 捕获"})]})]})]}),"\n",(0,n.jsxs)(e.blockquote,{children:["\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.strong,{children:"Since v1.9.0"}),", Flutter framework errors and unhandled exceptions are ",(0,n.jsx)(e.em,{children:"also"})," aggregated & deduplicated in the dedicated ",(0,n.jsx)(e.a,{href:"Errors",children:"Errors"})," tab (keyed by type + stack signature) — while this Log Viewer keeps showing them as error-level lines in the chronological stream. Use Errors for “is this crash repeating?”; use Log Viewer for the raw timeline."]}),"\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.strong,{children:"自 v1.9.0 起"}),",Flutter 框架异常与未捕获异常会",(0,n.jsx)(e.strong,{children:"同时"}),"进入独立的 ",(0,n.jsx)(e.a,{href:"Errors",children:"Errors"})," 标签页",(0,n.jsx)(e.strong,{children:"去重聚合"}),"(按类型 + 堆栈签名归并);本 Log Viewer 仍会在原始时间流中把它们显示为错误级日志。回答”该崩溃是否反复出现”请用 Errors;查看原始时间线请用 Log Viewer。"]}),"\n"]}),"\n",(0,n.jsx)(e.h2,{id:s[2].id,children:s[2].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(e.table,{children:[(0,n.jsx)(e.thead,{children:(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.th,{children:"Level"}),(0,n.jsx)(e.th,{children:"Abbreviation"}),(0,n.jsx)(e.th,{children:"Color"}),(0,n.jsx)(e.th,{children:"Description"})]})}),(0,n.jsxs)(e.tbody,{children:[(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Verbose"}),(0,n.jsx)(e.td,{children:"V"}),(0,n.jsx)(e.td,{children:"Gray"}),(0,n.jsx)(e.td,{children:"Detailed information / 详细信息"})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Debug"}),(0,n.jsx)(e.td,{children:"D"}),(0,n.jsx)(e.td,{children:"Blue"}),(0,n.jsx)(e.td,{children:"Debug information / 调试信息"})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Info"}),(0,n.jsx)(e.td,{children:"I"}),(0,n.jsx)(e.td,{children:"Green"}),(0,n.jsx)(e.td,{children:"General information / 一般信息"})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Warning"}),(0,n.jsx)(e.td,{children:"W"}),(0,n.jsx)(e.td,{children:"Orange"}),(0,n.jsx)(e.td,{children:"Warning messages / 警告信息"})]}),(0,n.jsxs)(e.tr,{children:[(0,n.jsx)(e.td,{children:"Error"}),(0,n.jsx)(e.td,{children:"E"}),(0,n.jsx)(e.td,{children:"Red"}),(0,n.jsx)(e.td,{children:"Error messages / 错误信息"})]})]})]}),"\n",(0,n.jsxs)(e.blockquote,{children:["\n",(0,n.jsxs)(e.p,{children:["Third-party library logs are categorized as ",(0,n.jsx)(e.strong,{children:"Info"})," level."]}),"\n"]}),"\n",(0,n.jsxs)(e.blockquote,{children:["\n",(0,n.jsxs)(e.p,{children:["第三方日志库的日志统一归类为 ",(0,n.jsx)(e.strong,{children:"Info"})," 级别。"]}),"\n"]}),"\n",(0,n.jsx)(e.h2,{id:s[3].id,children:s[3].value}),"\n",(0,n.jsx)(e.h3,{id:s[4].id,children:s[4].value}),"\n",(0,n.jsxs)(e.ul,{children:["\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"All"}),": Show all logs / 使用 All 显示所有日志"]}),"\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"V / D / I / W / E"}),": Filter by level / 按级别过滤"]}),"\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"Tag dropdown"}),": Filter by any captured tag / 按任意已捕获标签过滤"]}),"\n",(0,n.jsx)(e.li,{children:"Single-select mode / 单选模式"}),"\n"]}),"\n",(0,n.jsx)(e.h3,{id:s[5].id,children:s[5].value}),"\n",(0,n.jsxs)(e.ul,{children:["\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"Auto-scroll toggle"})," (default on): Jump to the newest log as new entries arrive; pause to keep history still for inspection / ",(0,n.jsx)(e.strong,{children:"自动滚动开关"}),"(默认开启):新日志到达时自动跳到最新;暂停可稳定查看历史"]}),"\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"Copy as JSON"}),": Copy all currently filtered logs as JSON / ",(0,n.jsx)(e.strong,{children:"复制为 JSON"}),":将当前过滤后的全部日志复制为 JSON"]}),"\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"Share as Text"}),": Share filtered logs as plain text / ",(0,n.jsx)(e.strong,{children:"分享为文本"}),":将过滤后的日志以纯文本分享"]}),"\n",(0,n.jsxs)(e.li,{children:[(0,n.jsx)(e.strong,{children:"Clear"}),": Clear all logs / ",(0,n.jsx)(e.strong,{children:"清除"}),":清空全部日志"]}),"\n"]}),"\n",(0,n.jsx)(e.h3,{id:s[6].id,children:s[6].value}),"\n",(0,n.jsxs)(e.ul,{children:["\n",(0,n.jsx)(e.li,{children:"Level badge with color / 带颜色的级别徽章"}),"\n",(0,n.jsx)(e.li,{children:"Timestamp display / 时间戳显示"}),"\n",(0,n.jsx)(e.li,{children:"Tag display (if available) / 标签显示(如有)"}),"\n",(0,n.jsx)(e.li,{children:"Error/warning rows have subtle background tint / 错误/警告行有淡色背景"}),"\n",(0,n.jsx)(e.li,{children:"Left border color indicates level / 左侧边框颜色表示级别"}),"\n",(0,n.jsx)(e.li,{children:"Tap a row to open the in-view detail page with full message and copy; use the back button to return / 点击行进入详情页(主视图内切换),可查看完整消息并复制,点返回按钮回到列表"}),"\n",(0,n.jsx)(e.li,{children:"Per-row copy button copies that single log as JSON / 行内复制按钮将该条日志以 JSON 复制"}),"\n"]}),"\n",(0,n.jsx)(e.h3,{id:s[7].id,children:s[7].value}),"\n",(0,n.jsxs)(e.ul,{children:["\n",(0,n.jsx)(e.li,{children:"Fuzzy search by message content or tag / 按消息内容或标签模糊搜索"}),"\n",(0,n.jsxs)(e.li,{children:["Toggle ",(0,n.jsx)(e.strong,{children:"regex mode"})," (",(0,n.jsx)(e.code,{children:". *"})," button) to search with ",(0,n.jsx)(e.code,{children:"RegExp"})," (case-insensitive); invalid patterns degrade gracefully instead of crashing / 点击 ",(0,n.jsx)(e.strong,{children:"正则模式"}),"(",(0,n.jsx)(e.code,{children:". *"})," 按钮)使用 ",(0,n.jsx)(e.code,{children:"RegExp"})," 搜索(不区分大小写);非法图案优雅降级,不会崩溃"]}),"\n",(0,n.jsx)(e.li,{children:"Combined with level and tag filters / 可与级别、标签过滤组合使用"}),"\n"]}),"\n",(0,n.jsx)(e.h2,{id:s[8].id,children:s[8].value}),"\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.strong,{children:"Quick shorthand (recommended) / 简化写法(推荐)"})," — available since v1.1.2 / v1.1.2 起可用:"]}),"\n",(0,n.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(e.code,{children:[(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"v"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Verbose message / 详细消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"d"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Debug message / 调试消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"i"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Info message / 信息消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"w"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Warning message / 警告消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"e"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Error message / 错误消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsx)(e.span,{children:" "}),"\n",(0,n.jsx)(e.span,{children:(0,n.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// With tag / 带标签"})}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"i"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'User logged in'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", tag"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'Auth'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]})]})}),"\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.strong,{children:"Full form / 完整写法"}),":"]}),"\n",(0,n.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(e.code,{children:[(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"verbose"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Verbose message / 详细消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"debug"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Debug message / 调试消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"info"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Info message / 信息消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"warning"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Warning message / 警告消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"error"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Error message / 错误消息'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsx)(e.span,{children:" "}),"\n",(0,n.jsx)(e.span,{children:(0,n.jsx)(e.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// With tag / 带标签"})}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"info"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'User logged in'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", tag"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'Auth'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]})]})}),"\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.code,{children:"InspectorLog"})," is a static wrapper around ",(0,n.jsx)(e.code,{children:"InspectorLogInterceptor.instance"})," for shorter log calls."]}),"\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.code,{children:"InspectorLog"})," 是 ",(0,n.jsx)(e.code,{children:"InspectorLogInterceptor.instance"})," 的静态包装,用于更简短的日志调用。"]}),"\n",(0,n.jsx)(e.h2,{id:s[9].id,children:s[9].value}),"\n",(0,n.jsx)(e.h3,{id:s[10].id,children:s[10].value}),"\n",(0,n.jsxs)(e.p,{children:["No configuration needed. Any library using ",(0,n.jsx)(e.code,{children:"print()"})," or ",(0,n.jsx)(e.code,{children:"debugPrint()"})," is automatically captured."]}),"\n",(0,n.jsxs)(e.p,{children:["无需配置。任何使用 ",(0,n.jsx)(e.code,{children:"print()"})," 或 ",(0,n.jsx)(e.code,{children:"debugPrint()"})," 的库都会被自动捕获。"]}),"\n",(0,n.jsx)(e.h3,{id:s[11].id,children:s[11].value}),"\n",(0,n.jsx)(e.p,{children:"To forward inspector logs to your third-party logger:"}),"\n",(0,n.jsx)(e.p,{children:"将检查器日志转发到第三方日志库:"}),"\n",(0,n.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(e.code,{children:[(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"import"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'package:logger/logger.dart'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]}),"\n",(0,n.jsx)(e.span,{children:" "}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" logger "}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Logger"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsx)(e.span,{children:" "}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.onLogCaptured "}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" (entry) {"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" logger."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"log"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" _mapLogLevel"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(entry.level),"})]}),"\n",(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" '"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"${"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"entry"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"tag"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" != "}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"null"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" ? "}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'["}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"${"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"entry"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"tag"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"] '"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" : "}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"''"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}${"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"entry"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"message"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"}"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsx)(e.span,{children:(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" );"})}),"\n",(0,n.jsx)(e.span,{children:(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"};"})})]})}),"\n",(0,n.jsxs)(e.blockquote,{children:["\n",(0,n.jsxs)(e.p,{children:[(0,n.jsx)(e.strong,{children:"Note"}),": Do NOT call logging methods inside ",(0,n.jsx)(e.code,{children:"onLogCaptured"}),", as this will cause infinite recursion.\n",(0,n.jsx)(e.strong,{children:"注意"}),":不要在 ",(0,n.jsx)(e.code,{children:"onLogCaptured"})," 内部调用日志方法,否则会导致无限递归。"]}),"\n"]}),"\n",(0,n.jsx)(e.h2,{id:s[12].id,children:s[12].value}),"\n",(0,n.jsxs)(e.p,{children:["If using ",(0,n.jsx)(e.code,{children:"runAppWithInspector()"}),", the log interceptor starts automatically. Otherwise:"]}),"\n",(0,n.jsxs)(e.p,{children:["如果使用 ",(0,n.jsx)(e.code,{children:"runAppWithInspector()"}),",日志拦截器会自动启动。否则:"]}),"\n",(0,n.jsx)(e.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsx)(e.code,{children:(0,n.jsxs)(e.span,{children:[(0,n.jsx)(e.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"start"}),(0,n.jsx)(e.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})})})]})},"/Log-Viewer",{filePath:"pages/Log-Viewer.md",timestamp:1787265563e3,pageMap:t.v,frontMatter:{},title:"Log Viewer / 日志查看器"},"undefined"==typeof RemoteContent?h:RemoteContent.useTOC)},7080:function(i,s,e){"use strict";e.d(s,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(i){i.O(0,[812,888,774,179],function(){return i(i.s=3541)}),_N_E=i.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Memory-Viewer-5f8bed1aa6f7d6b5.js b/docs/_next/static/chunks/pages/Memory-Viewer-dd3d33cfe929fd44.js similarity index 93% rename from docs/_next/static/chunks/pages/Memory-Viewer-5f8bed1aa6f7d6b5.js rename to docs/_next/static/chunks/pages/Memory-Viewer-dd3d33cfe929fd44.js index 4471dbe..f5b3ba5 100644 --- a/docs/_next/static/chunks/pages/Memory-Viewer-5f8bed1aa6f7d6b5.js +++ b/docs/_next/static/chunks/pages/Memory-Viewer-dd3d33cfe929fd44.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[878],{4886:function(e,i,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Memory-Viewer",function(){return n(3506)}])},3506:function(e,i,n){"use strict";n.r(i),n.d(i,{useTOC:function(){return a}});var r=n(5893),s=n(7812),t=n(7080),l=n(8925);function a(e){let i={code:"code",...(0,l.a)()};return[{value:"Overview / 概览",id:"overview--概览",depth:2},{value:"Master Switch / 总开关",id:"master-switch--总开关",depth:2},{value:"Features / 功能",id:"features--功能",depth:2},{value:"1. Memory Trend Chart / 内存趋势图",id:"1-memory-trend-chart--内存趋势图",depth:3},{value:"2. Native Memory / Native 内存(真机 100% 可用)",id:"2-native-memory--native-内存真机-100-可用",depth:3},{value:"3. Dart Heap Overview / Dart Heap 概览(需要 VM Service)",id:"3-dart-heap-overview--dart-heap-概览需要-vm-service",depth:3},{value:"4. Heap Generations / 堆分代详情(需要 VM Service)",id:"4-heap-generations--堆分代详情需要-vm-service",depth:3},{value:"5. Manual GC / 手动触发 GC(需要 VM Service)",id:"5-manual-gc--手动触发-gc需要-vm-service",depth:3},{value:"6. Memory Leak Detection / 内存泄漏检测",id:"6-memory-leak-detection--内存泄漏检测",depth:3},{value:"7. Image Cache / 图片缓存",id:"7-image-cache--图片缓存",depth:3},{value:"8. App Storage / 应用存储",id:"8-app-storage--应用存储",depth:3},{value:"⚠️ VM Service Availability / VM Service 可用性",id:"️-vm-service-availability--vm-service-可用性",depth:2},{value:(0,r.jsxs)(r.Fragment,{children:["When debugging via PC with ",(0,r.jsx)(i.code,{children:"flutter run"})," / 通过 PC 用 ",(0,r.jsx)(i.code,{children:"flutter run"})," 调试时"]}),id:"when-debugging-via-pc-with-flutter-run--通过-pc-用-flutter-run-调试时",depth:3},{value:"When opening debug app directly (no PC) / 直接打开 debug 应用(不连 PC)",id:"when-opening-debug-app-directly-no-pc--直接打开-debug-应用不连-pc",depth:3},{value:"Fallback / 降级方案",id:"fallback--降级方案",depth:3},{value:"Platform Support / 平台支持",id:"platform-support--平台支持",depth:2},{value:"Refresh Intervals / 刷新间隔",id:"refresh-intervals--刷新间隔",depth:2},{value:"Related / 相关",id:"related--相关",depth:2}]}i.default=(0,s.c)(function(e){let{toc:i=a(e)}=e,n={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.a)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.h1,{children:"Memory Viewer / 内存监控面板"}),"\n",(0,r.jsx)(n.p,{children:"The Memory Viewer provides comprehensive in-app memory analysis, including trend charts, Dart Heap details, Native memory breakdown, memory leak detection, image cache monitoring, and storage statistics."}),"\n",(0,r.jsx)(n.p,{children:"内存监控面板提供应用内全面内存分析,包括趋势图、Dart Heap 详情、Native 内存分项、内存泄漏检测、图片缓存监控和存储统计。"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Available since v1.1.0"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"v1.1.1 起可用"})}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:i[0].id,children:i[0].value}),"\n",(0,r.jsx)(n.p,{children:"The Memory Viewer is integrated into the inspector panel. Tap the floating inspector button → “Memory” tab to access."}),"\n",(0,r.jsx)(n.p,{children:"内存监控面板集成在检查器面板中。点击悬浮检查器按钮 → “Memory” 标签即可访问。"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"⚠️ Important"}),": Memory monitoring is ",(0,r.jsx)(n.strong,{children:"off by default"}),". You must toggle on the switch at the top of the panel to start collecting data."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"⚠️ 重要"}),":内存监控",(0,r.jsx)(n.strong,{children:"默认关闭"}),"。必须在面板顶部打开开关才会开始采集数据。"]}),"\n",(0,r.jsx)(n.h2,{id:i[1].id,children:i[1].value}),"\n",(0,r.jsx)(n.p,{children:"A switch at the top of the Memory panel controls whether monitoring is enabled."}),"\n",(0,r.jsx)(n.p,{children:"Memory 面板顶部有一个开关,控制是否启用监控。"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"State"}),(0,r.jsx)(n.th,{children:"Behavior"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"OFF (default)"})}),(0,r.jsx)(n.td,{children:"No timers, no VM Service connection, zero overhead / 无定时器、无 VM Service 连接、零开销"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"ON"})}),(0,r.jsx)(n.td,{children:"Starts RSS collection (500ms), Native memory (3s), storage stats (3s), leak detection (2s), and VM Service connection attempt / 启动 RSS 采集(500ms)、Native 内存(3s)、存储统计(3s)、泄漏检测(2s),并尝试连接 VM Service"})]})]})]}),"\n",(0,r.jsx)(n.p,{children:"When turned off, all timers are cancelled and the VM Service connection state is cleared, leaving no residual WebSocket overhead."}),"\n",(0,r.jsx)(n.p,{children:"关闭时所有定时器会被取消,VM Service 连接状态会被清空,不会残留 WebSocket 开销。"}),"\n",(0,r.jsx)(n.h2,{id:i[2].id,children:i[2].value}),"\n",(0,r.jsx)(n.h3,{id:i[3].id,children:i[3].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Real-time line chart with 2-minute history window (240 snapshots \xd7 500ms)"}),"\n",(0,r.jsxs)(n.li,{children:["Switchable between 4 metrics via the metric selector chips:\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Process RSS"})," — Process-level RSS (always available)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Dart Heap"})," — Total Dart Heap usage (requires VM Service)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"New Space"})," — New generation usage (requires VM Service)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Old Space"})," — Old generation usage (requires VM Service)"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"实时折线图,2 分钟历史窗口(240 条快照 \xd7 500ms)。可通过指标选择芯片切换 4 种指标。"}),"\n",(0,r.jsx)(n.h3,{id:i[4].id,children:i[4].value}),"\n",(0,r.jsx)(n.p,{children:"Does not depend on VM Service. Data is collected via Platform Channel:"}),"\n",(0,r.jsx)(n.p,{children:"不依赖 VM Service,通过 Platform Channel 采集:"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Android"})," (",(0,r.jsx)(n.code,{children:"Debug.MemoryInfo"})," + ",(0,r.jsx)(n.code,{children:"/proc/self/status"}),"):"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Total PSS / Dalvik PSS / Native PSS / Native Private Dirty"}),"\n",(0,r.jsxs)(n.li,{children:["Process RSS (from ",(0,r.jsx)(n.code,{children:"/proc/self/status"}),")"]}),"\n",(0,r.jsx)(n.li,{children:"Device memory status (availMem, threshold, lowMemory)"}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"iOS"})," (",(0,r.jsx)(n.code,{children:"mach task_info"}),"):"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Physical Footprint (Apple’s recommended metric)"}),"\n",(0,r.jsx)(n.li,{children:"Internal / Compressed / Resident Size"}),"\n",(0,r.jsx)(n.li,{children:"Process RSS"}),"\n",(0,r.jsx)(n.li,{children:"Device available memory"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:i[5].id,children:i[5].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Heap Usage / Capacity / external usage"}),"\n",(0,r.jsx)(n.li,{children:"Progress bar showing heap usage ratio"}),"\n",(0,r.jsx)(n.li,{children:"Three core metrics with bilingual labels"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"显示 Heap Usage / Capacity / External 三个核心指标 + 进度条。"}),"\n",(0,r.jsx)(n.h3,{id:i[6].id,children:i[6].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"New Space"}),": usage / capacity / external"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Old Space"}),": usage / capacity / external"]}),"\n",(0,r.jsx)(n.li,{children:"Helps identify allocation patterns (frequent new-space churn vs old-space growth)"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"显示新生代/老生代的 Usage / Capacity / External 详细数据。"}),"\n",(0,r.jsx)(n.h3,{id:i[7].id,children:i[7].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"“Trigger GC” button forces a full garbage collection"}),"\n",(0,r.jsx)(n.li,{children:"Disabled (grayed out) when VM Service is unavailable"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"“Trigger GC” 按钮强制触发完整垃圾回收。VM Service 不可用时按钮变灰禁用。"}),"\n",(0,r.jsx)(n.h3,{id:i[8].id,children:i[8].value}),"\n",(0,r.jsxs)(n.p,{children:["Based on Dart 2.17+ ",(0,r.jsx)(n.code,{children:"WeakReference"})," and ",(0,r.jsx)(n.code,{children:"Finalizer"}),". Does NOT depend on VM Service."]}),"\n",(0,r.jsxs)(n.p,{children:["基于 Dart 2.17+ 的 ",(0,r.jsx)(n.code,{children:"WeakReference"})," 和 ",(0,r.jsx)(n.code,{children:"Finalizer"}),",不依赖 VM Service。"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Four-state transition / 四状态流转"}),":"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"State"}),(0,r.jsx)(n.th,{children:"Meaning"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"tracking"})}),(0,r.jsx)(n.td,{children:"Object registered, waiting for expected release time / 对象已注册,等待预期释放时间"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"verifying"})}),(0,r.jsx)(n.td,{children:"Exceeded expected release time, GC verification triggered (if VM Service available) / 超过预期释放时间,触发 GC 验证(VM Service 可用时)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"leaked"})}),(0,r.jsxs)(n.td,{children:["Object still exists after GC — ",(0,r.jsx)(n.strong,{children:"suspected leak"})," / GC 后对象仍存在——",(0,r.jsx)(n.strong,{children:"疑似泄漏"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"released"})}),(0,r.jsx)(n.td,{children:"Object has been garbage collected / 对象已被垃圾回收"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"API / 接口"}),":"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Quick shorthand (recommended) / 简化写法(推荐)"})," — available since v1.1.2 / v1.1.2 起可用:"]}),"\n",(0,r.jsx)(n.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,r.jsxs)(n.code,{children:[(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Extension method on Object / Object 上的扩展方法"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"myBloc."}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"trackMemoryLeak"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(tag"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'HomePage_myBloc'"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,r.jsx)(n.span,{children:" "}),"\n",(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Or top-level function / 或使用顶层函数"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"trackMemoryLeak"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(myBloc, tag"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'HomePage_myBloc'"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,r.jsx)(n.span,{children:" "}),"\n",(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Cancel tracking / 取消追踪"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"myBloc."}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"untrackMemoryLeak"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Full form / 完整写法"}),":"]}),"\n",(0,r.jsx)(n.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,r.jsxs)(n.code,{children:[(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Register an object for leak tracking / 注册对象进行泄漏追踪"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MemoryInspectorService"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"trackObject"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" myController,"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" tag"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'HomeController_textController'"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// optional identifier"})]}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" expectedReleaseAfter"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Duration"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(seconds"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 30"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"), "}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// expected release time"})]}),"\n",(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})}),"\n",(0,r.jsx)(n.span,{children:" "}),"\n",(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Stop tracking a specific object / 停止追踪特定对象"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MemoryInspectorService"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"untrackObject"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(myController);"})]}),"\n",(0,r.jsx)(n.span,{children:" "}),"\n",(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Clear all records / 清空所有记录"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MemoryInspectorService"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"clearLeakRecords"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Limits / 限制"}),":"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Max 500 tracked objects (LRU eviction)"}),"\n",(0,r.jsx)(n.li,{children:"Detection interval: 2 seconds"}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"trackObject()"})," requires user code modification (mild invasion)"]}),"\n"]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Flutter MemoryAllocations bridge (since v1.9.0) / 官方泄漏追踪桥接(v1.9.0 起)"})}),"\n",(0,r.jsxs)(n.p,{children:["The WeakReference state machine has a blind spot: an object whose ",(0,r.jsx)(n.code,{children:"dispose()"})," ran but that has not been GC’d yet still resolves through the weak reference, producing a false “leaked” verdict. Since v1.9.0, ",(0,r.jsx)(n.code,{children:"MemoryInspectorService"})," also subscribes to Flutter’s official ",(0,r.jsx)(n.code,{children:"FlutterMemoryAllocations"})," (the data source behind ",(0,r.jsx)(n.code,{children:"leak_tracker"}),") via ",(0,r.jsx)(n.code,{children:"LeakTrackerBridge"}),": once the official stream reports ",(0,r.jsx)(n.code,{children:"disposed"}),", the object is treated as released (awaiting GC) even if the weak reference still resolves — cutting false positives. Enabled by default through ",(0,r.jsx)(n.code,{children:"enableFlutterLeakTracker"})," in ",(0,r.jsx)(n.code,{children:"init()"}),", and toggled in code via ",(0,r.jsx)(n.code,{children:"MemoryInspectorService.instance.flutterLeakTrackerEnabled"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["WeakReference 状态机有一个盲点:对象已调用 ",(0,r.jsx)(n.code,{children:"dispose()"})," 但尚未被 GC 时弱引用仍存活,会产生”疑似泄漏”的误报。v1.9.0 起,",(0,r.jsx)(n.code,{children:"MemoryInspectorService"})," 通过 ",(0,r.jsx)(n.code,{children:"LeakTrackerBridge"})," 额外订阅 Flutter 官方的 ",(0,r.jsx)(n.code,{children:"FlutterMemoryAllocations"}),"(",(0,r.jsx)(n.code,{children:"leak_tracker"})," 背后的数据源):只要官方上报 ",(0,r.jsx)(n.code,{children:"disposed"}),",即便弱引用仍存活也判定为已释放(等待 GC),从而显著降低误报。由 ",(0,r.jsx)(n.code,{children:"init()"})," 的 ",(0,r.jsx)(n.code,{children:"enableFlutterLeakTracker"})," 默认启用,也可用 ",(0,r.jsx)(n.code,{children:"MemoryInspectorService.instance.flutterLeakTrackerEnabled"})," 在代码中开关。"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:i[9].id,children:i[9].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Real-time Flutter image cache size and count"}),"\n",(0,r.jsx)(n.li,{children:"Pending (loading) / Live (in use) image counts"}),"\n",(0,r.jsx)(n.li,{children:"One-click clear all image cache"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"实时显示 Flutter 图片缓存大小、数量、加载中/使用中状态。一键清理图片缓存。"}),"\n",(0,r.jsx)(n.h3,{id:i[10].id,children:i[10].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Documents directory size"}),"\n",(0,r.jsx)(n.li,{children:"Temp cache directory size"}),"\n",(0,r.jsx)(n.li,{children:"Total database file size"}),"\n",(0,r.jsx)(n.li,{children:"One-click clear temp cache"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"显示文档目录、临时缓存、数据库文件大小。一键清理临时缓存。"}),"\n",(0,r.jsx)(n.h2,{id:i[11].id,children:i[11].value}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Dart Heap data and manual GC require VM Service connection."})," When VM Service is unavailable, these features gracefully degrade to show “N/A”."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Dart Heap 数据和手动 GC 需要 VM Service 连接。"})," VM Service 不可用时,这些功能优雅降级显示 “N/A”。"]}),"\n",(0,r.jsx)(n.h3,{id:i[12].id,children:i[12].value}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Dart VM Heap data may be unavailable (VM: OFF)."})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Dart VM Heap 数据可能不可用(VM: OFF)。"})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Reason / 原因"}),": When using ",(0,r.jsx)(n.code,{children:"flutter run"})," to debug via PC, the flutter tool sets up port forwarding between PC and device via ",(0,r.jsx)(n.code,{children:"adb reverse"}),", allowing PC-side DevTools to access the device’s VM Service. However, ",(0,r.jsx)(n.code,{children:"Service.getInfo()"})," returns a ",(0,r.jsx)(n.code,{children:"serverUri"})," from the PC’s perspective; when the app process internally accesses ",(0,r.jsx)(n.code,{children:"127.0.0.1:PC_port"}),", the device doesn’t have that port listening locally, resulting in ",(0,r.jsx)(n.code,{children:"Connection refused"})," and VM Service showing OFF."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"原因"}),":使用 ",(0,r.jsx)(n.code,{children:"flutter run"})," 连接 PC 调试时,flutter tool 会通过 ",(0,r.jsx)(n.code,{children:"adb reverse"})," 在 PC 和设备之间做端口转发,让 PC 上的 DevTools 能访问设备的 VM Service。但应用进程内部 ",(0,r.jsx)(n.code,{children:"Service.getInfo()"})," 返回的 ",(0,r.jsx)(n.code,{children:"serverUri"})," 是 PC 视角的端口,应用进程访问 ",(0,r.jsx)(n.code,{children:"127.0.0.1:PC端口"})," 时设备本地并没有监听该端口,导致 Connection refused,VM Service 显示 OFF。"]}),"\n",(0,r.jsx)(n.h3,{id:i[13].id,children:i[13].value}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"VM Service works normally."})," No flutter tool is involved, VM Service listens directly on the device’s local port, the app can connect normally, and Dart Heap data displays correctly."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"VM Service 正常工作。"})," 没有 flutter tool 介入,VM Service 直接监听设备本地端口,应用能正常连接,Dart Heap 数据正常显示。"]}),"\n",(0,r.jsx)(n.h3,{id:i[14].id,children:i[14].value}),"\n",(0,r.jsx)(n.p,{children:"When VM Service is unavailable:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"✅ Native memory (Android PSS / iOS physicalFootprint) — still available"}),"\n",(0,r.jsx)(n.li,{children:"✅ Process RSS — always available"}),"\n",(0,r.jsx)(n.li,{children:"✅ Image cache / storage stats — still available"}),"\n",(0,r.jsx)(n.li,{children:"✅ Leak detection — still available (doesn’t depend on VM Service)"}),"\n",(0,r.jsx)(n.li,{children:"❌ Dart Heap details — shows N/A"}),"\n",(0,r.jsx)(n.li,{children:"❌ Manual GC — button disabled"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:i[15].id,children:i[15].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Feature"}),(0,r.jsx)(n.th,{children:"Android"}),(0,r.jsx)(n.th,{children:"iOS"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Process RSS"}),(0,r.jsx)(n.td,{children:"✅"}),(0,r.jsx)(n.td,{children:"✅"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Native Memory"}),(0,r.jsx)(n.td,{children:"✅"}),(0,r.jsx)(n.td,{children:"✅"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Dart Heap (VM Service)"}),(0,r.jsx)(n.td,{children:"✅ (no PC)"}),(0,r.jsx)(n.td,{children:"✅ (no PC)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Manual GC"}),(0,r.jsx)(n.td,{children:"✅ (no PC)"}),(0,r.jsx)(n.td,{children:"✅ (no PC)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Leak Detection"}),(0,r.jsx)(n.td,{children:"✅"}),(0,r.jsx)(n.td,{children:"✅"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Image Cache"}),(0,r.jsx)(n.td,{children:"✅"}),(0,r.jsx)(n.td,{children:"✅"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Storage Stats"}),(0,r.jsx)(n.td,{children:"✅"}),(0,r.jsx)(n.td,{children:"✅"})]})]})]}),"\n",(0,r.jsx)(n.h2,{id:i[16].id,children:i[16].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Data Source"}),(0,r.jsx)(n.th,{children:"Interval"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Process RSS / Dart Heap"}),(0,r.jsx)(n.td,{children:"500ms"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Native Memory (Android/iOS)"}),(0,r.jsx)(n.td,{children:"3000ms"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Storage Stats"}),(0,r.jsx)(n.td,{children:"3000ms"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Leak Detection"}),(0,r.jsx)(n.td,{children:"2000ms"})]})]})]}),"\n",(0,r.jsx)(n.h2,{id:i[17].id,children:i[17].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"Usage",children:"Usage"})," — General usage guide"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"FAQ",children:"FAQ"})," — Common questions"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"Configuration",children:"Configuration"})," — Configuration options"]}),"\n"]})]})},"/Memory-Viewer",{filePath:"pages/Memory-Viewer.md",timestamp:1786753645e3,pageMap:t.v,frontMatter:{},title:"Memory Viewer / 内存监控面板"},"undefined"==typeof RemoteContent?a:RemoteContent.useTOC)},7080:function(e,i,n){"use strict";n.d(i,{v:function(){return r}});let r=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=4886)}),_N_E=e.O()}]); \ No newline at end of file +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[878],{4886:function(e,i,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Memory-Viewer",function(){return n(3506)}])},3506:function(e,i,n){"use strict";n.r(i),n.d(i,{useTOC:function(){return a}});var r=n(5893),s=n(7812),t=n(7080),l=n(8925);function a(e){let i={code:"code",...(0,l.a)()};return[{value:"Overview / 概览",id:"overview--概览",depth:2},{value:"Master Switch / 总开关",id:"master-switch--总开关",depth:2},{value:"Features / 功能",id:"features--功能",depth:2},{value:"1. Memory Trend Chart / 内存趋势图",id:"1-memory-trend-chart--内存趋势图",depth:3},{value:"2. Native Memory / Native 内存(真机 100% 可用)",id:"2-native-memory--native-内存真机-100-可用",depth:3},{value:"3. Dart Heap Overview / Dart Heap 概览(需要 VM Service)",id:"3-dart-heap-overview--dart-heap-概览需要-vm-service",depth:3},{value:"4. Heap Generations / 堆分代详情(需要 VM Service)",id:"4-heap-generations--堆分代详情需要-vm-service",depth:3},{value:"5. Manual GC / 手动触发 GC(需要 VM Service)",id:"5-manual-gc--手动触发-gc需要-vm-service",depth:3},{value:"6. Memory Leak Detection / 内存泄漏检测",id:"6-memory-leak-detection--内存泄漏检测",depth:3},{value:"7. Image Cache / 图片缓存",id:"7-image-cache--图片缓存",depth:3},{value:"8. App Storage / 应用存储",id:"8-app-storage--应用存储",depth:3},{value:"⚠️ VM Service Availability / VM Service 可用性",id:"️-vm-service-availability--vm-service-可用性",depth:2},{value:(0,r.jsxs)(r.Fragment,{children:["When debugging via PC with ",(0,r.jsx)(i.code,{children:"flutter run"})," / 通过 PC 用 ",(0,r.jsx)(i.code,{children:"flutter run"})," 调试时"]}),id:"when-debugging-via-pc-with-flutter-run--通过-pc-用-flutter-run-调试时",depth:3},{value:"When opening debug app directly (no PC) / 直接打开 debug 应用(不连 PC)",id:"when-opening-debug-app-directly-no-pc--直接打开-debug-应用不连-pc",depth:3},{value:"Fallback / 降级方案",id:"fallback--降级方案",depth:3},{value:"Platform Support / 平台支持",id:"platform-support--平台支持",depth:2},{value:"Refresh Intervals / 刷新间隔",id:"refresh-intervals--刷新间隔",depth:2},{value:"Related / 相关",id:"related--相关",depth:2}]}i.default=(0,s.c)(function(e){let{toc:i=a(e)}=e,n={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.a)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.h1,{children:"Memory Viewer / 内存监控面板"}),"\n",(0,r.jsx)(n.p,{children:"The Memory Viewer provides comprehensive in-app memory analysis, including trend charts, Dart Heap details, Native memory breakdown, memory leak detection, image cache monitoring, and storage statistics."}),"\n",(0,r.jsx)(n.p,{children:"内存监控面板提供应用内全面内存分析,包括趋势图、Dart Heap 详情、Native 内存分项、内存泄漏检测、图片缓存监控和存储统计。"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Available since v1.1.0"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"v1.1.1 起可用"})}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:i[0].id,children:i[0].value}),"\n",(0,r.jsx)(n.p,{children:"The Memory Viewer is integrated into the inspector panel. Tap the floating inspector button → “Memory” tab to access."}),"\n",(0,r.jsx)(n.p,{children:"内存监控面板集成在检查器面板中。点击悬浮检查器按钮 → “Memory” 标签即可访问。"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"⚠️ Important"}),": Memory monitoring is ",(0,r.jsx)(n.strong,{children:"off by default"}),". You must toggle on the switch at the top of the panel to start collecting data."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"⚠️ 重要"}),":内存监控",(0,r.jsx)(n.strong,{children:"默认关闭"}),"。必须在面板顶部打开开关才会开始采集数据。"]}),"\n",(0,r.jsx)(n.h2,{id:i[1].id,children:i[1].value}),"\n",(0,r.jsx)(n.p,{children:"A switch at the top of the Memory panel controls whether monitoring is enabled."}),"\n",(0,r.jsx)(n.p,{children:"Memory 面板顶部有一个开关,控制是否启用监控。"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"State"}),(0,r.jsx)(n.th,{children:"Behavior"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"OFF (default)"})}),(0,r.jsx)(n.td,{children:"No timers, no VM Service connection, zero overhead / 无定时器、无 VM Service 连接、零开销"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"ON"})}),(0,r.jsx)(n.td,{children:"Starts RSS collection (500ms), Native memory (3s), storage stats (3s), leak detection (2s), and VM Service connection attempt / 启动 RSS 采集(500ms)、Native 内存(3s)、存储统计(3s)、泄漏检测(2s),并尝试连接 VM Service"})]})]})]}),"\n",(0,r.jsx)(n.p,{children:"When turned off, all timers are cancelled and the VM Service connection state is cleared, leaving no residual WebSocket overhead."}),"\n",(0,r.jsx)(n.p,{children:"关闭时所有定时器会被取消,VM Service 连接状态会被清空,不会残留 WebSocket 开销。"}),"\n",(0,r.jsx)(n.h2,{id:i[2].id,children:i[2].value}),"\n",(0,r.jsx)(n.h3,{id:i[3].id,children:i[3].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Real-time line chart with 2-minute history window (240 snapshots \xd7 500ms)"}),"\n",(0,r.jsxs)(n.li,{children:["Switchable between 4 metrics via the metric selector chips:\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Process RSS"})," — Process-level RSS (always available)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Dart Heap"})," — Total Dart Heap usage (requires VM Service)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"New Space"})," — New generation usage (requires VM Service)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Old Space"})," — Old generation usage (requires VM Service)"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"实时折线图,2 分钟历史窗口(240 条快照 \xd7 500ms)。可通过指标选择芯片切换 4 种指标。"}),"\n",(0,r.jsx)(n.h3,{id:i[4].id,children:i[4].value}),"\n",(0,r.jsx)(n.p,{children:"Does not depend on VM Service. Data is collected via Platform Channel:"}),"\n",(0,r.jsx)(n.p,{children:"不依赖 VM Service,通过 Platform Channel 采集:"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Android"})," (",(0,r.jsx)(n.code,{children:"Debug.MemoryInfo"})," + ",(0,r.jsx)(n.code,{children:"/proc/self/status"}),"):"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Total PSS / Dalvik PSS / Native PSS / Native Private Dirty"}),"\n",(0,r.jsxs)(n.li,{children:["Process RSS (from ",(0,r.jsx)(n.code,{children:"/proc/self/status"}),")"]}),"\n",(0,r.jsx)(n.li,{children:"Device memory status (availMem, threshold, lowMemory)"}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"iOS"})," (",(0,r.jsx)(n.code,{children:"mach task_info"}),"):"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Physical Footprint (Apple’s recommended metric)"}),"\n",(0,r.jsx)(n.li,{children:"Internal / Compressed / Resident Size"}),"\n",(0,r.jsx)(n.li,{children:"Process RSS"}),"\n",(0,r.jsx)(n.li,{children:"Device available memory"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:i[5].id,children:i[5].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Heap Usage / Capacity / external usage"}),"\n",(0,r.jsx)(n.li,{children:"Progress bar showing heap usage ratio"}),"\n",(0,r.jsx)(n.li,{children:"Three core metrics with bilingual labels"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"显示 Heap Usage / Capacity / External 三个核心指标 + 进度条。"}),"\n",(0,r.jsx)(n.h3,{id:i[6].id,children:i[6].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"New Space"}),": usage / capacity / external"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Old Space"}),": usage / capacity / external"]}),"\n",(0,r.jsx)(n.li,{children:"Helps identify allocation patterns (frequent new-space churn vs old-space growth)"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"显示新生代/老生代的 Usage / Capacity / External 详细数据。"}),"\n",(0,r.jsx)(n.h3,{id:i[7].id,children:i[7].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"“Trigger GC” button forces a full garbage collection"}),"\n",(0,r.jsx)(n.li,{children:"Disabled (grayed out) when VM Service is unavailable"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"“Trigger GC” 按钮强制触发完整垃圾回收。VM Service 不可用时按钮变灰禁用。"}),"\n",(0,r.jsx)(n.h3,{id:i[8].id,children:i[8].value}),"\n",(0,r.jsxs)(n.p,{children:["Based on Dart 2.17+ ",(0,r.jsx)(n.code,{children:"WeakReference"})," and ",(0,r.jsx)(n.code,{children:"Finalizer"}),". Does NOT depend on VM Service."]}),"\n",(0,r.jsxs)(n.p,{children:["基于 Dart 2.17+ 的 ",(0,r.jsx)(n.code,{children:"WeakReference"})," 和 ",(0,r.jsx)(n.code,{children:"Finalizer"}),",不依赖 VM Service。"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Four-state transition / 四状态流转"}),":"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"State"}),(0,r.jsx)(n.th,{children:"Meaning"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"tracking"})}),(0,r.jsx)(n.td,{children:"Object registered, waiting for expected release time / 对象已注册,等待预期释放时间"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"verifying"})}),(0,r.jsx)(n.td,{children:"Exceeded expected release time, GC verification triggered (if VM Service available) / 超过预期释放时间,触发 GC 验证(VM Service 可用时)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"leaked"})}),(0,r.jsxs)(n.td,{children:["Object still exists after GC — ",(0,r.jsx)(n.strong,{children:"suspected leak"})," / GC 后对象仍存在——",(0,r.jsx)(n.strong,{children:"疑似泄漏"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"released"})}),(0,r.jsx)(n.td,{children:"Object has been garbage collected / 对象已被垃圾回收"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"API / 接口"}),":"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Quick shorthand (recommended) / 简化写法(推荐)"})," — available since v1.1.2 / v1.1.2 起可用:"]}),"\n",(0,r.jsx)(n.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,r.jsxs)(n.code,{children:[(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Extension method on Object / Object 上的扩展方法"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"myBloc."}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"trackMemoryLeak"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(tag"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'HomePage_myBloc'"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,r.jsx)(n.span,{children:" "}),"\n",(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Or top-level function / 或使用顶层函数"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"trackMemoryLeak"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(myBloc, tag"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'HomePage_myBloc'"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,r.jsx)(n.span,{children:" "}),"\n",(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Cancel tracking / 取消追踪"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"myBloc."}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"untrackMemoryLeak"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Full form / 完整写法"}),":"]}),"\n",(0,r.jsx)(n.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,r.jsxs)(n.code,{children:[(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Register an object for leak tracking / 注册对象进行泄漏追踪"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MemoryInspectorService"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"trackObject"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" myController,"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" tag"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'HomeController_textController'"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// optional identifier"})]}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" expectedReleaseAfter"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" const"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Duration"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(seconds"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" 30"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"), "}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// expected release time"})]}),"\n",(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})}),"\n",(0,r.jsx)(n.span,{children:" "}),"\n",(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Stop tracking a specific object / 停止追踪特定对象"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MemoryInspectorService"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"untrackObject"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(myController);"})]}),"\n",(0,r.jsx)(n.span,{children:" "}),"\n",(0,r.jsx)(n.span,{children:(0,r.jsx)(n.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Clear all records / 清空所有记录"})}),"\n",(0,r.jsxs)(n.span,{children:[(0,r.jsx)(n.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MemoryInspectorService"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"clearLeakRecords"}),(0,r.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Limits / 限制"}),":"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Max 500 tracked objects (LRU eviction)"}),"\n",(0,r.jsx)(n.li,{children:"Detection interval: 2 seconds"}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"trackObject()"})," requires user code modification (mild invasion)"]}),"\n"]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Flutter MemoryAllocations bridge (since v1.9.0) / 官方泄漏追踪桥接(v1.9.0 起)"})}),"\n",(0,r.jsxs)(n.p,{children:["The WeakReference state machine has a blind spot: an object whose ",(0,r.jsx)(n.code,{children:"dispose()"})," ran but that has not been GC’d yet still resolves through the weak reference, producing a false “leaked” verdict. Since v1.9.0, ",(0,r.jsx)(n.code,{children:"MemoryInspectorService"})," also subscribes to Flutter’s official ",(0,r.jsx)(n.code,{children:"FlutterMemoryAllocations"})," (the data source behind ",(0,r.jsx)(n.code,{children:"leak_tracker"}),") via ",(0,r.jsx)(n.code,{children:"LeakTrackerBridge"}),": once the official stream reports ",(0,r.jsx)(n.code,{children:"disposed"}),", the object is treated as released (awaiting GC) even if the weak reference still resolves — cutting false positives. Enabled by default through ",(0,r.jsx)(n.code,{children:"enableFlutterLeakTracker"})," in ",(0,r.jsx)(n.code,{children:"init()"}),", and toggled in code via ",(0,r.jsx)(n.code,{children:"MemoryInspectorService.instance.flutterLeakTrackerEnabled"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["WeakReference 状态机有一个盲点:对象已调用 ",(0,r.jsx)(n.code,{children:"dispose()"})," 但尚未被 GC 时弱引用仍存活,会产生”疑似泄漏”的误报。v1.9.0 起,",(0,r.jsx)(n.code,{children:"MemoryInspectorService"})," 通过 ",(0,r.jsx)(n.code,{children:"LeakTrackerBridge"})," 额外订阅 Flutter 官方的 ",(0,r.jsx)(n.code,{children:"FlutterMemoryAllocations"}),"(",(0,r.jsx)(n.code,{children:"leak_tracker"})," 背后的数据源):只要官方上报 ",(0,r.jsx)(n.code,{children:"disposed"}),",即便弱引用仍存活也判定为已释放(等待 GC),从而显著降低误报。由 ",(0,r.jsx)(n.code,{children:"init()"})," 的 ",(0,r.jsx)(n.code,{children:"enableFlutterLeakTracker"})," 默认启用,也可用 ",(0,r.jsx)(n.code,{children:"MemoryInspectorService.instance.flutterLeakTrackerEnabled"})," 在代码中开关。"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:i[9].id,children:i[9].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Real-time Flutter image cache size and count"}),"\n",(0,r.jsx)(n.li,{children:"Pending (loading) / Live (in use) image counts"}),"\n",(0,r.jsx)(n.li,{children:"One-click clear all image cache"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"实时显示 Flutter 图片缓存大小、数量、加载中/使用中状态。一键清理图片缓存。"}),"\n",(0,r.jsx)(n.h3,{id:i[10].id,children:i[10].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Documents directory size"}),"\n",(0,r.jsx)(n.li,{children:"Temp cache directory size"}),"\n",(0,r.jsx)(n.li,{children:"Total database file size"}),"\n",(0,r.jsx)(n.li,{children:"One-click clear temp cache"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"显示文档目录、临时缓存、数据库文件大小。一键清理临时缓存。"}),"\n",(0,r.jsx)(n.h2,{id:i[11].id,children:i[11].value}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Dart Heap data and manual GC require VM Service connection."})," When VM Service is unavailable, these features gracefully degrade to show “N/A”."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Dart Heap 数据和手动 GC 需要 VM Service 连接。"})," VM Service 不可用时,这些功能优雅降级显示 “N/A”。"]}),"\n",(0,r.jsx)(n.h3,{id:i[12].id,children:i[12].value}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Dart VM Heap data may be unavailable (VM: OFF)."})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Dart VM Heap 数据可能不可用(VM: OFF)。"})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Reason / 原因"}),": When using ",(0,r.jsx)(n.code,{children:"flutter run"})," to debug via PC, the flutter tool sets up port forwarding between PC and device via ",(0,r.jsx)(n.code,{children:"adb reverse"}),", allowing PC-side DevTools to access the device’s VM Service. However, ",(0,r.jsx)(n.code,{children:"Service.getInfo()"})," returns a ",(0,r.jsx)(n.code,{children:"serverUri"})," from the PC’s perspective; when the app process internally accesses ",(0,r.jsx)(n.code,{children:"127.0.0.1:PC_port"}),", the device doesn’t have that port listening locally, resulting in ",(0,r.jsx)(n.code,{children:"Connection refused"})," and VM Service showing OFF."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"原因"}),":使用 ",(0,r.jsx)(n.code,{children:"flutter run"})," 连接 PC 调试时,flutter tool 会通过 ",(0,r.jsx)(n.code,{children:"adb reverse"})," 在 PC 和设备之间做端口转发,让 PC 上的 DevTools 能访问设备的 VM Service。但应用进程内部 ",(0,r.jsx)(n.code,{children:"Service.getInfo()"})," 返回的 ",(0,r.jsx)(n.code,{children:"serverUri"})," 是 PC 视角的端口,应用进程访问 ",(0,r.jsx)(n.code,{children:"127.0.0.1:PC端口"})," 时设备本地并没有监听该端口,导致 Connection refused,VM Service 显示 OFF。"]}),"\n",(0,r.jsx)(n.h3,{id:i[13].id,children:i[13].value}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"VM Service works normally."})," No flutter tool is involved, VM Service listens directly on the device’s local port, the app can connect normally, and Dart Heap data displays correctly."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"VM Service 正常工作。"})," 没有 flutter tool 介入,VM Service 直接监听设备本地端口,应用能正常连接,Dart Heap 数据正常显示。"]}),"\n",(0,r.jsx)(n.h3,{id:i[14].id,children:i[14].value}),"\n",(0,r.jsx)(n.p,{children:"When VM Service is unavailable:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"✅ Native memory (Android PSS / iOS physicalFootprint) — still available"}),"\n",(0,r.jsx)(n.li,{children:"✅ Process RSS — always available"}),"\n",(0,r.jsx)(n.li,{children:"✅ Image cache / storage stats — still available"}),"\n",(0,r.jsx)(n.li,{children:"✅ Leak detection — still available (doesn’t depend on VM Service)"}),"\n",(0,r.jsx)(n.li,{children:"❌ Dart Heap details — shows N/A"}),"\n",(0,r.jsx)(n.li,{children:"❌ Manual GC — button disabled"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:i[15].id,children:i[15].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Feature"}),(0,r.jsx)(n.th,{children:"Android"}),(0,r.jsx)(n.th,{children:"iOS"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Process RSS"}),(0,r.jsx)(n.td,{children:"✅"}),(0,r.jsx)(n.td,{children:"✅"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Native Memory"}),(0,r.jsx)(n.td,{children:"✅"}),(0,r.jsx)(n.td,{children:"✅"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Dart Heap (VM Service)"}),(0,r.jsx)(n.td,{children:"✅ (no PC)"}),(0,r.jsx)(n.td,{children:"✅ (no PC)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Manual GC"}),(0,r.jsx)(n.td,{children:"✅ (no PC)"}),(0,r.jsx)(n.td,{children:"✅ (no PC)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Leak Detection"}),(0,r.jsx)(n.td,{children:"✅"}),(0,r.jsx)(n.td,{children:"✅"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Image Cache"}),(0,r.jsx)(n.td,{children:"✅"}),(0,r.jsx)(n.td,{children:"✅"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Storage Stats"}),(0,r.jsx)(n.td,{children:"✅"}),(0,r.jsx)(n.td,{children:"✅"})]})]})]}),"\n",(0,r.jsx)(n.h2,{id:i[16].id,children:i[16].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Data Source"}),(0,r.jsx)(n.th,{children:"Interval"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Process RSS / Dart Heap"}),(0,r.jsx)(n.td,{children:"500ms"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Native Memory (Android/iOS)"}),(0,r.jsx)(n.td,{children:"3000ms"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Storage Stats"}),(0,r.jsx)(n.td,{children:"3000ms"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Leak Detection"}),(0,r.jsx)(n.td,{children:"2000ms"})]})]})]}),"\n",(0,r.jsx)(n.h2,{id:i[17].id,children:i[17].value}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"Usage",children:"Usage"})," — General usage guide"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"FAQ",children:"FAQ"})," — Common questions"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"Configuration",children:"Configuration"})," — Configuration options"]}),"\n"]})]})},"/Memory-Viewer",{filePath:"pages/Memory-Viewer.md",timestamp:1786753645e3,pageMap:t.v,frontMatter:{},title:"Memory Viewer / 内存监控面板"},"undefined"==typeof RemoteContent?a:RemoteContent.useTOC)},7080:function(e,i,n){"use strict";n.d(i,{v:function(){return r}});let r=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=4886)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Network-Inspector-e55229099cd437f6.js b/docs/_next/static/chunks/pages/Network-Inspector-b7d8a61d2d542ffd.js similarity index 95% rename from docs/_next/static/chunks/pages/Network-Inspector-e55229099cd437f6.js rename to docs/_next/static/chunks/pages/Network-Inspector-b7d8a61d2d542ffd.js index bc3a4b7..679c79c 100644 --- a/docs/_next/static/chunks/pages/Network-Inspector-e55229099cd437f6.js +++ b/docs/_next/static/chunks/pages/Network-Inspector-b7d8a61d2d542ffd.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[310],{3814:function(e,s,i){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Network-Inspector",function(){return i(7643)}])},7643:function(e,s,i){"use strict";i.r(s),i.d(s,{useTOC:function(){return l}});var n=i(5893),r=i(7812),t=i(7080),d=i(8925);function l(e){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"How It Works / 工作原理",id:"how-it-works--工作原理",depth:2},{value:"WebSocket & gRPC Capture / WebSocket 与 gRPC 抓取",id:"websocket--grpc-capture--websocket-与-grpc-抓取",depth:2},{value:"Enable / 开启方式",id:"enable--开启方式",depth:3},{value:"Two Usage Modes / 两种使用方式",id:"two-usage-modes--两种使用方式",depth:3},{value:"What You See / 查看方式",id:"what-you-see--查看方式",depth:3},{value:"Captured Information / 捕获的信息",id:"captured-information--捕获的信息",depth:2},{value:"UI Features / UI 功能",id:"ui-features--ui-功能",depth:2},{value:"Request List / 请求列表",id:"request-list--请求列表",depth:3},{value:"Request Detail / 请求详情",id:"request-detail--请求详情",depth:3},{value:"Search / 搜索",id:"search--搜索",depth:3},{value:"Batch Operations / 批量操作",id:"batch-operations--批量操作",depth:3},{value:"Export & Sensitive-field Masking / 导出与敏感字段遮蔽",id:"export--sensitive-field-masking--导出与敏感字段遮蔽",depth:3},{value:"Copy as cURL / 复制为 cURL",id:"copy-as-curl--复制为-curl",depth:3},{value:"Replay Editor / 重放编辑器",id:"replay-editor--重放编辑器",depth:2},{value:"Status Code Colors / 状态码颜色",id:"status-code-colors--状态码颜色",depth:2},{value:"HTTP Method Colors / HTTP 方法颜色",id:"http-method-colors--http-方法颜色",depth:2},{value:"Usage Example / 使用示例",id:"usage-example--使用示例",depth:2},{value:"Request Interceptor / 请求拦截修改",id:"request-interceptor--请求拦截修改",depth:2},{value:"Workflow / 工作流程",id:"workflow--工作流程",depth:3},{value:"Supported Modifications / 支持的修改",id:"supported-modifications--支持的修改",depth:3},{value:"Rule Matching / 规则匹配",id:"rule-matching--规则匹配",depth:3},{value:"Why GET Requests Cannot Be Modified / 为什么 GET 请求不能修改",id:"why-get-requests-cannot-be-modified--为什么-get-请求不能修改",depth:3},{value:"Master Toggle / 拦截总开关",id:"master-toggle--拦截总开关",depth:3},{value:"Rule Management / 规则管理",id:"rule-management--规则管理",depth:3}]}s.default=(0,r.c)(function(e){let{toc:s=l(e)}=e,i={blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(i.h1,{children:"Network Inspector / 网络检查器"}),"\n",(0,n.jsx)(i.h2,{id:s[0].id,children:s[0].value}),"\n",(0,n.jsxs)(i.p,{children:["The Network Inspector automatically captures all HTTP requests made via the ",(0,n.jsx)(i.strong,{children:"http package"})," and ",(0,n.jsx)(i.strong,{children:"Dio"}),", with zero configuration needed."]}),"\n",(0,n.jsxs)(i.p,{children:["网络检查器自动捕获所有通过 ",(0,n.jsx)(i.strong,{children:"http 包"})," 和 ",(0,n.jsx)(i.strong,{children:"Dio"})," 发送的 HTTP 请求,无需任何配置。"]}),"\n",(0,n.jsx)(i.h2,{id:s[1].id,children:s[1].value}),"\n",(0,n.jsxs)(i.p,{children:["The inspector uses Flutter’s ",(0,n.jsx)(i.code,{children:"HttpOverrides"})," to intercept all HTTP traffic at the ",(0,n.jsx)(i.code,{children:"dart:io"})," level. This means:"]}),"\n",(0,n.jsxs)(i.p,{children:["检查器通过 Flutter 的 ",(0,n.jsx)(i.code,{children:"HttpOverrides"})," 在 ",(0,n.jsx)(i.code,{children:"dart:io"})," 层面拦截所有 HTTP 流量。这意味着:"]}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"http package"}),": Auto-captured ✅ / 自动捕获 ✅"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Dio"}),": Auto-captured (uses IOHttpClientAdapter → HttpClient) ✅ / 自动捕获 ✅"]}),"\n",(0,n.jsx)(i.li,{children:"No manual interceptor setup needed / 无需手动添加拦截器"}),"\n"]}),"\n",(0,n.jsx)(i.h2,{id:s[2].id,children:s[2].value}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"Available since v1.7.0"})," (opt-in, off by default)"]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"v1.7.0 起可用"}),"(可选开启,默认关闭)"]}),"\n"]}),"\n",(0,n.jsxs)(i.p,{children:["HTTP/HTTPS traffic is captured automatically, but the ",(0,n.jsx)(i.code,{children:"HttpOverrides"})," interceptor does ",(0,n.jsx)(i.strong,{children:"not"})," cover streaming protocols like ",(0,n.jsx)(i.strong,{children:"WebSocket"})," and ",(0,n.jsx)(i.strong,{children:"gRPC"}),". For those, enable the opt-in capture so frames and calls show up as ",(0,n.jsx)(i.code,{children:"WS"})," / ",(0,n.jsx)(i.code,{children:"gRPC"})," rows in the Network tab."]}),"\n",(0,n.jsxs)(i.p,{children:["HTTP/HTTPS 流量会自动捕获,但 ",(0,n.jsx)(i.code,{children:"HttpOverrides"})," 拦截器",(0,n.jsx)(i.strong,{children:"无法覆盖"})," WebSocket、gRPC 这类流式协议。针对它们需开启可选的抓取,开启后收发帧/调用会以 ",(0,n.jsx)(i.code,{children:"WS"})," / ",(0,n.jsx)(i.code,{children:"gRPC"})," 行的形式出现在 Network 标签页。"]}),"\n",(0,n.jsx)(i.h3,{id:s[3].id,children:s[3].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:["Toggle the ",(0,n.jsx)(i.strong,{children:"WS"})," switch in the Network tab toolbar, or / 在 Network 标签页工具栏点击 ",(0,n.jsx)(i.strong,{children:"WS"})," 开关,或"]}),"\n",(0,n.jsx)(i.li,{children:"Set it programmatically: / 通过代码开启:"}),"\n"]}),"\n",(0,n.jsx)(i.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(i.code,{children:[(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// on / 开启"})}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"WsInspectorService"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"enable"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// off / 关闭"})}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"WsInspectorService"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"disable"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n",(0,n.jsxs)(i.p,{children:["Capture is ",(0,n.jsx)(i.strong,{children:"off by default"})," and only records while enabled — apps that don’t use these protocols pay nothing."]}),"\n",(0,n.jsxs)(i.p,{children:["抓取",(0,n.jsx)(i.strong,{children:"默认关闭"}),",且只在开启时记录;不使用这类协议的应用零开销。"]}),"\n",(0,n.jsx)(i.h3,{id:s[4].id,children:s[4].value}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsxs)(i.strong,{children:["1. Transparent wrapper — ",(0,n.jsx)(i.code,{children:"InspectorWebSocket"})]})}),"\n",(0,n.jsxs)(i.p,{children:["Replace ",(0,n.jsx)(i.code,{children:"WebSocket.connect"})," with ",(0,n.jsx)(i.code,{children:"InspectorWebSocket.connect"}),". Frames are auto-recorded (",(0,n.jsx)(i.code,{children:"→"})," out, ",(0,n.jsx)(i.code,{children:"←"})," in) when capture is on; when off it passes through with zero overhead."]}),"\n",(0,n.jsxs)(i.p,{children:["将 ",(0,n.jsx)(i.code,{children:"WebSocket.connect"})," 替换为 ",(0,n.jsx)(i.code,{children:"InspectorWebSocket.connect"}),"。开启抓取时会自动记录收发帧(",(0,n.jsx)(i.code,{children:"→"})," 出站、",(0,n.jsx)(i.code,{children:"←"})," 入站);关闭时零开销透传。"]}),"\n",(0,n.jsx)(i.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(i.code,{children:[(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ws "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" await"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" InspectorWebSocket"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"connect"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'wss://echo.websocket.events'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"ws."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"listen"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"((msg) "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"=>"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" print"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'received: "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"$"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"msg"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"));"})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"ws."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"add"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'hello'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"); "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// recorded as an outgoing frame / 记录为出站帧"})]})]})}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsxs)(i.strong,{children:["2. Manual hook — ",(0,n.jsx)(i.code,{children:"recordCall"})]})}),"\n",(0,n.jsxs)(i.p,{children:["For stacks not transparently interceptable by ",(0,n.jsx)(i.code,{children:"dart:io"})," (gRPC, ",(0,n.jsx)(i.code,{children:"web_socket_channel"}),", custom protocols), call ",(0,n.jsx)(i.code,{children:"recordCall"})," to log a request/response pair. No-ops when capture is disabled."]}),"\n",(0,n.jsxs)(i.p,{children:["对于无法被 ",(0,n.jsx)(i.code,{children:"dart:io"})," 透明拦截的栈(gRPC、",(0,n.jsx)(i.code,{children:"web_socket_channel"}),"、自定义协议),调用 ",(0,n.jsx)(i.code,{children:"recordCall"})," 记录一次请求/响应。关闭抓取时为空操作。"]}),"\n",(0,n.jsx)(i.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(i.code,{children:[(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"WsInspectorService"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"recordCall"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" name"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'user.UserService/GetUser'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" request"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" '{ \"id\": 1 }'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" response"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:' \'{ "name": "Ada" }\''}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" protocol"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'gRPC'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// appears in the method column / 显示在 method 列"})]}),"\n",(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})})]})}),"\n",(0,n.jsx)(i.h3,{id:s[5].id,children:s[5].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:["Network tab lists a ",(0,n.jsx)(i.code,{children:"WS"})," (or ",(0,n.jsx)(i.code,{children:"gRPC"}),") entry per connection/call / Network 标签页按连接/调用列出 ",(0,n.jsx)(i.code,{children:"WS"}),"(或 ",(0,n.jsx)(i.code,{children:"gRPC"}),")记录"]}),"\n",(0,n.jsxs)(i.li,{children:["Open the detail view to see the frame log (outgoing ",(0,n.jsx)(i.code,{children:"→"})," / incoming ",(0,n.jsx)(i.code,{children:"←"}),"), accumulated in the response body / 进入详情页查看帧日志(出站 ",(0,n.jsx)(i.code,{children:"→"})," / 入站 ",(0,n.jsx)(i.code,{children:"←"}),"),累积显示在响应体中"]}),"\n",(0,n.jsxs)(i.li,{children:["A ",(0,n.jsx)(i.code,{children:"[connection closed]"})," marker is appended when the socket closes / 连接关闭后会追加 ",(0,n.jsx)(i.code,{children:"[connection closed]"})," 标记"]}),"\n"]}),"\n",(0,n.jsx)(i.h2,{id:s[6].id,children:s[6].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(i.table,{children:[(0,n.jsx)(i.thead,{children:(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.th,{children:"Field"}),(0,n.jsx)(i.th,{children:"Description"})]})}),(0,n.jsxs)(i.tbody,{children:[(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Method"}),(0,n.jsx)(i.td,{children:"GET, POST, PUT, DELETE, PATCH"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"URL"}),(0,n.jsx)(i.td,{children:"Full request URL"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Status Code"}),(0,n.jsx)(i.td,{children:"HTTP response status code"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Duration"}),(0,n.jsx)(i.td,{children:"Request duration"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Request Headers"}),(0,n.jsx)(i.td,{children:"All request headers"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Request Body"}),(0,n.jsx)(i.td,{children:"Request payload (JSON formatted)"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Response Body"}),(0,n.jsx)(i.td,{children:"Response payload (JSON formatted)"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Host"}),(0,n.jsx)(i.td,{children:"Parsed from URL"})]})]})]}),"\n",(0,n.jsx)(i.h2,{id:s[7].id,children:s[7].value}),"\n",(0,n.jsx)(i.h3,{id:s[8].id,children:s[8].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Color-coded by HTTP method / 按 HTTP 方法着色"}),"\n",(0,n.jsx)(i.li,{children:"Status code badge / 状态码徽章"}),"\n",(0,n.jsx)(i.li,{children:"Duration display / 耗时显示"}),"\n",(0,n.jsx)(i.li,{children:"Left border color indicates status / 左侧边框颜色表示状态"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[9].id,children:s[9].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Click a request to enter detail view / 点击请求进入详情视图"}),"\n",(0,n.jsx)(i.li,{children:"Back button to return to list / 返回按钮返回列表"}),"\n",(0,n.jsx)(i.li,{children:"Request and response sections / 请求和响应分段显示"}),"\n",(0,n.jsx)(i.li,{children:"JSON formatted body / JSON 格式化显示"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[10].id,children:s[10].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Fuzzy search by URL or method / 按 URL 或方法模糊搜索"}),"\n",(0,n.jsx)(i.li,{children:"Search bar hidden in detail view / 详情视图隐藏搜索栏"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[11].id,children:s[11].value}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"Available since v1.3.0"})}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"v1.3.0 起可用"})}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"The request list supports a selection mode for operating on multiple requests at once."}),"\n",(0,n.jsx)(i.p,{children:"请求列表支持选择模式,可一次性操作多条请求。"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:["Tap the ",(0,n.jsx)(i.strong,{children:"selection icon"})," in the toolbar to enter selection mode / 点击工具栏的",(0,n.jsx)(i.strong,{children:"选择图标"}),"进入选择模式"]}),"\n",(0,n.jsxs)(i.li,{children:["Check/uncheck items; a top batch bar shows ",(0,n.jsx)(i.strong,{children:"Select all"})," / ",(0,n.jsx)(i.strong,{children:"Cancel"})," / 勾选/取消勾选;顶部批量条提供",(0,n.jsx)(i.strong,{children:"全选"})," / ",(0,n.jsx)(i.strong,{children:"取消"})]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Batch “Copy as cURL”"}),": copies all selected requests as cURL commands / ",(0,n.jsx)(i.strong,{children:"批量「Copy as cURL」"}),":将所有选中请求复制为 cURL 命令"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Batch delete"}),": removes selected requests from the list / ",(0,n.jsx)(i.strong,{children:"批量删除"}),":从列表中移除选中请求"]}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[12].id,children:s[12].value}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"Available since v1.3.0"})}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"v1.3.0 起可用"})}),"\n"]}),"\n",(0,n.jsxs)(i.p,{children:["The toolbar includes an ",(0,n.jsx)(i.strong,{children:"eye toggle"})," that controls whether sensitive headers are masked on export."]}),"\n",(0,n.jsxs)(i.p,{children:["工具栏包含",(0,n.jsx)(i.strong,{children:"眼睛开关"}),",控制导出时是否遮蔽敏感请求头。"]}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Default: off (fully visible)"})," — cURL / JSON / HAR reflect the real request verbatim / ",(0,n.jsx)(i.strong,{children:"默认关闭(完整可见)"}),":cURL / JSON / HAR 原样反映真实请求"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"On"}),": ",(0,n.jsx)(i.code,{children:"toCurl"})," / ",(0,n.jsx)(i.code,{children:"netToJson"})," / ",(0,n.jsx)(i.code,{children:"netToHar"})," / ",(0,n.jsx)(i.code,{children:"copyNet"})," / ",(0,n.jsx)(i.code,{children:"exportNetToFile"})," mask these headers / ",(0,n.jsx)(i.strong,{children:"开启后"}),":以下请求头被遮蔽:\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.code,{children:"Authorization"}),", ",(0,n.jsx)(i.code,{children:"Cookie"}),", ",(0,n.jsx)(i.code,{children:"Set-Cookie"}),", ",(0,n.jsx)(i.code,{children:"Proxy-Authorization"}),", ",(0,n.jsx)(i.code,{children:"X-Auth-Token"}),", ",(0,n.jsx)(i.code,{children:"X-CSRF-Token"}),", ",(0,n.jsx)(i.code,{children:"X-XSRF-Token"})]}),"\n"]}),"\n"]}),"\n",(0,n.jsx)(i.li,{children:"The detail page “Copy as cURL” / “Copy as JSON” follow the same toggle / 详情页「Copy as cURL」「Copy as JSON」跟随同一开关"}),"\n",(0,n.jsxs)(i.li,{children:["When masking is active, the snackbar notes ",(0,n.jsx)(i.strong,{children:"(sensitive hidden)"})," / 遮蔽开启时,snackbar 提示 ",(0,n.jsx)(i.strong,{children:"(sensitive hidden)"})]}),"\n"]}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsxs)(i.p,{children:["Tip: keep masking ",(0,n.jsx)(i.strong,{children:"on"})," before pasting cURL/JSON into terminals, chat, or issue trackers to avoid leaking credentials."]}),"\n",(0,n.jsx)(i.p,{children:"提示:将 cURL/JSON 粘贴到终端、聊天或 issue 前,建议开启遮蔽,避免凭据泄露。"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[13].id,children:s[13].value}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"Available since v1.3.0"})}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"v1.3.0 起可用"})}),"\n"]}),"\n",(0,n.jsxs)(i.p,{children:["From the request detail view, tap ",(0,n.jsx)(i.strong,{children:"Copy as cURL"})," to copy the request as a ready-to-run cURL command (method, URL, headers, body). Respects the sensitive-field masking toggle above."]}),"\n",(0,n.jsxs)(i.p,{children:["在请求详情页点击 ",(0,n.jsx)(i.strong,{children:"Copy as cURL"}),",即可将请求复制为可直接运行的 cURL 命令(方法、URL、请求头、请求体),并遵循上方的敏感字段遮蔽开关。"]}),"\n",(0,n.jsx)(i.h2,{id:s[14].id,children:s[14].value}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"Available since v1.11.0"})}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"v1.11.0 起可用"})}),"\n"]}),"\n",(0,n.jsxs)(i.p,{children:["Every request detail view has a ",(0,n.jsx)(i.strong,{children:"replay"})," action (↻ icon). It opens an editable sheet that lets you ",(0,n.jsx)(i.strong,{children:"re-issue the captured request in-app"})," and preview the response — handy for quickly re-running an endpoint without leaving the app."]}),"\n",(0,n.jsxs)(i.p,{children:["每个请求详情页都有",(0,n.jsx)(i.strong,{children:"重放"}),"操作(↻ 图标)。它会打开一个可编辑弹层,让你",(0,n.jsx)(i.strong,{children:"在 App 内重新发出该请求"}),"并预览响应——无需离开应用即可快速复跑某个接口。"]}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Only the URL query parameters are editable"})," — add / edit / remove ",(0,n.jsx)(i.code,{children:"key=value"})," rows; the URL is rebuilt from them on send / ",(0,n.jsx)(i.strong,{children:"仅 URL 查询参数可编辑"}),"——可增删改 ",(0,n.jsx)(i.code,{children:"key=value"})," 行,发送时用其重建 URL"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"URL, request headers and request body are read-only"})," by design (the captured values are re-sent verbatim) / ",(0,n.jsx)(i.strong,{children:"URL、请求头与请求体按设计只读"}),"(以捕获到的原值原样重发)"]}),"\n",(0,n.jsxs)(i.li,{children:["On ",(0,n.jsx)(i.strong,{children:"Send"}),", the request is re-issued with the original method / headers / body and the edited query string; the sheet shows the returned status code, elapsed time, and a response preview / 点击 ",(0,n.jsx)(i.strong,{children:"Send"})," 后用原始方法 / 请求头 / 请求体加上修改后的查询串重新发出,弹层展示返回状态码、耗时与响应预览"]}),"\n"]}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsxs)(i.p,{children:["This is distinct from the interceptor (which builds a durable modification rule applied to ",(0,n.jsx)(i.em,{children:"future"})," matching requests). The replay editor makes a ",(0,n.jsx)(i.strong,{children:"one-off"})," call with editable query params — including GET requests, whose params can’t be changed by the interceptor."]}),"\n",(0,n.jsxs)(i.p,{children:["它与拦截器不同(拦截器生成作用于",(0,n.jsx)(i.em,{children:"后续"}),"匹配请求的持久规则)。重放编辑器发起的是",(0,n.jsx)(i.strong,{children:"一次性"}),"调用,仅查询参数可改——包括 GET 请求(其参数拦截器无法修改)。"]}),"\n"]}),"\n",(0,n.jsx)(i.h2,{id:s[15].id,children:s[15].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(i.table,{children:[(0,n.jsx)(i.thead,{children:(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.th,{children:"Range"}),(0,n.jsx)(i.th,{children:"Color"}),(0,n.jsx)(i.th,{children:"Description"})]})}),(0,n.jsxs)(i.tbody,{children:[(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"2xx"}),(0,n.jsx)(i.td,{children:"Green"}),(0,n.jsx)(i.td,{children:"Success / 成功"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"3xx"}),(0,n.jsx)(i.td,{children:"Blue"}),(0,n.jsx)(i.td,{children:"Redirect / 重定向"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"4xx"}),(0,n.jsx)(i.td,{children:"Orange"}),(0,n.jsx)(i.td,{children:"Client error / 客户端错误"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"5xx"}),(0,n.jsx)(i.td,{children:"Red"}),(0,n.jsx)(i.td,{children:"Server error / 服务器错误"})]})]})]}),"\n",(0,n.jsx)(i.h2,{id:s[16].id,children:s[16].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(i.table,{children:[(0,n.jsx)(i.thead,{children:(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.th,{children:"Method"}),(0,n.jsx)(i.th,{children:"Color"})]})}),(0,n.jsxs)(i.tbody,{children:[(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"GET"}),(0,n.jsx)(i.td,{children:"Blue / 蓝色"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"POST"}),(0,n.jsx)(i.td,{children:"Green / 绿色"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"PUT"}),(0,n.jsx)(i.td,{children:"Orange / 橙色"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"DELETE"}),(0,n.jsx)(i.td,{children:"Red / 红色"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"PATCH"}),(0,n.jsx)(i.td,{children:"Purple / 紫色"})]})]})]}),"\n",(0,n.jsx)(i.h2,{id:s[17].id,children:s[17].value}),"\n",(0,n.jsx)(i.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(i.code,{children:[(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// http package - auto-captured / http 包 - 自动捕获"})}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" response "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" await"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" http."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"get"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Uri"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"parse"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'https://api.example.com/data'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"),"})]}),"\n",(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})}),"\n",(0,n.jsx)(i.span,{children:" "}),"\n",(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Dio - auto-captured / Dio - 自动捕获"})}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" response "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" await"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" dio."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"post"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'https://api.example.com/data'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" data"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'key'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'value'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"},"})]}),"\n",(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})})]})}),"\n",(0,n.jsx)(i.p,{children:"No additional setup required! All requests will appear in the Network tab."}),"\n",(0,n.jsx)(i.p,{children:"无需额外配置!所有请求都会出现在 Network 标签页中。"}),"\n",(0,n.jsx)(i.h2,{id:s[18].id,children:s[18].value}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"Available since v1.0.7"})," (response fields locked to read-only since v1.0.8)"]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"v1.0.7 起可用"}),"(v1.0.8 起响应字段锁定为只读)"]}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"The inspector supports intercepting and modifying network requests via rules. This is useful for testing different request parameters without modifying app code."}),"\n",(0,n.jsx)(i.p,{children:"检查器支持通过规则拦截并修改网络请求,适合在不修改应用代码的情况下测试不同的请求参数。"}),"\n",(0,n.jsx)(i.h3,{id:s[19].id,children:s[19].value}),"\n",(0,n.jsxs)(i.ol,{children:["\n",(0,n.jsx)(i.li,{children:"Send a request normally (it will be captured in the Network panel) / 正常发送请求(会被捕获到 Network 面板)"}),"\n",(0,n.jsx)(i.li,{children:"Open the request detail and tap the Interceptor icon / 打开请求详情,点击拦截器图标"}),"\n",(0,n.jsx)(i.li,{children:"Configure the modification rule (URL pattern, HTTP method, request modifications) / 配置修改规则(URL 模式、HTTP 方法、请求修改)"}),"\n",(0,n.jsx)(i.li,{children:"Save the rule — subsequent matching requests will use the modified parameters / 保存规则——后续匹配的请求将使用修改后的参数"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[20].id,children:s[20].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(i.table,{children:[(0,n.jsx)(i.thead,{children:(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.th,{children:"Field"}),(0,n.jsx)(i.th,{children:"Editable"}),(0,n.jsx)(i.th,{children:"Notes"})]})}),(0,n.jsxs)(i.tbody,{children:[(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:(0,n.jsx)(i.strong,{children:"Request Body"})}),(0,n.jsx)(i.td,{children:"✅"}),(0,n.jsx)(i.td,{children:"Only for requests with body (POST, PUT, PATCH, etc.) / 仅适用于有 body 的请求"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:(0,n.jsx)(i.strong,{children:"Request Headers"})}),(0,n.jsx)(i.td,{children:"✅"}),(0,n.jsx)(i.td,{children:"Add / modify / remove headers / 新增 / 修改 / 删除请求头"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"URL"}),(0,n.jsx)(i.td,{children:"❌"}),(0,n.jsx)(i.td,{children:"Grayed out, read-only / 灰色不可编辑"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Response Status Code"}),(0,n.jsx)(i.td,{children:"❌"}),(0,n.jsx)(i.td,{children:"Read-only since v1.0.8 / v1.0.8 起只读"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Response Body"}),(0,n.jsx)(i.td,{children:"❌"}),(0,n.jsx)(i.td,{children:"Read-only since v1.0.8 / v1.0.8 起只读"})]})]})]}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsxs)(i.p,{children:["The interception edit panel only allows modifying the ",(0,n.jsx)(i.strong,{children:"request body"})," and ",(0,n.jsx)(i.strong,{children:"request headers"}),". Response fields (status code, response body) are grayed out and uneditable."]}),"\n",(0,n.jsxs)(i.p,{children:["拦截编辑面板仅允许修改",(0,n.jsx)(i.strong,{children:"请求体"}),"和",(0,n.jsx)(i.strong,{children:"请求头"}),"。响应字段(状态码、响应体)灰色不可编辑。"]}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[21].id,children:s[21].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"URL pattern matching"}),": exact match or regex / URL 模式匹配:精确匹配或正则匹配"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"HTTP method filtering"}),": GET, POST, PUT, DELETE, PATCH, HEAD, or Any / HTTP 方法过滤"]}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[22].id,children:s[22].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"The interceptor currently supports modifying request body and headers only / 拦截器目前仅支持修改请求体和请求头"}),"\n",(0,n.jsx)(i.li,{children:"GET requests don’t have a request body / GET 请求没有请求体"}),"\n",(0,n.jsx)(i.li,{children:"Modifying GET request parameters would require URL modification / 修改 GET 请求参数需要修改 URL"}),"\n",(0,n.jsx)(i.li,{children:"URL modification may cause unexpected issues with request routing and parameter encoding / 修改 URL 可能导致请求路由和参数编码的意外问题"}),"\n"]}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsx)(i.p,{children:"GET request detail pages do not display interception edit buttons, making them unmodifiable."}),"\n",(0,n.jsx)(i.p,{children:"GET 请求详情页不显示拦截编辑按钮,因此无法修改。"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[23].id,children:s[23].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:["The interception master toggle is displayed ",(0,n.jsx)(i.strong,{children:"only on the Network list page"}),", not in the detail page / 拦截总开关",(0,n.jsx)(i.strong,{children:"只显示在 Network 列表页"}),",不在详情页"]}),"\n",(0,n.jsx)(i.li,{children:"Rules are only applied when modification mode is enabled / 规则仅在启用修改模式时生效"}),"\n",(0,n.jsx)(i.li,{children:"When no rules are configured or rules are disabled, all requests are sent normally without any modification / 未配置规则或禁用规则时,所有请求正常发送,不做任何修改"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[24].id,children:s[24].value}),"\n",(0,n.jsx)(i.p,{children:"The network panel includes an interceptor rule editor where you can:"}),"\n",(0,n.jsx)(i.p,{children:"网络面板包含拦截规则编辑器,可以:"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Create / edit / delete rules / 创建 / 编辑 / 删除规则"}),"\n",(0,n.jsx)(i.li,{children:"Enable / disable individual rules / 启用 / 禁用单条规则"}),"\n",(0,n.jsx)(i.li,{children:"View rule status indicators in the request list / 在请求列表中查看规则状态标识"}),"\n"]})]})},"/Network-Inspector",{filePath:"pages/Network-Inspector.md",timestamp:1788107481e3,pageMap:t.v,frontMatter:{},title:"Network Inspector / 网络检查器"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,s,i){"use strict";i.d(s,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=3814)}),_N_E=e.O()}]); \ No newline at end of file +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[310],{3814:function(e,s,i){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Network-Inspector",function(){return i(7643)}])},7643:function(e,s,i){"use strict";i.r(s),i.d(s,{useTOC:function(){return l}});var n=i(5893),r=i(7812),t=i(7080),d=i(8925);function l(e){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"How It Works / 工作原理",id:"how-it-works--工作原理",depth:2},{value:"WebSocket & gRPC Capture / WebSocket 与 gRPC 抓取",id:"websocket--grpc-capture--websocket-与-grpc-抓取",depth:2},{value:"Enable / 开启方式",id:"enable--开启方式",depth:3},{value:"Two Usage Modes / 两种使用方式",id:"two-usage-modes--两种使用方式",depth:3},{value:"What You See / 查看方式",id:"what-you-see--查看方式",depth:3},{value:"Captured Information / 捕获的信息",id:"captured-information--捕获的信息",depth:2},{value:"UI Features / UI 功能",id:"ui-features--ui-功能",depth:2},{value:"Request List / 请求列表",id:"request-list--请求列表",depth:3},{value:"Request Detail / 请求详情",id:"request-detail--请求详情",depth:3},{value:"Search / 搜索",id:"search--搜索",depth:3},{value:"Batch Operations / 批量操作",id:"batch-operations--批量操作",depth:3},{value:"Export & Sensitive-field Masking / 导出与敏感字段遮蔽",id:"export--sensitive-field-masking--导出与敏感字段遮蔽",depth:3},{value:"Copy as cURL / 复制为 cURL",id:"copy-as-curl--复制为-curl",depth:3},{value:"Replay Editor / 重放编辑器",id:"replay-editor--重放编辑器",depth:2},{value:"Status Code Colors / 状态码颜色",id:"status-code-colors--状态码颜色",depth:2},{value:"HTTP Method Colors / HTTP 方法颜色",id:"http-method-colors--http-方法颜色",depth:2},{value:"Usage Example / 使用示例",id:"usage-example--使用示例",depth:2},{value:"Request Interceptor / 请求拦截修改",id:"request-interceptor--请求拦截修改",depth:2},{value:"Workflow / 工作流程",id:"workflow--工作流程",depth:3},{value:"Supported Modifications / 支持的修改",id:"supported-modifications--支持的修改",depth:3},{value:"Rule Matching / 规则匹配",id:"rule-matching--规则匹配",depth:3},{value:"Why GET Requests Cannot Be Modified / 为什么 GET 请求不能修改",id:"why-get-requests-cannot-be-modified--为什么-get-请求不能修改",depth:3},{value:"Master Toggle / 拦截总开关",id:"master-toggle--拦截总开关",depth:3},{value:"Rule Management / 规则管理",id:"rule-management--规则管理",depth:3}]}s.default=(0,r.c)(function(e){let{toc:s=l(e)}=e,i={blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(i.h1,{children:"Network Inspector / 网络检查器"}),"\n",(0,n.jsx)(i.h2,{id:s[0].id,children:s[0].value}),"\n",(0,n.jsxs)(i.p,{children:["The Network Inspector automatically captures all HTTP requests made via the ",(0,n.jsx)(i.strong,{children:"http package"})," and ",(0,n.jsx)(i.strong,{children:"Dio"}),", with zero configuration needed."]}),"\n",(0,n.jsxs)(i.p,{children:["网络检查器自动捕获所有通过 ",(0,n.jsx)(i.strong,{children:"http 包"})," 和 ",(0,n.jsx)(i.strong,{children:"Dio"})," 发送的 HTTP 请求,无需任何配置。"]}),"\n",(0,n.jsx)(i.h2,{id:s[1].id,children:s[1].value}),"\n",(0,n.jsxs)(i.p,{children:["The inspector uses Flutter’s ",(0,n.jsx)(i.code,{children:"HttpOverrides"})," to intercept all HTTP traffic at the ",(0,n.jsx)(i.code,{children:"dart:io"})," level. This means:"]}),"\n",(0,n.jsxs)(i.p,{children:["检查器通过 Flutter 的 ",(0,n.jsx)(i.code,{children:"HttpOverrides"})," 在 ",(0,n.jsx)(i.code,{children:"dart:io"})," 层面拦截所有 HTTP 流量。这意味着:"]}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"http package"}),": Auto-captured ✅ / 自动捕获 ✅"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Dio"}),": Auto-captured (uses IOHttpClientAdapter → HttpClient) ✅ / 自动捕获 ✅"]}),"\n",(0,n.jsx)(i.li,{children:"No manual interceptor setup needed / 无需手动添加拦截器"}),"\n"]}),"\n",(0,n.jsx)(i.h2,{id:s[2].id,children:s[2].value}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"Available since v1.7.0"})," (opt-in, off by default)"]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"v1.7.0 起可用"}),"(可选开启,默认关闭)"]}),"\n"]}),"\n",(0,n.jsxs)(i.p,{children:["HTTP/HTTPS traffic is captured automatically, but the ",(0,n.jsx)(i.code,{children:"HttpOverrides"})," interceptor does ",(0,n.jsx)(i.strong,{children:"not"})," cover streaming protocols like ",(0,n.jsx)(i.strong,{children:"WebSocket"})," and ",(0,n.jsx)(i.strong,{children:"gRPC"}),". For those, enable the opt-in capture so frames and calls show up as ",(0,n.jsx)(i.code,{children:"WS"})," / ",(0,n.jsx)(i.code,{children:"gRPC"})," rows in the Network tab."]}),"\n",(0,n.jsxs)(i.p,{children:["HTTP/HTTPS 流量会自动捕获,但 ",(0,n.jsx)(i.code,{children:"HttpOverrides"})," 拦截器",(0,n.jsx)(i.strong,{children:"无法覆盖"})," WebSocket、gRPC 这类流式协议。针对它们需开启可选的抓取,开启后收发帧/调用会以 ",(0,n.jsx)(i.code,{children:"WS"})," / ",(0,n.jsx)(i.code,{children:"gRPC"})," 行的形式出现在 Network 标签页。"]}),"\n",(0,n.jsx)(i.h3,{id:s[3].id,children:s[3].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:["Toggle the ",(0,n.jsx)(i.strong,{children:"WS"})," switch in the Network tab toolbar, or / 在 Network 标签页工具栏点击 ",(0,n.jsx)(i.strong,{children:"WS"})," 开关,或"]}),"\n",(0,n.jsx)(i.li,{children:"Set it programmatically: / 通过代码开启:"}),"\n"]}),"\n",(0,n.jsx)(i.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(i.code,{children:[(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// on / 开启"})}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"WsInspectorService"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"enable"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// off / 关闭"})}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"WsInspectorService"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"disable"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]})]})}),"\n",(0,n.jsxs)(i.p,{children:["Capture is ",(0,n.jsx)(i.strong,{children:"off by default"})," and only records while enabled — apps that don’t use these protocols pay nothing."]}),"\n",(0,n.jsxs)(i.p,{children:["抓取",(0,n.jsx)(i.strong,{children:"默认关闭"}),",且只在开启时记录;不使用这类协议的应用零开销。"]}),"\n",(0,n.jsx)(i.h3,{id:s[4].id,children:s[4].value}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsxs)(i.strong,{children:["1. Transparent wrapper — ",(0,n.jsx)(i.code,{children:"InspectorWebSocket"})]})}),"\n",(0,n.jsxs)(i.p,{children:["Replace ",(0,n.jsx)(i.code,{children:"WebSocket.connect"})," with ",(0,n.jsx)(i.code,{children:"InspectorWebSocket.connect"}),". Frames are auto-recorded (",(0,n.jsx)(i.code,{children:"→"})," out, ",(0,n.jsx)(i.code,{children:"←"})," in) when capture is on; when off it passes through with zero overhead."]}),"\n",(0,n.jsxs)(i.p,{children:["将 ",(0,n.jsx)(i.code,{children:"WebSocket.connect"})," 替换为 ",(0,n.jsx)(i.code,{children:"InspectorWebSocket.connect"}),"。开启抓取时会自动记录收发帧(",(0,n.jsx)(i.code,{children:"→"})," 出站、",(0,n.jsx)(i.code,{children:"←"})," 入站);关闭时零开销透传。"]}),"\n",(0,n.jsx)(i.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(i.code,{children:[(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ws "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" await"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" InspectorWebSocket"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"connect"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'wss://echo.websocket.events'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"ws."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"listen"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"((msg) "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"=>"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" print"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'received: "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"$"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"msg"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"));"})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"ws."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"add"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'hello'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"); "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// recorded as an outgoing frame / 记录为出站帧"})]})]})}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsxs)(i.strong,{children:["2. Manual hook — ",(0,n.jsx)(i.code,{children:"recordCall"})]})}),"\n",(0,n.jsxs)(i.p,{children:["For stacks not transparently interceptable by ",(0,n.jsx)(i.code,{children:"dart:io"})," (gRPC, ",(0,n.jsx)(i.code,{children:"web_socket_channel"}),", custom protocols), call ",(0,n.jsx)(i.code,{children:"recordCall"})," to log a request/response pair. No-ops when capture is disabled."]}),"\n",(0,n.jsxs)(i.p,{children:["对于无法被 ",(0,n.jsx)(i.code,{children:"dart:io"})," 透明拦截的栈(gRPC、",(0,n.jsx)(i.code,{children:"web_socket_channel"}),"、自定义协议),调用 ",(0,n.jsx)(i.code,{children:"recordCall"})," 记录一次请求/响应。关闭抓取时为空操作。"]}),"\n",(0,n.jsx)(i.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(i.code,{children:[(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"WsInspectorService"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"recordCall"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" name"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'user.UserService/GetUser'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" request"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" '{ \"id\": 1 }'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" response"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:' \'{ "name": "Ada" }\''}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" protocol"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'gRPC'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:", "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// appears in the method column / 显示在 method 列"})]}),"\n",(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})})]})}),"\n",(0,n.jsx)(i.h3,{id:s[5].id,children:s[5].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:["Network tab lists a ",(0,n.jsx)(i.code,{children:"WS"})," (or ",(0,n.jsx)(i.code,{children:"gRPC"}),") entry per connection/call / Network 标签页按连接/调用列出 ",(0,n.jsx)(i.code,{children:"WS"}),"(或 ",(0,n.jsx)(i.code,{children:"gRPC"}),")记录"]}),"\n",(0,n.jsxs)(i.li,{children:["Open the detail view to see the frame log (outgoing ",(0,n.jsx)(i.code,{children:"→"})," / incoming ",(0,n.jsx)(i.code,{children:"←"}),"), accumulated in the response body / 进入详情页查看帧日志(出站 ",(0,n.jsx)(i.code,{children:"→"})," / 入站 ",(0,n.jsx)(i.code,{children:"←"}),"),累积显示在响应体中"]}),"\n",(0,n.jsxs)(i.li,{children:["A ",(0,n.jsx)(i.code,{children:"[connection closed]"})," marker is appended when the socket closes / 连接关闭后会追加 ",(0,n.jsx)(i.code,{children:"[connection closed]"})," 标记"]}),"\n"]}),"\n",(0,n.jsx)(i.h2,{id:s[6].id,children:s[6].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(i.table,{children:[(0,n.jsx)(i.thead,{children:(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.th,{children:"Field"}),(0,n.jsx)(i.th,{children:"Description"})]})}),(0,n.jsxs)(i.tbody,{children:[(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Method"}),(0,n.jsx)(i.td,{children:"GET, POST, PUT, DELETE, PATCH"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"URL"}),(0,n.jsx)(i.td,{children:"Full request URL"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Status Code"}),(0,n.jsx)(i.td,{children:"HTTP response status code"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Duration"}),(0,n.jsx)(i.td,{children:"Request duration"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Request Headers"}),(0,n.jsx)(i.td,{children:"All request headers"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Request Body"}),(0,n.jsx)(i.td,{children:"Request payload (JSON formatted)"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Response Body"}),(0,n.jsx)(i.td,{children:"Response payload (JSON formatted)"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Host"}),(0,n.jsx)(i.td,{children:"Parsed from URL"})]})]})]}),"\n",(0,n.jsx)(i.h2,{id:s[7].id,children:s[7].value}),"\n",(0,n.jsx)(i.h3,{id:s[8].id,children:s[8].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Color-coded by HTTP method / 按 HTTP 方法着色"}),"\n",(0,n.jsx)(i.li,{children:"Status code badge / 状态码徽章"}),"\n",(0,n.jsx)(i.li,{children:"Duration display / 耗时显示"}),"\n",(0,n.jsx)(i.li,{children:"Left border color indicates status / 左侧边框颜色表示状态"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[9].id,children:s[9].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Click a request to enter detail view / 点击请求进入详情视图"}),"\n",(0,n.jsx)(i.li,{children:"Back button to return to list / 返回按钮返回列表"}),"\n",(0,n.jsx)(i.li,{children:"Request and response sections / 请求和响应分段显示"}),"\n",(0,n.jsx)(i.li,{children:"JSON formatted body / JSON 格式化显示"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[10].id,children:s[10].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Fuzzy search by URL or method / 按 URL 或方法模糊搜索"}),"\n",(0,n.jsx)(i.li,{children:"Search bar hidden in detail view / 详情视图隐藏搜索栏"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[11].id,children:s[11].value}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"Available since v1.3.0"})}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"v1.3.0 起可用"})}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"The request list supports a selection mode for operating on multiple requests at once."}),"\n",(0,n.jsx)(i.p,{children:"请求列表支持选择模式,可一次性操作多条请求。"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:["Tap the ",(0,n.jsx)(i.strong,{children:"selection icon"})," in the toolbar to enter selection mode / 点击工具栏的",(0,n.jsx)(i.strong,{children:"选择图标"}),"进入选择模式"]}),"\n",(0,n.jsxs)(i.li,{children:["Check/uncheck items; a top batch bar shows ",(0,n.jsx)(i.strong,{children:"Select all"})," / ",(0,n.jsx)(i.strong,{children:"Cancel"})," / 勾选/取消勾选;顶部批量条提供",(0,n.jsx)(i.strong,{children:"全选"})," / ",(0,n.jsx)(i.strong,{children:"取消"})]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Batch “Copy as cURL”"}),": copies all selected requests as cURL commands / ",(0,n.jsx)(i.strong,{children:"批量「Copy as cURL」"}),":将所有选中请求复制为 cURL 命令"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Batch delete"}),": removes selected requests from the list / ",(0,n.jsx)(i.strong,{children:"批量删除"}),":从列表中移除选中请求"]}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[12].id,children:s[12].value}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"Available since v1.3.0"})}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"v1.3.0 起可用"})}),"\n"]}),"\n",(0,n.jsxs)(i.p,{children:["The toolbar includes an ",(0,n.jsx)(i.strong,{children:"eye toggle"})," that controls whether sensitive headers are masked on export."]}),"\n",(0,n.jsxs)(i.p,{children:["工具栏包含",(0,n.jsx)(i.strong,{children:"眼睛开关"}),",控制导出时是否遮蔽敏感请求头。"]}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Default: off (fully visible)"})," — cURL / JSON / HAR reflect the real request verbatim / ",(0,n.jsx)(i.strong,{children:"默认关闭(完整可见)"}),":cURL / JSON / HAR 原样反映真实请求"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"On"}),": ",(0,n.jsx)(i.code,{children:"toCurl"})," / ",(0,n.jsx)(i.code,{children:"netToJson"})," / ",(0,n.jsx)(i.code,{children:"netToHar"})," / ",(0,n.jsx)(i.code,{children:"copyNet"})," / ",(0,n.jsx)(i.code,{children:"exportNetToFile"})," mask these headers / ",(0,n.jsx)(i.strong,{children:"开启后"}),":以下请求头被遮蔽:\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.code,{children:"Authorization"}),", ",(0,n.jsx)(i.code,{children:"Cookie"}),", ",(0,n.jsx)(i.code,{children:"Set-Cookie"}),", ",(0,n.jsx)(i.code,{children:"Proxy-Authorization"}),", ",(0,n.jsx)(i.code,{children:"X-Auth-Token"}),", ",(0,n.jsx)(i.code,{children:"X-CSRF-Token"}),", ",(0,n.jsx)(i.code,{children:"X-XSRF-Token"})]}),"\n"]}),"\n"]}),"\n",(0,n.jsx)(i.li,{children:"The detail page “Copy as cURL” / “Copy as JSON” follow the same toggle / 详情页「Copy as cURL」「Copy as JSON」跟随同一开关"}),"\n",(0,n.jsxs)(i.li,{children:["When masking is active, the snackbar notes ",(0,n.jsx)(i.strong,{children:"(sensitive hidden)"})," / 遮蔽开启时,snackbar 提示 ",(0,n.jsx)(i.strong,{children:"(sensitive hidden)"})]}),"\n"]}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsxs)(i.p,{children:["Tip: keep masking ",(0,n.jsx)(i.strong,{children:"on"})," before pasting cURL/JSON into terminals, chat, or issue trackers to avoid leaking credentials."]}),"\n",(0,n.jsx)(i.p,{children:"提示:将 cURL/JSON 粘贴到终端、聊天或 issue 前,建议开启遮蔽,避免凭据泄露。"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[13].id,children:s[13].value}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"Available since v1.3.0"})}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"v1.3.0 起可用"})}),"\n"]}),"\n",(0,n.jsxs)(i.p,{children:["From the request detail view, tap ",(0,n.jsx)(i.strong,{children:"Copy as cURL"})," to copy the request as a ready-to-run cURL command (method, URL, headers, body). Respects the sensitive-field masking toggle above."]}),"\n",(0,n.jsxs)(i.p,{children:["在请求详情页点击 ",(0,n.jsx)(i.strong,{children:"Copy as cURL"}),",即可将请求复制为可直接运行的 cURL 命令(方法、URL、请求头、请求体),并遵循上方的敏感字段遮蔽开关。"]}),"\n",(0,n.jsx)(i.h2,{id:s[14].id,children:s[14].value}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"Available since v1.11.0"})}),"\n",(0,n.jsx)(i.p,{children:(0,n.jsx)(i.strong,{children:"v1.11.0 起可用"})}),"\n"]}),"\n",(0,n.jsxs)(i.p,{children:["Every request detail view has a ",(0,n.jsx)(i.strong,{children:"replay"})," action (↻ icon). It opens an editable sheet that lets you ",(0,n.jsx)(i.strong,{children:"re-issue the captured request in-app"})," and preview the response — handy for quickly re-running an endpoint without leaving the app."]}),"\n",(0,n.jsxs)(i.p,{children:["每个请求详情页都有",(0,n.jsx)(i.strong,{children:"重放"}),"操作(↻ 图标)。它会打开一个可编辑弹层,让你",(0,n.jsx)(i.strong,{children:"在 App 内重新发出该请求"}),"并预览响应——无需离开应用即可快速复跑某个接口。"]}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"Only the URL query parameters are editable"})," — add / edit / remove ",(0,n.jsx)(i.code,{children:"key=value"})," rows; the URL is rebuilt from them on send / ",(0,n.jsx)(i.strong,{children:"仅 URL 查询参数可编辑"}),"——可增删改 ",(0,n.jsx)(i.code,{children:"key=value"})," 行,发送时用其重建 URL"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"URL, request headers and request body are read-only"})," by design (the captured values are re-sent verbatim) / ",(0,n.jsx)(i.strong,{children:"URL、请求头与请求体按设计只读"}),"(以捕获到的原值原样重发)"]}),"\n",(0,n.jsxs)(i.li,{children:["On ",(0,n.jsx)(i.strong,{children:"Send"}),", the request is re-issued with the original method / headers / body and the edited query string; the sheet shows the returned status code, elapsed time, and a response preview / 点击 ",(0,n.jsx)(i.strong,{children:"Send"})," 后用原始方法 / 请求头 / 请求体加上修改后的查询串重新发出,弹层展示返回状态码、耗时与响应预览"]}),"\n"]}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsxs)(i.p,{children:["This is distinct from the interceptor (which builds a durable modification rule applied to ",(0,n.jsx)(i.em,{children:"future"})," matching requests). The replay editor makes a ",(0,n.jsx)(i.strong,{children:"one-off"})," call with editable query params — including GET requests, whose params can’t be changed by the interceptor."]}),"\n",(0,n.jsxs)(i.p,{children:["它与拦截器不同(拦截器生成作用于",(0,n.jsx)(i.em,{children:"后续"}),"匹配请求的持久规则)。重放编辑器发起的是",(0,n.jsx)(i.strong,{children:"一次性"}),"调用,仅查询参数可改——包括 GET 请求(其参数拦截器无法修改)。"]}),"\n"]}),"\n",(0,n.jsx)(i.h2,{id:s[15].id,children:s[15].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(i.table,{children:[(0,n.jsx)(i.thead,{children:(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.th,{children:"Range"}),(0,n.jsx)(i.th,{children:"Color"}),(0,n.jsx)(i.th,{children:"Description"})]})}),(0,n.jsxs)(i.tbody,{children:[(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"2xx"}),(0,n.jsx)(i.td,{children:"Green"}),(0,n.jsx)(i.td,{children:"Success / 成功"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"3xx"}),(0,n.jsx)(i.td,{children:"Blue"}),(0,n.jsx)(i.td,{children:"Redirect / 重定向"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"4xx"}),(0,n.jsx)(i.td,{children:"Orange"}),(0,n.jsx)(i.td,{children:"Client error / 客户端错误"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"5xx"}),(0,n.jsx)(i.td,{children:"Red"}),(0,n.jsx)(i.td,{children:"Server error / 服务器错误"})]})]})]}),"\n",(0,n.jsx)(i.h2,{id:s[16].id,children:s[16].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(i.table,{children:[(0,n.jsx)(i.thead,{children:(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.th,{children:"Method"}),(0,n.jsx)(i.th,{children:"Color"})]})}),(0,n.jsxs)(i.tbody,{children:[(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"GET"}),(0,n.jsx)(i.td,{children:"Blue / 蓝色"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"POST"}),(0,n.jsx)(i.td,{children:"Green / 绿色"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"PUT"}),(0,n.jsx)(i.td,{children:"Orange / 橙色"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"DELETE"}),(0,n.jsx)(i.td,{children:"Red / 红色"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"PATCH"}),(0,n.jsx)(i.td,{children:"Purple / 紫色"})]})]})]}),"\n",(0,n.jsx)(i.h2,{id:s[17].id,children:s[17].value}),"\n",(0,n.jsx)(i.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(i.code,{children:[(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// http package - auto-captured / http 包 - 自动捕获"})}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" response "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" await"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" http."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"get"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" Uri"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"parse"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'https://api.example.com/data'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"),"})]}),"\n",(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})}),"\n",(0,n.jsx)(i.span,{children:" "}),"\n",(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"},children:"// Dio - auto-captured / Dio - 自动捕获"})}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"final"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" response "}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:" await"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" dio."}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"post"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'https://api.example.com/data'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:","})]}),"\n",(0,n.jsxs)(i.span,{children:[(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" data"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" {"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'key'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'value'"}),(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"},"})]}),"\n",(0,n.jsx)(i.span,{children:(0,n.jsx)(i.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})})]})}),"\n",(0,n.jsx)(i.p,{children:"No additional setup required! All requests will appear in the Network tab."}),"\n",(0,n.jsx)(i.p,{children:"无需额外配置!所有请求都会出现在 Network 标签页中。"}),"\n",(0,n.jsx)(i.h2,{id:s[18].id,children:s[18].value}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"Available since v1.0.7"})," (response fields locked to read-only since v1.0.8)"]}),"\n",(0,n.jsxs)(i.p,{children:[(0,n.jsx)(i.strong,{children:"v1.0.7 起可用"}),"(v1.0.8 起响应字段锁定为只读)"]}),"\n"]}),"\n",(0,n.jsx)(i.p,{children:"The inspector supports intercepting and modifying network requests via rules. This is useful for testing different request parameters without modifying app code."}),"\n",(0,n.jsx)(i.p,{children:"检查器支持通过规则拦截并修改网络请求,适合在不修改应用代码的情况下测试不同的请求参数。"}),"\n",(0,n.jsx)(i.h3,{id:s[19].id,children:s[19].value}),"\n",(0,n.jsxs)(i.ol,{children:["\n",(0,n.jsx)(i.li,{children:"Send a request normally (it will be captured in the Network panel) / 正常发送请求(会被捕获到 Network 面板)"}),"\n",(0,n.jsx)(i.li,{children:"Open the request detail and tap the Interceptor icon / 打开请求详情,点击拦截器图标"}),"\n",(0,n.jsx)(i.li,{children:"Configure the modification rule (URL pattern, HTTP method, request modifications) / 配置修改规则(URL 模式、HTTP 方法、请求修改)"}),"\n",(0,n.jsx)(i.li,{children:"Save the rule — subsequent matching requests will use the modified parameters / 保存规则——后续匹配的请求将使用修改后的参数"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[20].id,children:s[20].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(i.table,{children:[(0,n.jsx)(i.thead,{children:(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.th,{children:"Field"}),(0,n.jsx)(i.th,{children:"Editable"}),(0,n.jsx)(i.th,{children:"Notes"})]})}),(0,n.jsxs)(i.tbody,{children:[(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:(0,n.jsx)(i.strong,{children:"Request Body"})}),(0,n.jsx)(i.td,{children:"✅"}),(0,n.jsx)(i.td,{children:"Only for requests with body (POST, PUT, PATCH, etc.) / 仅适用于有 body 的请求"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:(0,n.jsx)(i.strong,{children:"Request Headers"})}),(0,n.jsx)(i.td,{children:"✅"}),(0,n.jsx)(i.td,{children:"Add / modify / remove headers / 新增 / 修改 / 删除请求头"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"URL"}),(0,n.jsx)(i.td,{children:"❌"}),(0,n.jsx)(i.td,{children:"Grayed out, read-only / 灰色不可编辑"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Response Status Code"}),(0,n.jsx)(i.td,{children:"❌"}),(0,n.jsx)(i.td,{children:"Read-only since v1.0.8 / v1.0.8 起只读"})]}),(0,n.jsxs)(i.tr,{children:[(0,n.jsx)(i.td,{children:"Response Body"}),(0,n.jsx)(i.td,{children:"❌"}),(0,n.jsx)(i.td,{children:"Read-only since v1.0.8 / v1.0.8 起只读"})]})]})]}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsxs)(i.p,{children:["The interception edit panel only allows modifying the ",(0,n.jsx)(i.strong,{children:"request body"})," and ",(0,n.jsx)(i.strong,{children:"request headers"}),". Response fields (status code, response body) are grayed out and uneditable."]}),"\n",(0,n.jsxs)(i.p,{children:["拦截编辑面板仅允许修改",(0,n.jsx)(i.strong,{children:"请求体"}),"和",(0,n.jsx)(i.strong,{children:"请求头"}),"。响应字段(状态码、响应体)灰色不可编辑。"]}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[21].id,children:s[21].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"URL pattern matching"}),": exact match or regex / URL 模式匹配:精确匹配或正则匹配"]}),"\n",(0,n.jsxs)(i.li,{children:[(0,n.jsx)(i.strong,{children:"HTTP method filtering"}),": GET, POST, PUT, DELETE, PATCH, HEAD, or Any / HTTP 方法过滤"]}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[22].id,children:s[22].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"The interceptor currently supports modifying request body and headers only / 拦截器目前仅支持修改请求体和请求头"}),"\n",(0,n.jsx)(i.li,{children:"GET requests don’t have a request body / GET 请求没有请求体"}),"\n",(0,n.jsx)(i.li,{children:"Modifying GET request parameters would require URL modification / 修改 GET 请求参数需要修改 URL"}),"\n",(0,n.jsx)(i.li,{children:"URL modification may cause unexpected issues with request routing and parameter encoding / 修改 URL 可能导致请求路由和参数编码的意外问题"}),"\n"]}),"\n",(0,n.jsxs)(i.blockquote,{children:["\n",(0,n.jsx)(i.p,{children:"GET request detail pages do not display interception edit buttons, making them unmodifiable."}),"\n",(0,n.jsx)(i.p,{children:"GET 请求详情页不显示拦截编辑按钮,因此无法修改。"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[23].id,children:s[23].value}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsxs)(i.li,{children:["The interception master toggle is displayed ",(0,n.jsx)(i.strong,{children:"only on the Network list page"}),", not in the detail page / 拦截总开关",(0,n.jsx)(i.strong,{children:"只显示在 Network 列表页"}),",不在详情页"]}),"\n",(0,n.jsx)(i.li,{children:"Rules are only applied when modification mode is enabled / 规则仅在启用修改模式时生效"}),"\n",(0,n.jsx)(i.li,{children:"When no rules are configured or rules are disabled, all requests are sent normally without any modification / 未配置规则或禁用规则时,所有请求正常发送,不做任何修改"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:s[24].id,children:s[24].value}),"\n",(0,n.jsx)(i.p,{children:"The network panel includes an interceptor rule editor where you can:"}),"\n",(0,n.jsx)(i.p,{children:"网络面板包含拦截规则编辑器,可以:"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Create / edit / delete rules / 创建 / 编辑 / 删除规则"}),"\n",(0,n.jsx)(i.li,{children:"Enable / disable individual rules / 启用 / 禁用单条规则"}),"\n",(0,n.jsx)(i.li,{children:"View rule status indicators in the request list / 在请求列表中查看规则状态标识"}),"\n"]})]})},"/Network-Inspector",{filePath:"pages/Network-Inspector.md",timestamp:1788107481e3,pageMap:t.v,frontMatter:{},title:"Network Inspector / 网络检查器"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,s,i){"use strict";i.d(s,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=3814)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Route-Tracker-8de8d462ef1f1c4e.js b/docs/_next/static/chunks/pages/Route-Tracker-8d1b310180aa3bf2.js similarity index 79% rename from docs/_next/static/chunks/pages/Route-Tracker-8de8d462ef1f1c4e.js rename to docs/_next/static/chunks/pages/Route-Tracker-8d1b310180aa3bf2.js index a159427..679150c 100644 --- a/docs/_next/static/chunks/pages/Route-Tracker-8de8d462ef1f1c4e.js +++ b/docs/_next/static/chunks/pages/Route-Tracker-8d1b310180aa3bf2.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[930],{3242:function(e,t,r){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Route-Tracker",function(){return r(205)}])},205:function(e,t,r){"use strict";r.r(t),r.d(t,{useTOC:function(){return d}});var i=r(5893),n=r(7812),s=r(7080),a=r(8925);function d(e){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"Auto-Injection / 自动注入",id:"auto-injection--自动注入",depth:2},{value:"Tracked Route Actions / 追踪的路由操作",id:"tracked-route-actions--追踪的路由操作",depth:2},{value:"UI Features / UI 功能",id:"ui-features--ui-功能",depth:2},{value:"Route List / 路由列表",id:"route-list--路由列表",depth:3},{value:"Route Detail / 路由详情",id:"route-detail--路由详情",depth:3},{value:"Manual Setup (Optional) / 手动设置(可选)",id:"manual-setup-optional--手动设置可选",depth:2}]}t.default=(0,n.c)(function(e){let{toc:t=d(e)}=e,r={code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(r.h1,{children:"Route Tracker / 路由追踪"}),"\n",(0,i.jsx)(r.h2,{id:t[0].id,children:t[0].value}),"\n",(0,i.jsx)(r.p,{children:"The Route Tracker monitors navigation history, recording all route push, pop, and replacement events."}),"\n",(0,i.jsx)(r.p,{children:"路由追踪器监控导航历史,记录所有路由 push、pop 和替换事件。"}),"\n",(0,i.jsx)(r.h2,{id:t[1].id,children:t[1].value}),"\n",(0,i.jsxs)(r.p,{children:["When using ",(0,i.jsx)(r.code,{children:"runAppWithInspector()"})," or ",(0,i.jsx)(r.code,{children:"wrapApp()"}),", the ",(0,i.jsx)(r.code,{children:"InspectorRouteObserver"})," is automatically injected into ",(0,i.jsx)(r.code,{children:"MaterialApp"}),"’s ",(0,i.jsx)(r.code,{children:"navigatorObservers"}),"."]}),"\n",(0,i.jsxs)(r.p,{children:["使用 ",(0,i.jsx)(r.code,{children:"runAppWithInspector()"})," 或 ",(0,i.jsx)(r.code,{children:"wrapApp()"})," 时,",(0,i.jsx)(r.code,{children:"InspectorRouteObserver"})," 会自动注入到 ",(0,i.jsx)(r.code,{children:"MaterialApp"})," 的 ",(0,i.jsx)(r.code,{children:"navigatorObservers"})," 中。"]}),"\n",(0,i.jsx)(r.h2,{id:t[2].id,children:t[2].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(r.table,{children:[(0,i.jsx)(r.thead,{children:(0,i.jsxs)(r.tr,{children:[(0,i.jsx)(r.th,{children:"Action"}),(0,i.jsx)(r.th,{children:"Color"}),(0,i.jsx)(r.th,{children:"Description"})]})}),(0,i.jsxs)(r.tbody,{children:[(0,i.jsxs)(r.tr,{children:[(0,i.jsx)(r.td,{children:"push"}),(0,i.jsx)(r.td,{children:"Blue"}),(0,i.jsxs)(r.td,{children:[(0,i.jsx)(r.code,{children:"Navigator.push()"})," / 推入新路由"]})]}),(0,i.jsxs)(r.tr,{children:[(0,i.jsx)(r.td,{children:"pushNamed"}),(0,i.jsx)(r.td,{children:"Blue"}),(0,i.jsxs)(r.td,{children:[(0,i.jsx)(r.code,{children:"Navigator.pushNamed()"})," / 按名称推入"]})]}),(0,i.jsxs)(r.tr,{children:[(0,i.jsx)(r.td,{children:"pop"}),(0,i.jsx)(r.td,{children:"Orange"}),(0,i.jsxs)(r.td,{children:[(0,i.jsx)(r.code,{children:"Navigator.pop()"})," / 弹出路由"]})]}),(0,i.jsxs)(r.tr,{children:[(0,i.jsx)(r.td,{children:"popUntil"}),(0,i.jsx)(r.td,{children:"Orange"}),(0,i.jsxs)(r.td,{children:[(0,i.jsx)(r.code,{children:"Navigator.popUntil()"})," / 弹出直到"]})]}),(0,i.jsxs)(r.tr,{children:[(0,i.jsx)(r.td,{children:"pushReplacement"}),(0,i.jsx)(r.td,{children:"Purple"}),(0,i.jsxs)(r.td,{children:[(0,i.jsx)(r.code,{children:"Navigator.pushReplacement()"})," / 替换路由"]})]})]})]}),"\n",(0,i.jsx)(r.h2,{id:t[3].id,children:t[3].value}),"\n",(0,i.jsx)(r.h3,{id:t[4].id,children:t[4].value}),"\n",(0,i.jsxs)(r.ul,{children:["\n",(0,i.jsx)(r.li,{children:"Action badge with color / 带颜色的操作徽章"}),"\n",(0,i.jsx)(r.li,{children:"Route name display / 路由名称显示"}),"\n",(0,i.jsx)(r.li,{children:"Timestamp display / 时间戳显示"}),"\n",(0,i.jsx)(r.li,{children:"Left border color indicates action type / 左侧边框颜色表示操作类型"}),"\n"]}),"\n",(0,i.jsx)(r.h3,{id:t[5].id,children:t[5].value}),"\n",(0,i.jsxs)(r.ul,{children:["\n",(0,i.jsx)(r.li,{children:"Click a route to view details / 点击路由查看详情"}),"\n",(0,i.jsx)(r.li,{children:"Shows: Action, Route Name, Timestamp, Arguments / 显示:操作、路由名、时间戳、参数"}),"\n",(0,i.jsx)(r.li,{children:"Arguments displayed as formatted JSON / 参数以 JSON 格式显示"}),"\n"]}),"\n",(0,i.jsx)(r.h2,{id:t[6].id,children:t[6].value}),"\n",(0,i.jsxs)(r.p,{children:["If you use a custom Navigator or don’t use ",(0,i.jsx)(r.code,{children:"MaterialApp"}),", add the observer manually:"]}),"\n",(0,i.jsxs)(r.p,{children:["如果使用自定义 Navigator 或不使用 ",(0,i.jsx)(r.code,{children:"MaterialApp"}),",请手动添加观察者:"]}),"\n",(0,i.jsx)(r.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,i.jsxs)(r.code,{children:[(0,i.jsxs)(r.span,{children:[(0,i.jsx)(r.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MaterialApp"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,i.jsxs)(r.span,{children:[(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" navigatorObservers"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ["}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorRouteObserver"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"()],"})]}),"\n",(0,i.jsxs)(r.span,{children:[(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" home"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyHomePage"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(),"})]}),"\n",(0,i.jsx)(r.span,{children:(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:")"})})]})})]})},"/Route-Tracker",{filePath:"pages/Route-Tracker.md",timestamp:1786656379e3,pageMap:s.v,frontMatter:{},title:"Route Tracker / 路由追踪"},"undefined"==typeof RemoteContent?d:RemoteContent.useTOC)},7080:function(e,t,r){"use strict";r.d(t,{v:function(){return i}});let i=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=3242)}),_N_E=e.O()}]); \ No newline at end of file +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[930],{3242:function(e,t,r){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Route-Tracker",function(){return r(205)}])},205:function(e,t,r){"use strict";r.r(t),r.d(t,{useTOC:function(){return d}});var i=r(5893),n=r(7812),s=r(7080),a=r(8925);function d(e){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"Auto-Injection / 自动注入",id:"auto-injection--自动注入",depth:2},{value:"Tracked Route Actions / 追踪的路由操作",id:"tracked-route-actions--追踪的路由操作",depth:2},{value:"UI Features / UI 功能",id:"ui-features--ui-功能",depth:2},{value:"Route List / 路由列表",id:"route-list--路由列表",depth:3},{value:"Route Detail / 路由详情",id:"route-detail--路由详情",depth:3},{value:"Manual Setup (Optional) / 手动设置(可选)",id:"manual-setup-optional--手动设置可选",depth:2}]}t.default=(0,n.c)(function(e){let{toc:t=d(e)}=e,r={code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(r.h1,{children:"Route Tracker / 路由追踪"}),"\n",(0,i.jsx)(r.h2,{id:t[0].id,children:t[0].value}),"\n",(0,i.jsx)(r.p,{children:"The Route Tracker monitors navigation history, recording all route push, pop, and replacement events."}),"\n",(0,i.jsx)(r.p,{children:"路由追踪器监控导航历史,记录所有路由 push、pop 和替换事件。"}),"\n",(0,i.jsx)(r.h2,{id:t[1].id,children:t[1].value}),"\n",(0,i.jsxs)(r.p,{children:["When using ",(0,i.jsx)(r.code,{children:"runAppWithInspector()"})," or ",(0,i.jsx)(r.code,{children:"wrapApp()"}),", the ",(0,i.jsx)(r.code,{children:"InspectorRouteObserver"})," is automatically injected into ",(0,i.jsx)(r.code,{children:"MaterialApp"}),"’s ",(0,i.jsx)(r.code,{children:"navigatorObservers"}),"."]}),"\n",(0,i.jsxs)(r.p,{children:["使用 ",(0,i.jsx)(r.code,{children:"runAppWithInspector()"})," 或 ",(0,i.jsx)(r.code,{children:"wrapApp()"})," 时,",(0,i.jsx)(r.code,{children:"InspectorRouteObserver"})," 会自动注入到 ",(0,i.jsx)(r.code,{children:"MaterialApp"})," 的 ",(0,i.jsx)(r.code,{children:"navigatorObservers"})," 中。"]}),"\n",(0,i.jsx)(r.h2,{id:t[2].id,children:t[2].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(r.table,{children:[(0,i.jsx)(r.thead,{children:(0,i.jsxs)(r.tr,{children:[(0,i.jsx)(r.th,{children:"Action"}),(0,i.jsx)(r.th,{children:"Color"}),(0,i.jsx)(r.th,{children:"Description"})]})}),(0,i.jsxs)(r.tbody,{children:[(0,i.jsxs)(r.tr,{children:[(0,i.jsx)(r.td,{children:"push"}),(0,i.jsx)(r.td,{children:"Blue"}),(0,i.jsxs)(r.td,{children:[(0,i.jsx)(r.code,{children:"Navigator.push()"})," / 推入新路由"]})]}),(0,i.jsxs)(r.tr,{children:[(0,i.jsx)(r.td,{children:"pushNamed"}),(0,i.jsx)(r.td,{children:"Blue"}),(0,i.jsxs)(r.td,{children:[(0,i.jsx)(r.code,{children:"Navigator.pushNamed()"})," / 按名称推入"]})]}),(0,i.jsxs)(r.tr,{children:[(0,i.jsx)(r.td,{children:"pop"}),(0,i.jsx)(r.td,{children:"Orange"}),(0,i.jsxs)(r.td,{children:[(0,i.jsx)(r.code,{children:"Navigator.pop()"})," / 弹出路由"]})]}),(0,i.jsxs)(r.tr,{children:[(0,i.jsx)(r.td,{children:"popUntil"}),(0,i.jsx)(r.td,{children:"Orange"}),(0,i.jsxs)(r.td,{children:[(0,i.jsx)(r.code,{children:"Navigator.popUntil()"})," / 弹出直到"]})]}),(0,i.jsxs)(r.tr,{children:[(0,i.jsx)(r.td,{children:"pushReplacement"}),(0,i.jsx)(r.td,{children:"Purple"}),(0,i.jsxs)(r.td,{children:[(0,i.jsx)(r.code,{children:"Navigator.pushReplacement()"})," / 替换路由"]})]})]})]}),"\n",(0,i.jsx)(r.h2,{id:t[3].id,children:t[3].value}),"\n",(0,i.jsx)(r.h3,{id:t[4].id,children:t[4].value}),"\n",(0,i.jsxs)(r.ul,{children:["\n",(0,i.jsx)(r.li,{children:"Action badge with color / 带颜色的操作徽章"}),"\n",(0,i.jsx)(r.li,{children:"Route name display / 路由名称显示"}),"\n",(0,i.jsx)(r.li,{children:"Timestamp display / 时间戳显示"}),"\n",(0,i.jsx)(r.li,{children:"Left border color indicates action type / 左侧边框颜色表示操作类型"}),"\n"]}),"\n",(0,i.jsx)(r.h3,{id:t[5].id,children:t[5].value}),"\n",(0,i.jsxs)(r.ul,{children:["\n",(0,i.jsx)(r.li,{children:"Click a route to view details / 点击路由查看详情"}),"\n",(0,i.jsx)(r.li,{children:"Shows: Action, Route Name, Timestamp, Arguments / 显示:操作、路由名、时间戳、参数"}),"\n",(0,i.jsx)(r.li,{children:"Arguments displayed as formatted JSON / 参数以 JSON 格式显示"}),"\n"]}),"\n",(0,i.jsx)(r.h2,{id:t[6].id,children:t[6].value}),"\n",(0,i.jsxs)(r.p,{children:["If you use a custom Navigator or don’t use ",(0,i.jsx)(r.code,{children:"MaterialApp"}),", add the observer manually:"]}),"\n",(0,i.jsxs)(r.p,{children:["如果使用自定义 Navigator 或不使用 ",(0,i.jsx)(r.code,{children:"MaterialApp"}),",请手动添加观察者:"]}),"\n",(0,i.jsx)(r.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,i.jsxs)(r.code,{children:[(0,i.jsxs)(r.span,{children:[(0,i.jsx)(r.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"MaterialApp"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("})]}),"\n",(0,i.jsxs)(r.span,{children:[(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" navigatorObservers"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" ["}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorRouteObserver"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"()],"})]}),"\n",(0,i.jsxs)(r.span,{children:[(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" home"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:":"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyHomePage"}),(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(),"})]}),"\n",(0,i.jsx)(r.span,{children:(0,i.jsx)(r.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:")"})})]})})]})},"/Route-Tracker",{filePath:"pages/Route-Tracker.md",timestamp:1786656379e3,pageMap:s.v,frontMatter:{},title:"Route Tracker / 路由追踪"},"undefined"==typeof RemoteContent?d:RemoteContent.useTOC)},7080:function(e,t,r){"use strict";r.d(t,{v:function(){return i}});let i=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=3242)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Timeline-a6d10ad811492520.js b/docs/_next/static/chunks/pages/Timeline-a6d10ad811492520.js new file mode 100644 index 0000000..0f4906a --- /dev/null +++ b/docs/_next/static/chunks/pages/Timeline-a6d10ad811492520.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[848],{2139:function(e,r,t){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Timeline",function(){return t(6347)}])},6347:function(e,r,t){"use strict";t.r(r),t.d(r,{useTOC:function(){return a}});var n=t(5893),i=t(7812),s=t(7080),o=t(8925);function a(e){return[{value:"Overview / 概述",id:"overview--概述",depth:2},{value:"Where to find it / 入口",id:"where-to-find-it--入口",depth:2},{value:"Features / 功能",id:"features--功能",depth:2},{value:"1. Merged stream / 归并流",id:"1-merged-stream--归并流",depth:3},{value:"2. Per-source filtering / 逐来源筛选",id:"2-per-source-filtering--逐来源筛选",depth:3},{value:"3. Focus \xb1N seconds / \xb1N 秒聚焦",id:"3-focus-n-seconds--n-秒聚焦",depth:3},{value:"How It Works / 工作原理",id:"how-it-works--工作原理",depth:2},{value:"Related / 相关",id:"related--相关",depth:2}]}r.default=(0,i.c)(function(e){let{toc:r=a(e)}=e,t={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",strong:"strong",ul:"ul",...(0,o.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.h1,{children:"Timeline / 统一会话时间线"}),"\n",(0,n.jsx)(t.h2,{id:r[0].id,children:r[0].value}),"\n",(0,n.jsxs)(t.p,{children:["The Timeline tab merges network, logs, errors, routes, and alerts into a ",(0,n.jsx)(t.strong,{children:"single time-ordered stream"}),", so you can see the full causal chain of a session at a glance — e.g. “route push → two requests → error log → 5xx alert”. Each source keeps its own data; the Timeline view only merges them for display."]}),"\n",(0,n.jsxs)(t.p,{children:["统一会话时间线把网络、日志、异常、路由、告警归并为",(0,n.jsx)(t.strong,{children:"一条按时间排序的流"}),",让你一眼看清一次会话的完整因果链——例如「路由跳转 → 两个请求 → error 日志 → 5xx 告警」。各来源的数据仍归属各自服务,时间线视图只做归并显示。"]}),"\n",(0,n.jsxs)(t.blockquote,{children:["\n",(0,n.jsx)(t.p,{children:(0,n.jsx)(t.strong,{children:"Available since v1.12.0"})}),"\n",(0,n.jsx)(t.p,{children:(0,n.jsx)(t.strong,{children:"v1.12.0 起可用"})}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:r[1].id,children:r[1].value}),"\n",(0,n.jsxs)(t.p,{children:["Tap the floating inspector button → the ",(0,n.jsx)(t.strong,{children:"Timeline"})," tab (located after ",(0,n.jsx)(t.strong,{children:"Routes"})," and before ",(0,n.jsx)(t.strong,{children:"Widgets"})," in the panel)."]}),"\n",(0,n.jsxs)(t.p,{children:["点击悬浮检查器按钮 → ",(0,n.jsx)(t.strong,{children:"Timeline"})," 标签页(位于面板中 ",(0,n.jsx)(t.strong,{children:"Routes"})," 之后、",(0,n.jsx)(t.strong,{children:"Widgets"})," 之前)。"]}),"\n",(0,n.jsx)(t.h2,{id:r[2].id,children:r[2].value}),"\n",(0,n.jsx)(t.h3,{id:r[3].id,children:r[3].value}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:"Network / Logs / Errors / Routes / Alerts appear interleaved by timestamp / 网络 / 日志 / 异常 / 路由 / 告警按时间戳交错排列"}),"\n",(0,n.jsx)(t.li,{children:"Each entry shows its source icon, a colored left accent bar, timestamp, and a concise summary / 每条显示来源图标、左侧彩色强调条、时间戳与精炼摘要"}),"\n",(0,n.jsx)(t.li,{children:"Tap any entry to expand details (e.g. request URL/status, log level/message, route action/name) / 点击任意条目展开详情(如请求 URL/状态、日志级别/内容、路由操作/名称)"}),"\n"]}),"\n",(0,n.jsx)(t.p,{children:"把五类数据按时间归并,每条带来源图标、彩色强调条、时间戳与摘要,可点击展开。"}),"\n",(0,n.jsx)(t.h3,{id:r[4].id,children:r[4].value}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:"Toggle each source on/off to focus on the ones you care about / 逐来源开关,只看关心的来源"}),"\n",(0,n.jsx)(t.li,{children:"Filtering only hides entries from the merged view; underlying data is untouched / 筛选只隐藏归并视图中的条目,底层数据不受影响"}),"\n"]}),"\n",(0,n.jsx)(t.p,{children:"顶部可按来源开关自由筛选,归并显示内容随之变化,底层数据不变。"}),"\n",(0,n.jsx)(t.h3,{id:r[5].id,children:r[5].value}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:"Tap any event to enter “focus” mode: the view keeps only events within \xb1N seconds of the anchor / 点击任意事件进入「聚焦」模式,仅保留锚点前后 \xb1N 秒内的事件"}),"\n",(0,n.jsx)(t.li,{children:"Window selector: 5s / 10s (default) / 30s / 窗口可选:5s / 10s(默认)/ 30s"}),"\n",(0,n.jsx)(t.li,{children:"Great for isolating the exact cause-effect around a failure (e.g. what happened right before a 5xx alert) / 适合隔离某次失败前后的精确因果(如 5xx 告警前到底发生了什么)"}),"\n"]}),"\n",(0,n.jsx)(t.p,{children:"点击任意事件即可「聚焦它前后 \xb1N 秒」,把噪音过滤掉,只留因果相关事件。"}),"\n",(0,n.jsx)(t.h2,{id:r[6].id,children:r[6].value}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.strong,{children:"View-only merge"})," — ",(0,n.jsx)(t.code,{children:"TimelineService.build()"})," reads from ",(0,n.jsx)(t.code,{children:"InspectorService"})," (network / logs / errors), ",(0,n.jsx)(t.code,{children:"RouteTrackerService"}),", and ",(0,n.jsx)(t.code,{children:"AlertService"}),", sorts them by time, and tags each with its ",(0,n.jsx)(t.code,{children:"TimelineEventKind"}),". It does ",(0,n.jsx)(t.strong,{children:"not"})," copy or own any data. / ",(0,n.jsx)(t.strong,{children:"仅视图归并"}),":",(0,n.jsx)(t.code,{children:"TimelineService.build()"})," 从各服务读取数据、按时间排序并打上来源标签,不复制、不持有数据。"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.strong,{children:"Zero cost when closed"})," — no timers, no listeners, no polling while the panel is not open; the merge runs on demand when you open the Timeline tab. / ",(0,n.jsx)(t.strong,{children:"面板关闭时零开销"}),":未打开面板时不跑定时器、不监听、不轮询,仅在打开 Timeline 时按需归并。"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.strong,{children:(0,n.jsx)(t.code,{children:"contextAround(anchor, window)"})})," — given an anchor event and a ",(0,n.jsx)(t.code,{children:"Duration"})," window, returns the slice of events whose timestamp falls within ",(0,n.jsx)(t.code,{children:"[anchor - window, anchor + window]"}),", oldest-first. / 给定锚点事件与窗口时长,返回落在 ",(0,n.jsx)(t.code,{children:"[锚点 - 窗口, 锚点 + 窗口]"})," 内的事件,按时间升序。"]}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:r[7].id,children:r[7].value}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"Usage",children:"Usage"})," — General usage guide / 通用使用指南"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"FPS-Viewer",children:"FPS Viewer"})," — Includes the main-thread blocking watchdog / 含主线程阻塞看门狗"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"Route-Tracker",children:"Route Tracker"})," — Route tracking details / 路由追踪详情"]}),"\n"]})]})},"/Timeline",{filePath:"pages/Timeline.md",pageMap:s.v,frontMatter:{},title:"Timeline / 统一会话时间线"},"undefined"==typeof RemoteContent?a:RemoteContent.useTOC)},7080:function(e,r,t){"use strict";t.d(r,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=2139)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Usage-405dadea5015323f.js b/docs/_next/static/chunks/pages/Usage-405dadea5015323f.js new file mode 100644 index 0000000..73f70a7 --- /dev/null +++ b/docs/_next/static/chunks/pages/Usage-405dadea5015323f.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[924],{4276:function(e,i,s){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Usage",function(){return s(1258)}])},1258:function(e,i,s){"use strict";s.r(i),s.d(i,{useTOC:function(){return h}});var n=s(5893),r=s(7812),t=s(7080),l=s(8925);function h(e){return[{value:"Integration Methods / 集成方式",id:"integration-methods--集成方式",depth:2},{value:"1. One-Line Integration (Recommended) / 一行代码集成(推荐)",id:"1-one-line-integration-recommended--一行代码集成推荐",depth:3},{value:"2. Two-Line Integration / 两行代码集成",id:"2-two-line-integration--两行代码集成",depth:3},{value:"Inspector Panel / 检查器面板",id:"inspector-panel--检查器面板",depth:2},{value:"One-Click Bug Report / 一键 Bug 报告",id:"one-click-bug-report--一键-bug-报告",depth:2},{value:"Floating Button / 悬浮按钮",id:"floating-button--悬浮按钮",depth:2},{value:"Edge Docking (since v1.2.0) / 边缘吸附(v1.2.0 起)",id:"edge-docking-since-v120--边缘吸附v120-起",depth:3},{value:"Search / 搜索",id:"search--搜索",depth:2},{value:"Manual Logging (Optional) / 手动记录日志(可选)",id:"manual-logging-optional--手动记录日志可选",depth:2},{value:"Third-Party Log Integration / 第三方日志库集成",id:"third-party-log-integration--第三方日志库集成",depth:2},{value:"Bidirectional Sync (Optional) / 双向同步(可选)",id:"bidirectional-sync-optional--双向同步可选",depth:3},{value:"Feature Pages / 功能详情",id:"feature-pages--功能详情",depth:2},{value:"Session Persistence / 会话持久化",id:"session-persistence--会话持久化",depth:2}]}i.default=(0,r.c)(function(e){let{toc:i=h(e)}=e,s={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.h1,{children:"Usage / 使用指南"}),"\n",(0,n.jsx)(s.h2,{id:i[0].id,children:i[0].value}),"\n",(0,n.jsx)(s.h3,{id:i[1].id,children:i[1].value}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"void"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" main"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"() {"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"runAppWithInspector"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"());"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})})]})}),"\n",(0,n.jsx)(s.p,{children:"This method:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Auto-initializes inspector / 自动初始化检查器"}),"\n",(0,n.jsxs)(s.li,{children:["Captures ",(0,n.jsx)(s.code,{children:"print()"})," via Zone / 通过 Zone 捕获 print()"]}),"\n",(0,n.jsx)(s.li,{children:"Displays floating button via Overlay / 通过 Overlay 显示悬浮按钮"}),"\n",(0,n.jsx)(s.li,{children:"Auto-injects route observer / 自动注入路由观察者"}),"\n"]}),"\n",(0,n.jsx)(s.h3,{id:i[2].id,children:i[2].value}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"void"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" main"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"() {"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"init"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" runApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"wrapApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"()));"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})})]})}),"\n",(0,n.jsx)(s.h2,{id:i[3].id,children:i[3].value}),"\n",(0,n.jsxs)(s.p,{children:["The inspector panel contains ",(0,n.jsx)(s.strong,{children:"10 tabs"})," (as of v1.12.0):"]}),"\n",(0,n.jsxs)(s.p,{children:["检查器面板包含 ",(0,n.jsx)(s.strong,{children:"10 个标签页"}),"(v1.12.0 起):"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Tab"}),(0,n.jsx)(s.th,{children:"Icon"}),(0,n.jsx)(s.th,{children:"Feature"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Network"})}),(0,n.jsx)(s.td,{children:"\uD83C\uDF10"}),(0,n.jsx)(s.td,{children:"HTTP request viewing + interceptor rules / 网络请求查看 + 拦截修改"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Logs"})}),(0,n.jsx)(s.td,{children:"\uD83D\uDCDD"}),(0,n.jsx)(s.td,{children:"Log viewing with level filter / 日志查看"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Errors"})}),(0,n.jsx)(s.td,{children:"\uD83D\uDEA8"}),(0,n.jsx)(s.td,{children:"Aggregated & deduped crash viewing / 去重聚合的异常查看"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Database"})}),(0,n.jsx)(s.td,{children:"\uD83D\uDCBE"}),(0,n.jsx)(s.td,{children:"Database and table inspection / 数据库查看"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Memory"})}),(0,n.jsx)(s.td,{children:"\uD83D\uDCCA"}),(0,n.jsx)(s.td,{children:"Memory trend, Dart Heap, Native memory, leak detection / 内存趋势、Dart Heap、Native 内存、泄漏检测"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"FPS"})}),(0,n.jsx)(s.td,{children:"\uD83C\uDFAF"}),(0,n.jsx)(s.td,{children:"Real-time FPS, jank rate, trend chart, plus main-thread blocking watchdog / 实时 FPS、卡顿率、趋势图,以及主线程阻塞看门狗"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Routes"})}),(0,n.jsx)(s.td,{children:"\uD83E\uDDED"}),(0,n.jsx)(s.td,{children:"Route navigation tracking / 路由追踪"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Timeline"})}),(0,n.jsx)(s.td,{children:"\uD83E\uDDF5"}),(0,n.jsx)(s.td,{children:"Unified session timeline: network / logs / errors / routes / alerts merged into one stream, with \xb1N-second focus / 统一会话时间线:网络/日志/异常/路由/告警按时间归并,支持 \xb1N 秒聚焦"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Widgets"})}),(0,n.jsx)(s.td,{children:"\uD83D\uDD0D"}),(0,n.jsx)(s.td,{children:"Widget tree snapshot for the current route / 当前路由的 Widget 树快照"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Alerts"})}),(0,n.jsx)(s.td,{children:"\uD83D\uDD14"}),(0,n.jsx)(s.td,{children:"Rule-based alerts with unread badge / 基于规则的告警与未读角标"})]})]})]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:["The Memory and FPS monitors are ",(0,n.jsx)(s.strong,{children:"off by default"})," to avoid performance overhead. Toggle the switch at the top of each panel to start collecting data."]}),"\n",(0,n.jsxs)(s.p,{children:["Memory 与 FPS 监控",(0,n.jsx)(s.strong,{children:"默认关闭"}),"以避免性能开销。在各自面板顶部打开开关才会开始采集数据。"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[4].id,children:i[4].value}),"\n",(0,n.jsxs)(s.p,{children:["Tap the ",(0,n.jsx)(s.strong,{children:"bug icon"})," in the inspector panel header to generate and share a bug report in one tap — ideal for QA to attach environment context when filing issues."]}),"\n",(0,n.jsxs)(s.p,{children:["点击检查器面板头部的",(0,n.jsx)(s.strong,{children:"虫子图标"}),",即可一键生成并分享一份 Bug 报告——非常适合 QA 在提 issue 时附上环境上下文。"]}),"\n",(0,n.jsx)(s.p,{children:"The shared text snapshot includes / 分享的文本快照包含:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Device / 设备:"})," real model (e.g. ",(0,n.jsx)(s.code,{children:"Pixel 8 Pro"})," / ",(0,n.jsx)(s.code,{children:"iPhone (iPhone16,1)"}),"), OS & version, locale, Dart runtime, CPU cores."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Memory / 内存:"})," current heap usage and whether Native memory is supported."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Recent logs / 最近日志:"})," the latest captured log entries."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Recent network / 最近网络:"})," the latest captured requests."]}),"\n"]}),"\n",(0,n.jsx)(s.p,{children:"Sensitive headers are masked the same way as in the Network tab. No data leaves the device except through the share target you choose."}),"\n",(0,n.jsx)(s.p,{children:"敏感请求头会与网络标签页一样被遮蔽。除你选择的分享目标外,数据不会离开设备。"}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Requires no extra setup — it works as soon as the inspector is running. / 无需额外配置——检查器运行后即可使用。"}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[5].id,children:i[5].value}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"The floating button appears after 1 second delay / 悬浮按钮延迟 1 秒出现"}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Drag"})," to move it along the screen edge / ",(0,n.jsx)(s.strong,{children:"拖动"})," 可沿屏幕边缘移动"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Tap"})," (when fully visible) to open/close the inspector panel / ",(0,n.jsx)(s.strong,{children:"点击"}),"(完全可见时)打开/关闭检查器面板"]}),"\n",(0,n.jsx)(s.li,{children:"Button auto-snaps to the nearest screen edge / 按钮自动吸附到最近的屏幕边缘"}),"\n",(0,n.jsx)(s.li,{children:"Breathing animation when idle / 空闲时有呼吸动画"}),"\n"]}),"\n",(0,n.jsx)(s.h3,{id:i[6].id,children:i[6].value}),"\n",(0,n.jsxs)(s.p,{children:["When released near a screen edge, the button auto-docks and tucks into the edge, leaving only a ",(0,n.jsx)(s.strong,{children:"24px peek"})," visible:"]}),"\n",(0,n.jsxs)(s.p,{children:["拖动松手后,按钮会自动吸附到最近边缘并”收入”边缘,仅露出 ",(0,n.jsx)(s.strong,{children:"24px 小弧边"}),":"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"State"}),(0,n.jsx)(s.th,{children:"Behavior / 行为"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Docked (tucked in)"})}),(0,n.jsx)(s.td,{children:"Only 24px peek visible; icon becomes a directional chevron (left dock → ➡, right dock → ⬅) hinting at tap-to-pull-out / 仅露出 24px;图标变为方向箭头提示可点击拉出"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Tap docked peek"})}),(0,n.jsx)(s.td,{children:"Smoothly pulls out to fully visible (panel NOT opened, avoids accidental open) / 平滑拉出到完全可见(不打开面板,避免误触)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Tap fully visible"})}),(0,n.jsx)(s.td,{children:"Opens the inspector panel / 打开检查器面板"})]})]})]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"This design avoids conflicts with system back gestures (Android/iOS edge swipe to go back) when pulling out from the docked state."}),"\n",(0,n.jsx)(s.p,{children:"此设计避免了从吸附态拖出时与系统返回手势(Android/iOS 边缘右滑退出)的冲突。"}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[7].id,children:i[7].value}),"\n",(0,n.jsxs)(s.p,{children:["The main viewers support ",(0,n.jsx)(s.strong,{children:"fuzzy search"}),":"]}),"\n",(0,n.jsxs)(s.p,{children:["各查看器均支持",(0,n.jsx)(s.strong,{children:"模糊搜索"}),":"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Viewer"}),(0,n.jsx)(s.th,{children:"Search Scope"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Network"}),(0,n.jsx)(s.td,{children:"URL, HTTP method / URL、请求方法"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Logs"}),(0,n.jsx)(s.td,{children:"Message, tag / 消息、标签"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Errors"}),(0,n.jsx)(s.td,{children:"Exception type, message / 异常类型、消息"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Database (global)"}),(0,n.jsx)(s.td,{children:"Database name, table name / 数据库名、表名"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Database (in-database)"}),(0,n.jsx)(s.td,{children:"Table name, all column data / 表名、所有列数据"})]})]})]}),"\n",(0,n.jsx)(s.h2,{id:i[8].id,children:i[8].value}),"\n",(0,n.jsxs)(s.p,{children:["The inspector auto-captures ",(0,n.jsx)(s.code,{children:"print()"})," output. You can also use manual log methods for precise level control:"]}),"\n",(0,n.jsxs)(s.p,{children:["检查器会自动捕获 ",(0,n.jsx)(s.code,{children:"print()"})," 输出。也可以使用手动日志方法进行精确级别控制:"]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"Quick shorthand (recommended) / 简化写法(推荐)"}),":"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"v"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Verbose log / 详细日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"d"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Debug log / 调试日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"i"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Info log / 信息日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"w"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Warning log / 警告日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"e"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Error log / 错误日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]})]})}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"Full form / 完整写法"}),":"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"verbose"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Verbose log / 详细日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"debug"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Debug log / 调试日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"info"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Info log / 信息日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"warning"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Warning log / 警告日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"error"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Error log / 错误日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]})]})}),"\n",(0,n.jsx)(s.h2,{id:i[9].id,children:i[9].value}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"No configuration needed!"})," The plugin automatically captures logs from third-party logging libraries (e.g., logger, flutter_logger) that use ",(0,n.jsx)(s.code,{children:"print()"})," or ",(0,n.jsx)(s.code,{children:"debugPrint()"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"无需配置!"})," 插件会自动捕获所有使用 ",(0,n.jsx)(s.code,{children:"print()"})," 的第三方日志库的日志。"]}),"\n",(0,n.jsxs)(s.p,{children:["These logs are categorized as ",(0,n.jsx)(s.strong,{children:"INFO level"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["这些日志统一归类为 ",(0,n.jsx)(s.strong,{children:"INFO 级别"}),"。"]}),"\n",(0,n.jsx)(s.h3,{id:i[10].id,children:i[10].value}),"\n",(0,n.jsx)(s.p,{children:"To sync inspector-captured logs to your third-party logger:"}),"\n",(0,n.jsx)(s.p,{children:"将检查器捕获的日志同步到第三方日志库:"}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.onLogCaptured "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" (entry) {"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" yourLogger."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"log"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(entry.message);"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"};"})})]})}),"\n",(0,n.jsx)(s.h2,{id:i[11].id,children:i[11].value}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Network-Inspector",children:"Network Inspector"})," — Network request details + interceptor rules / 网络检查器详情 + 拦截修改"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Log-Viewer",children:"Log Viewer"})," — Log viewing details / 日志查看器详情"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Errors",children:"Errors"})," — Aggregated error viewing / 异常聚合查看"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Database-Viewer",children:"Database Viewer"})," — Database inspection details / 数据库查看器详情"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Route-Tracker",children:"Route Tracker"})," — Route tracking details / 路由追踪详情"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Memory-Viewer",children:"Memory Viewer"})," — Memory monitoring & leak detection / 内存监控与泄漏检测"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"FPS-Viewer",children:"FPS Viewer"})," — FPS monitoring & jank detection / FPS 监控与卡顿检测"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[12].id,children:i[12].value}),"\n",(0,n.jsxs)(s.p,{children:["Logs, network requests, and aggregated errors are asynchronously flushed to a local SQLite ring buffer. On the next launch, ",(0,n.jsx)(s.strong,{children:"logs and aggregated errors replay into their tabs"}),"; network requests stay archived on disk for later export. Data therefore survives app restarts even if the inspector panel was never opened. Tap the ",(0,n.jsx)(s.strong,{children:"storage icon"})," in the panel header to open the ",(0,n.jsx)(s.strong,{children:"Persisted data"})," manager: view row counts per category, ",(0,n.jsx)(s.strong,{children:"export the full session archive"}),", or clear the disk. See ",(0,n.jsx)(s.a,{href:"Configuration",children:"Configuration"})," (PersistenceService section) for the API and tuning parameters."]}),"\n",(0,n.jsxs)(s.p,{children:["日志、网络请求与聚合异常会被异步写入本地 SQLite 环形缓冲。",(0,n.jsx)(s.strong,{children:"下次启动时,日志与聚合异常会回放入各自标签页"}),";网络请求保留在磁盘存档,供之后导出。因此即使从未打开过检查器面板,数据也能跨重启保留。点击面板头部的",(0,n.jsx)(s.strong,{children:"存储图标"}),"可打开 ",(0,n.jsx)(s.strong,{children:"Persisted data"})," 管理弹层:查看各类别行数、",(0,n.jsx)(s.strong,{children:"导出完整会话存档"}),"或清空磁盘。API 与调参详见 ",(0,n.jsx)(s.a,{href:"Configuration",children:"Configuration"}),"(PersistenceService 一节)。"]})]})},"/Usage",{filePath:"pages/Usage.md",timestamp:1789675059e3,pageMap:t.v,frontMatter:{},title:"Usage / 使用指南"},"undefined"==typeof RemoteContent?h:RemoteContent.useTOC)},7080:function(e,i,s){"use strict";s.d(i,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=4276)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Usage-d81feeeea27e1c63.js b/docs/_next/static/chunks/pages/Usage-d81feeeea27e1c63.js deleted file mode 100644 index fa268a5..0000000 --- a/docs/_next/static/chunks/pages/Usage-d81feeeea27e1c63.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[848],{4276:function(e,i,s){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Usage",function(){return s(1258)}])},1258:function(e,i,s){"use strict";s.r(i),s.d(i,{useTOC:function(){return h}});var n=s(5893),r=s(7812),t=s(7080),l=s(8925);function h(e){return[{value:"Integration Methods / 集成方式",id:"integration-methods--集成方式",depth:2},{value:"1. One-Line Integration (Recommended) / 一行代码集成(推荐)",id:"1-one-line-integration-recommended--一行代码集成推荐",depth:3},{value:"2. Two-Line Integration / 两行代码集成",id:"2-two-line-integration--两行代码集成",depth:3},{value:"Inspector Panel / 检查器面板",id:"inspector-panel--检查器面板",depth:2},{value:"One-Click Bug Report / 一键 Bug 报告",id:"one-click-bug-report--一键-bug-报告",depth:2},{value:"Floating Button / 悬浮按钮",id:"floating-button--悬浮按钮",depth:2},{value:"Edge Docking (since v1.2.0) / 边缘吸附(v1.2.0 起)",id:"edge-docking-since-v120--边缘吸附v120-起",depth:3},{value:"Search / 搜索",id:"search--搜索",depth:2},{value:"Manual Logging (Optional) / 手动记录日志(可选)",id:"manual-logging-optional--手动记录日志可选",depth:2},{value:"Third-Party Log Integration / 第三方日志库集成",id:"third-party-log-integration--第三方日志库集成",depth:2},{value:"Bidirectional Sync (Optional) / 双向同步(可选)",id:"bidirectional-sync-optional--双向同步可选",depth:3},{value:"Feature Pages / 功能详情",id:"feature-pages--功能详情",depth:2},{value:"Session Persistence / 会话持久化",id:"session-persistence--会话持久化",depth:2}]}i.default=(0,r.c)(function(e){let{toc:i=h(e)}=e,s={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.h1,{children:"Usage / 使用指南"}),"\n",(0,n.jsx)(s.h2,{id:i[0].id,children:i[0].value}),"\n",(0,n.jsx)(s.h3,{id:i[1].id,children:i[1].value}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"void"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" main"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"() {"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"runAppWithInspector"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"());"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})})]})}),"\n",(0,n.jsx)(s.p,{children:"This method:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Auto-initializes inspector / 自动初始化检查器"}),"\n",(0,n.jsxs)(s.li,{children:["Captures ",(0,n.jsx)(s.code,{children:"print()"})," via Zone / 通过 Zone 捕获 print()"]}),"\n",(0,n.jsx)(s.li,{children:"Displays floating button via Overlay / 通过 Overlay 显示悬浮按钮"}),"\n",(0,n.jsx)(s.li,{children:"Auto-injects route observer / 自动注入路由观察者"}),"\n"]}),"\n",(0,n.jsx)(s.h3,{id:i[2].id,children:i[2].value}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"void"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" main"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"() {"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"init"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"();"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:" runApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"ZeroInspectorKit"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"wrapApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"const"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:" MyApp"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"()));"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"}"})})]})}),"\n",(0,n.jsx)(s.h2,{id:i[3].id,children:i[3].value}),"\n",(0,n.jsxs)(s.p,{children:["The inspector panel contains ",(0,n.jsx)(s.strong,{children:"9 tabs"})," (as of v1.9.0):"]}),"\n",(0,n.jsxs)(s.p,{children:["检查器面板包含 ",(0,n.jsx)(s.strong,{children:"9 个标签页"}),"(v1.9.0 起):"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Tab"}),(0,n.jsx)(s.th,{children:"Icon"}),(0,n.jsx)(s.th,{children:"Feature"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Network"})}),(0,n.jsx)(s.td,{children:"\uD83C\uDF10"}),(0,n.jsx)(s.td,{children:"HTTP request viewing + interceptor rules / 网络请求查看 + 拦截修改"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Logs"})}),(0,n.jsx)(s.td,{children:"\uD83D\uDCDD"}),(0,n.jsx)(s.td,{children:"Log viewing with level filter / 日志查看"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Errors"})}),(0,n.jsx)(s.td,{children:"\uD83D\uDEA8"}),(0,n.jsx)(s.td,{children:"Aggregated & deduped crash viewing / 去重聚合的异常查看"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Database"})}),(0,n.jsx)(s.td,{children:"\uD83D\uDCBE"}),(0,n.jsx)(s.td,{children:"Database and table inspection / 数据库查看"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Memory"})}),(0,n.jsx)(s.td,{children:"\uD83D\uDCCA"}),(0,n.jsx)(s.td,{children:"Memory trend, Dart Heap, Native memory, leak detection / 内存趋势、Dart Heap、Native 内存、泄漏检测"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"FPS"})}),(0,n.jsx)(s.td,{children:"\uD83C\uDFAF"}),(0,n.jsx)(s.td,{children:"Real-time FPS, jank rate, trend chart / 实时 FPS、卡顿率、趋势图"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Routes"})}),(0,n.jsx)(s.td,{children:"\uD83E\uDDED"}),(0,n.jsx)(s.td,{children:"Route navigation tracking / 路由追踪"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Widgets"})}),(0,n.jsx)(s.td,{children:"\uD83D\uDD0D"}),(0,n.jsx)(s.td,{children:"Widget tree snapshot for the current route / 当前路由的 Widget 树快照"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Alerts"})}),(0,n.jsx)(s.td,{children:"\uD83D\uDD14"}),(0,n.jsx)(s.td,{children:"Rule-based alerts with unread badge / 基于规则的告警与未读角标"})]})]})]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:["The Memory and FPS monitors are ",(0,n.jsx)(s.strong,{children:"off by default"})," to avoid performance overhead. Toggle the switch at the top of each panel to start collecting data."]}),"\n",(0,n.jsxs)(s.p,{children:["Memory 与 FPS 监控",(0,n.jsx)(s.strong,{children:"默认关闭"}),"以避免性能开销。在各自面板顶部打开开关才会开始采集数据。"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[4].id,children:i[4].value}),"\n",(0,n.jsxs)(s.p,{children:["Tap the ",(0,n.jsx)(s.strong,{children:"bug icon"})," in the inspector panel header to generate and share a bug report in one tap — ideal for QA to attach environment context when filing issues."]}),"\n",(0,n.jsxs)(s.p,{children:["点击检查器面板头部的",(0,n.jsx)(s.strong,{children:"虫子图标"}),",即可一键生成并分享一份 Bug 报告——非常适合 QA 在提 issue 时附上环境上下文。"]}),"\n",(0,n.jsx)(s.p,{children:"The shared text snapshot includes / 分享的文本快照包含:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Device / 设备:"})," real model (e.g. ",(0,n.jsx)(s.code,{children:"Pixel 8 Pro"})," / ",(0,n.jsx)(s.code,{children:"iPhone (iPhone16,1)"}),"), OS & version, locale, Dart runtime, CPU cores."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Memory / 内存:"})," current heap usage and whether Native memory is supported."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Recent logs / 最近日志:"})," the latest captured log entries."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Recent network / 最近网络:"})," the latest captured requests."]}),"\n"]}),"\n",(0,n.jsx)(s.p,{children:"Sensitive headers are masked the same way as in the Network tab. No data leaves the device except through the share target you choose."}),"\n",(0,n.jsx)(s.p,{children:"敏感请求头会与网络标签页一样被遮蔽。除你选择的分享目标外,数据不会离开设备。"}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"Requires no extra setup — it works as soon as the inspector is running. / 无需额外配置——检查器运行后即可使用。"}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[5].id,children:i[5].value}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"The floating button appears after 1 second delay / 悬浮按钮延迟 1 秒出现"}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Drag"})," to move it along the screen edge / ",(0,n.jsx)(s.strong,{children:"拖动"})," 可沿屏幕边缘移动"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Tap"})," (when fully visible) to open/close the inspector panel / ",(0,n.jsx)(s.strong,{children:"点击"}),"(完全可见时)打开/关闭检查器面板"]}),"\n",(0,n.jsx)(s.li,{children:"Button auto-snaps to the nearest screen edge / 按钮自动吸附到最近的屏幕边缘"}),"\n",(0,n.jsx)(s.li,{children:"Breathing animation when idle / 空闲时有呼吸动画"}),"\n"]}),"\n",(0,n.jsx)(s.h3,{id:i[6].id,children:i[6].value}),"\n",(0,n.jsxs)(s.p,{children:["When released near a screen edge, the button auto-docks and tucks into the edge, leaving only a ",(0,n.jsx)(s.strong,{children:"24px peek"})," visible:"]}),"\n",(0,n.jsxs)(s.p,{children:["拖动松手后,按钮会自动吸附到最近边缘并”收入”边缘,仅露出 ",(0,n.jsx)(s.strong,{children:"24px 小弧边"}),":"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"State"}),(0,n.jsx)(s.th,{children:"Behavior / 行为"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Docked (tucked in)"})}),(0,n.jsx)(s.td,{children:"Only 24px peek visible; icon becomes a directional chevron (left dock → ➡, right dock → ⬅) hinting at tap-to-pull-out / 仅露出 24px;图标变为方向箭头提示可点击拉出"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Tap docked peek"})}),(0,n.jsx)(s.td,{children:"Smoothly pulls out to fully visible (panel NOT opened, avoids accidental open) / 平滑拉出到完全可见(不打开面板,避免误触)"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.strong,{children:"Tap fully visible"})}),(0,n.jsx)(s.td,{children:"Opens the inspector panel / 打开检查器面板"})]})]})]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsx)(s.p,{children:"This design avoids conflicts with system back gestures (Android/iOS edge swipe to go back) when pulling out from the docked state."}),"\n",(0,n.jsx)(s.p,{children:"此设计避免了从吸附态拖出时与系统返回手势(Android/iOS 边缘右滑退出)的冲突。"}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[7].id,children:i[7].value}),"\n",(0,n.jsxs)(s.p,{children:["The main viewers support ",(0,n.jsx)(s.strong,{children:"fuzzy search"}),":"]}),"\n",(0,n.jsxs)(s.p,{children:["各查看器均支持",(0,n.jsx)(s.strong,{children:"模糊搜索"}),":"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Viewer"}),(0,n.jsx)(s.th,{children:"Search Scope"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Network"}),(0,n.jsx)(s.td,{children:"URL, HTTP method / URL、请求方法"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Logs"}),(0,n.jsx)(s.td,{children:"Message, tag / 消息、标签"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Errors"}),(0,n.jsx)(s.td,{children:"Exception type, message / 异常类型、消息"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Database (global)"}),(0,n.jsx)(s.td,{children:"Database name, table name / 数据库名、表名"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:"Database (in-database)"}),(0,n.jsx)(s.td,{children:"Table name, all column data / 表名、所有列数据"})]})]})]}),"\n",(0,n.jsx)(s.h2,{id:i[8].id,children:i[8].value}),"\n",(0,n.jsxs)(s.p,{children:["The inspector auto-captures ",(0,n.jsx)(s.code,{children:"print()"})," output. You can also use manual log methods for precise level control:"]}),"\n",(0,n.jsxs)(s.p,{children:["检查器会自动捕获 ",(0,n.jsx)(s.code,{children:"print()"})," 输出。也可以使用手动日志方法进行精确级别控制:"]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"Quick shorthand (recommended) / 简化写法(推荐)"}),":"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"v"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Verbose log / 详细日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"d"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Debug log / 调试日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"i"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Info log / 信息日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"w"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Warning log / 警告日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLog"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"e"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Error log / 错误日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]})]})}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"Full form / 完整写法"}),":"]}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"verbose"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Verbose log / 详细日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"debug"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Debug log / 调试日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"info"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Info log / 信息日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"warning"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Warning log / 警告日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"error"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"("}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"'Error log / 错误日志'"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:");"})]})]})}),"\n",(0,n.jsx)(s.h2,{id:i[9].id,children:i[9].value}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"No configuration needed!"})," The plugin automatically captures logs from third-party logging libraries (e.g., logger, flutter_logger) that use ",(0,n.jsx)(s.code,{children:"print()"})," or ",(0,n.jsx)(s.code,{children:"debugPrint()"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"无需配置!"})," 插件会自动捕获所有使用 ",(0,n.jsx)(s.code,{children:"print()"})," 的第三方日志库的日志。"]}),"\n",(0,n.jsxs)(s.p,{children:["These logs are categorized as ",(0,n.jsx)(s.strong,{children:"INFO level"}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["这些日志统一归类为 ",(0,n.jsx)(s.strong,{children:"INFO 级别"}),"。"]}),"\n",(0,n.jsx)(s.h3,{id:i[10].id,children:i[10].value}),"\n",(0,n.jsx)(s.p,{children:"To sync inspector-captured logs to your third-party logger:"}),"\n",(0,n.jsx)(s.p,{children:"将检查器捕获的日志同步到第三方日志库:"}),"\n",(0,n.jsx)(s.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(s.code,{children:[(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"},children:"InspectorLogInterceptor"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:".instance.onLogCaptured "}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"="}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" (entry) {"})]}),"\n",(0,n.jsxs)(s.span,{children:[(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:" yourLogger."}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"log"}),(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"(entry.message);"})]}),"\n",(0,n.jsx)(s.span,{children:(0,n.jsx)(s.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:"};"})})]})}),"\n",(0,n.jsx)(s.h2,{id:i[11].id,children:i[11].value}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Network-Inspector",children:"Network Inspector"})," — Network request details + interceptor rules / 网络检查器详情 + 拦截修改"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Log-Viewer",children:"Log Viewer"})," — Log viewing details / 日志查看器详情"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Errors",children:"Errors"})," — Aggregated error viewing / 异常聚合查看"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Database-Viewer",children:"Database Viewer"})," — Database inspection details / 数据库查看器详情"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Route-Tracker",children:"Route Tracker"})," — Route tracking details / 路由追踪详情"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"Memory-Viewer",children:"Memory Viewer"})," — Memory monitoring & leak detection / 内存监控与泄漏检测"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"FPS-Viewer",children:"FPS Viewer"})," — FPS monitoring & jank detection / FPS 监控与卡顿检测"]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:i[12].id,children:i[12].value}),"\n",(0,n.jsxs)(s.p,{children:["Logs, network requests, and aggregated errors are asynchronously flushed to a local SQLite ring buffer. On the next launch, ",(0,n.jsx)(s.strong,{children:"logs and aggregated errors replay into their tabs"}),"; network requests stay archived on disk for later export. Data therefore survives app restarts even if the inspector panel was never opened. Tap the ",(0,n.jsx)(s.strong,{children:"storage icon"})," in the panel header to open the ",(0,n.jsx)(s.strong,{children:"Persisted data"})," manager: view row counts per category, ",(0,n.jsx)(s.strong,{children:"export the full session archive"}),", or clear the disk. See ",(0,n.jsx)(s.a,{href:"Configuration",children:"Configuration"})," (PersistenceService section) for the API and tuning parameters."]}),"\n",(0,n.jsxs)(s.p,{children:["日志、网络请求与聚合异常会被异步写入本地 SQLite 环形缓冲。",(0,n.jsx)(s.strong,{children:"下次启动时,日志与聚合异常会回放入各自标签页"}),";网络请求保留在磁盘存档,供之后导出。因此即使从未打开过检查器面板,数据也能跨重启保留。点击面板头部的",(0,n.jsx)(s.strong,{children:"存储图标"}),"可打开 ",(0,n.jsx)(s.strong,{children:"Persisted data"})," 管理弹层:查看各类别行数、",(0,n.jsx)(s.strong,{children:"导出完整会话存档"}),"或清空磁盘。API 与调参详见 ",(0,n.jsx)(s.a,{href:"Configuration",children:"Configuration"}),"(PersistenceService 一节)。"]})]})},"/Usage",{filePath:"pages/Usage.md",timestamp:1787932379e3,pageMap:t.v,frontMatter:{},title:"Usage / 使用指南"},"undefined"==typeof RemoteContent?h:RemoteContent.useTOC)},7080:function(e,i,s){"use strict";s.d(i,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=4276)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/index-09093d7a1b2e97fb.js b/docs/_next/static/chunks/pages/index-09093d7a1b2e97fb.js new file mode 100644 index 0000000..41d330a --- /dev/null +++ b/docs/_next/static/chunks/pages/index-09093d7a1b2e97fb.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[405],{2725:function(e,n,r){(window.__NEXT_P=window.__NEXT_P||[]).push(["/",function(){return r(4991)}])},4991:function(e,n,r){"use strict";r.r(n),r.d(n,{useTOC:function(){return a}});var t=r(5893),i=r(7812),s=r(7080),d=r(8925);function a(e){return[{value:"✨ Features / 功能特性",id:"-features--功能特性",depth:2},{value:"\uD83D\uDCDA Table of Contents / 目录",id:"-table-of-contents--目录",depth:2},{value:"\uD83D\uDD17 Links / 链接",id:"-links--链接",depth:2},{value:"\uD83D\uDCC4 License / 许可证",id:"-license--许可证",depth:2}]}n.default=(0,i.c)(function(e){let{toc:n=a(e)}=e,r={a:"a",h1:"h1",h2:"h2",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.a)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(r.h1,{children:"Zero Inspector Kit"}),"\n",(0,t.jsx)(r.p,{children:"A powerful Flutter plugin for in-app developer console, providing real-time debugging tools including network request inspection, logging, error aggregation, database viewing, and route tracking."}),"\n",(0,t.jsx)(r.p,{children:"一个功能强大的 Flutter 插件,提供应用内开发者控制台,包括网络请求检查、日志记录、异常聚合、数据库查看和路由追踪。"}),"\n",(0,t.jsx)(r.h2,{id:n[0].id,children:n[0].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,t.jsxs)(r.table,{children:[(0,t.jsx)(r.thead,{children:(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.th,{children:"Feature"}),(0,t.jsx)(r.th,{children:"Description"})]})}),(0,t.jsxs)(r.tbody,{children:[(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Zero Invasion"})}),(0,t.jsx)(r.td,{children:"Integrate with 1 line of code / 一行代码集成"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Network Inspector"})}),(0,t.jsx)(r.td,{children:"Real-time HTTP request viewing (http + Dio) / 实时网络请求查看"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Log Viewer"})}),(0,t.jsx)(r.td,{children:"Auto-capture print()/debugPrint() and third-party logs / 自动捕获日志"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Errors"})}),(0,t.jsx)(r.td,{children:"Aggregate & dedupe crashes by type + stack, with count and first/last seen / 按类型+堆栈去重聚合崩溃,记录次数与首末次时间"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Database Viewer"})}),(0,t.jsx)(r.td,{children:"SQLite inspection with table data / 数据库查看"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Memory Viewer"})}),(0,t.jsx)(r.td,{children:"Trend chart, Dart Heap, Native memory, leak detection (incl. Flutter MemoryAllocations bridge) / 内存趋势图、Dart Heap、Native 内存、泄漏检测(含官方 MemoryAllocations 桥接)"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"FPS Monitor"})}),(0,t.jsx)(r.td,{children:"Real-time FPS, jank rate, trend chart / 实时 FPS、卡顿率、趋势图"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Route Tracker"})}),(0,t.jsx)(r.td,{children:"Navigation history tracking / 路由追踪"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Session Timeline"})}),(0,t.jsx)(r.td,{children:"Unified view of network/logs/errors/routes/alerts by time, with \xb1N-second focus / 网络/日志/异常/路由/告警按时间归并,支持 \xb1N 秒聚焦"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Session Persistence"})}),(0,t.jsx)(r.td,{children:"Logs/network/errors survive restarts via a disk ring buffer; logs & errors replay on launch; one-tap session archive export / 日志/网络/异常通过磁盘环形缓冲跨重启保留,启动时回放日志与异常;一键导出会话存档"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Alerts"})}),(0,t.jsx)(r.td,{children:"Rule-based alerts (network/log/memory/FPS) with unread badge / 基于规则的告警(网络/日志/内存/FPS)与未读角标"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Sensitive Masking & cURL"})}),(0,t.jsx)(r.td,{children:"Mask secrets on export; one-click cURL copy; batch ops / 导出遮蔽敏感字段、一键复制 cURL、批量操作"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Fuzzy Search"})}),(0,t.jsx)(r.td,{children:"Search in all viewers / 各查看器模糊搜索"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.strong,{children:"Cross-platform"})}),(0,t.jsx)(r.td,{children:"Android, iOS / 跨平台支持"})]})]})]}),"\n",(0,t.jsx)(r.h2,{id:n[1].id,children:n[1].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,t.jsxs)(r.table,{children:[(0,t.jsx)(r.thead,{children:(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.th,{children:"Page"}),(0,t.jsx)(r.th,{children:"Description"})]})}),(0,t.jsxs)(r.tbody,{children:[(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Getting-Started",children:"Getting Started"})}),(0,t.jsx)(r.td,{children:"Quick start guide / 快速开始"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Installation",children:"Installation"})}),(0,t.jsx)(r.td,{children:"How to install / 安装方式"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Usage",children:"Usage"})}),(0,t.jsx)(r.td,{children:"Detailed usage / 详细使用"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Network-Inspector",children:"Network Inspector"})}),(0,t.jsx)(r.td,{children:"Network request viewing / 网络检查器"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Log-Viewer",children:"Log Viewer"})}),(0,t.jsx)(r.td,{children:"Log capturing and viewing / 日志查看器"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Errors",children:"Errors"})}),(0,t.jsx)(r.td,{children:"Aggregated error viewing / 异常聚合查看"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Database-Viewer",children:"Database Viewer"})}),(0,t.jsx)(r.td,{children:"Database inspection / 数据库查看器"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Route-Tracker",children:"Route Tracker"})}),(0,t.jsx)(r.td,{children:"Route tracking / 路由追踪"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Timeline",children:"Timeline"})}),(0,t.jsx)(r.td,{children:"Unified session timeline / 统一会话时间线"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Memory-Viewer",children:"Memory Viewer"})}),(0,t.jsx)(r.td,{children:"Memory monitoring & leak detection / 内存监控与泄漏检测"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"FPS-Viewer",children:"FPS Viewer"})}),(0,t.jsx)(r.td,{children:"FPS monitoring & jank detection / FPS 监控与卡顿检测"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Alerts",children:"Alerts"})}),(0,t.jsx)(r.td,{children:"Rule-based alerting / 基于规则的告警"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Configuration",children:"Configuration"})}),(0,t.jsx)(r.td,{children:"Configuration options / 配置说明"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"Custom-Database-Provider",children:"Custom Database Provider"})}),(0,t.jsx)(r.td,{children:"Extend database support / 自定义数据库提供者"})]}),(0,t.jsxs)(r.tr,{children:[(0,t.jsx)(r.td,{children:(0,t.jsx)(r.a,{href:"FAQ",children:"FAQ"})}),(0,t.jsx)(r.td,{children:"Frequently asked questions / 常见问题"})]})]})]}),"\n",(0,t.jsx)(r.h2,{id:n[2].id,children:n[2].value}),"\n",(0,t.jsxs)(r.ul,{children:["\n",(0,t.jsx)(r.li,{children:(0,t.jsx)(r.a,{href:"https://github.com/zero-labsco/zero_inspector_kit",children:"GitHub"})}),"\n",(0,t.jsx)(r.li,{children:(0,t.jsx)(r.a,{href:"https://www.zerolabsco.com/",children:"Official Website"})}),"\n",(0,t.jsx)(r.li,{children:(0,t.jsx)(r.a,{href:"https://pub.dev/packages/zero_inspector_kit",children:"pub.dev"})}),"\n"]}),"\n",(0,t.jsx)(r.h2,{id:n[3].id,children:n[3].value}),"\n",(0,t.jsxs)(r.p,{children:["This project is licensed under the ",(0,t.jsx)(r.strong,{children:"GNU General Public License v3.0"}),"."]}),"\n",(0,t.jsx)(r.p,{children:"本项目采用 GNU General Public License v3.0 许可证。"}),"\n",(0,t.jsx)(r.p,{children:"This plugin is provided “as is”, without warranty of any kind. The author assumes no responsibility or liability for the functionality, security, or any consequences arising from the use of modified versions or derivative projects."}),"\n",(0,t.jsx)(r.p,{children:"本插件按”原样”提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及任何使用后果承担责任。"})]})},"/",{filePath:"pages/index.md",timestamp:1788758921e3,pageMap:s.v,frontMatter:{},title:"Zero Inspector Kit"},"undefined"==typeof RemoteContent?a:RemoteContent.useTOC)},7080:function(e,n,r){"use strict";r.d(n,{v:function(){return t}});let t=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker",Timeline:"\uD83E\uDDF5 Timeline","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Timeline",route:"/Timeline",frontMatter:{sidebarTitle:"Timeline"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=2725)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/index-2690f32ccd636754.js b/docs/_next/static/chunks/pages/index-2690f32ccd636754.js deleted file mode 100644 index c8fd513..0000000 --- a/docs/_next/static/chunks/pages/index-2690f32ccd636754.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[405],{2725:function(e,r,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/",function(){return n(4991)}])},4991:function(e,r,n){"use strict";n.r(r),n.d(r,{useTOC:function(){return a}});var t=n(5893),i=n(7812),s=n(7080),d=n(8925);function a(e){return[{value:"✨ Features / 功能特性",id:"-features--功能特性",depth:2},{value:"\uD83D\uDCDA Table of Contents / 目录",id:"-table-of-contents--目录",depth:2},{value:"\uD83D\uDD17 Links / 链接",id:"-links--链接",depth:2},{value:"\uD83D\uDCC4 License / 许可证",id:"-license--许可证",depth:2}]}r.default=(0,i.c)(function(e){let{toc:r=a(e)}=e,n={a:"a",h1:"h1",h2:"h2",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.a)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{children:"Zero Inspector Kit"}),"\n",(0,t.jsx)(n.p,{children:"A powerful Flutter plugin for in-app developer console, providing real-time debugging tools including network request inspection, logging, error aggregation, database viewing, and route tracking."}),"\n",(0,t.jsx)(n.p,{children:"一个功能强大的 Flutter 插件,提供应用内开发者控制台,包括网络请求检查、日志记录、异常聚合、数据库查看和路由追踪。"}),"\n",(0,t.jsx)(n.h2,{id:r[0].id,children:r[0].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Feature"}),(0,t.jsx)(n.th,{children:"Description"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Zero Invasion"})}),(0,t.jsx)(n.td,{children:"Integrate with 1 line of code / 一行代码集成"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Network Inspector"})}),(0,t.jsx)(n.td,{children:"Real-time HTTP request viewing (http + Dio) / 实时网络请求查看"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Log Viewer"})}),(0,t.jsx)(n.td,{children:"Auto-capture print()/debugPrint() and third-party logs / 自动捕获日志"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Errors"})}),(0,t.jsx)(n.td,{children:"Aggregate & dedupe crashes by type + stack, with count and first/last seen / 按类型+堆栈去重聚合崩溃,记录次数与首末次时间"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Database Viewer"})}),(0,t.jsx)(n.td,{children:"SQLite inspection with table data / 数据库查看"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Memory Viewer"})}),(0,t.jsx)(n.td,{children:"Trend chart, Dart Heap, Native memory, leak detection (incl. Flutter MemoryAllocations bridge) / 内存趋势图、Dart Heap、Native 内存、泄漏检测(含官方 MemoryAllocations 桥接)"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"FPS Monitor"})}),(0,t.jsx)(n.td,{children:"Real-time FPS, jank rate, trend chart / 实时 FPS、卡顿率、趋势图"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Route Tracker"})}),(0,t.jsx)(n.td,{children:"Navigation history tracking / 路由追踪"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Session Persistence"})}),(0,t.jsx)(n.td,{children:"Logs/network/errors survive restarts via a disk ring buffer; logs & errors replay on launch; one-tap session archive export / 日志/网络/异常通过磁盘环形缓冲跨重启保留,启动时回放日志与异常;一键导出会话存档"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Alerts"})}),(0,t.jsx)(n.td,{children:"Rule-based alerts (network/log/memory/FPS) with unread badge / 基于规则的告警(网络/日志/内存/FPS)与未读角标"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Sensitive Masking & cURL"})}),(0,t.jsx)(n.td,{children:"Mask secrets on export; one-click cURL copy; batch ops / 导出遮蔽敏感字段、一键复制 cURL、批量操作"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Fuzzy Search"})}),(0,t.jsx)(n.td,{children:"Search in all viewers / 各查看器模糊搜索"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Cross-platform"})}),(0,t.jsx)(n.td,{children:"Android, iOS / 跨平台支持"})]})]})]}),"\n",(0,t.jsx)(n.h2,{id:r[1].id,children:r[1].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Page"}),(0,t.jsx)(n.th,{children:"Description"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"Getting-Started",children:"Getting Started"})}),(0,t.jsx)(n.td,{children:"Quick start guide / 快速开始"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"Installation",children:"Installation"})}),(0,t.jsx)(n.td,{children:"How to install / 安装方式"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"Usage",children:"Usage"})}),(0,t.jsx)(n.td,{children:"Detailed usage / 详细使用"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"Network-Inspector",children:"Network Inspector"})}),(0,t.jsx)(n.td,{children:"Network request viewing / 网络检查器"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"Log-Viewer",children:"Log Viewer"})}),(0,t.jsx)(n.td,{children:"Log capturing and viewing / 日志查看器"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"Errors",children:"Errors"})}),(0,t.jsx)(n.td,{children:"Aggregated error viewing / 异常聚合查看"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"Database-Viewer",children:"Database Viewer"})}),(0,t.jsx)(n.td,{children:"Database inspection / 数据库查看器"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"Route-Tracker",children:"Route Tracker"})}),(0,t.jsx)(n.td,{children:"Route tracking / 路由追踪"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"Memory-Viewer",children:"Memory Viewer"})}),(0,t.jsx)(n.td,{children:"Memory monitoring & leak detection / 内存监控与泄漏检测"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"FPS-Viewer",children:"FPS Viewer"})}),(0,t.jsx)(n.td,{children:"FPS monitoring & jank detection / FPS 监控与卡顿检测"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"Alerts",children:"Alerts"})}),(0,t.jsx)(n.td,{children:"Rule-based alerting / 基于规则的告警"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"Configuration",children:"Configuration"})}),(0,t.jsx)(n.td,{children:"Configuration options / 配置说明"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"Custom-Database-Provider",children:"Custom Database Provider"})}),(0,t.jsx)(n.td,{children:"Extend database support / 自定义数据库提供者"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.a,{href:"FAQ",children:"FAQ"})}),(0,t.jsx)(n.td,{children:"Frequently asked questions / 常见问题"})]})]})]}),"\n",(0,t.jsx)(n.h2,{id:r[2].id,children:r[2].value}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://github.com/zero-labsco/zero_inspector_kit",children:"GitHub"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://www.zerolabsco.com/",children:"Official Website"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://pub.dev/packages/zero_inspector_kit",children:"pub.dev"})}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:r[3].id,children:r[3].value}),"\n",(0,t.jsxs)(n.p,{children:["This project is licensed under the ",(0,t.jsx)(n.strong,{children:"GNU General Public License v3.0"}),"."]}),"\n",(0,t.jsx)(n.p,{children:"本项目采用 GNU General Public License v3.0 许可证。"}),"\n",(0,t.jsx)(n.p,{children:"This plugin is provided “as is”, without warranty of any kind. The author assumes no responsibility or liability for the functionality, security, or any consequences arising from the use of modified versions or derivative projects."}),"\n",(0,t.jsx)(n.p,{children:"本插件按”原样”提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及任何使用后果承担责任。"})]})},"/",{filePath:"pages/index.md",timestamp:1787178796e3,pageMap:s.v,frontMatter:{},title:"Zero Inspector Kit"},"undefined"==typeof RemoteContent?a:RemoteContent.useTOC)},7080:function(e,r,n){"use strict";n.d(r,{v:function(){return t}});let t=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--features":{type:"separator",title:"\uD83D\uDD27 Features"},"Network-Inspector":"\uD83C\uDF10 Network Inspector","Log-Viewer":"\uD83D\uDCDD Log Viewer",Errors:"\uD83D\uDEA8 Errors","Database-Viewer":"\uD83D\uDCBE Database Viewer","Route-Tracker":"\uD83E\uDDED Route Tracker","Memory-Viewer":"\uD83D\uDCCA Memory Viewer","FPS-Viewer":"\uD83C\uDFAF FPS Viewer",Alerts:"\uD83D\uDD14 Alerts","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},Configuration:"⚙️ Configuration","Custom-Database-Provider":"\uD83D\uDD0C Custom Database Provider","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"Alerts",route:"/Alerts",frontMatter:{sidebarTitle:"Alerts"}},{name:"Configuration",route:"/Configuration",frontMatter:{sidebarTitle:"Configuration"}},{name:"Custom-Database-Provider",route:"/Custom-Database-Provider",frontMatter:{sidebarTitle:"Custom Database Provider"}},{name:"Database-Viewer",route:"/Database-Viewer",frontMatter:{sidebarTitle:"Database Viewer"}},{name:"Errors",route:"/Errors",frontMatter:{sidebarTitle:"Errors"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"FPS-Viewer",route:"/FPS-Viewer",frontMatter:{sidebarTitle:"Fps Viewer"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Log-Viewer",route:"/Log-Viewer",frontMatter:{sidebarTitle:"Log Viewer"}},{name:"Memory-Viewer",route:"/Memory-Viewer",frontMatter:{sidebarTitle:"Memory Viewer"}},{name:"Network-Inspector",route:"/Network-Inspector",frontMatter:{sidebarTitle:"Network Inspector"}},{name:"Route-Tracker",route:"/Route-Tracker",frontMatter:{sidebarTitle:"Route Tracker"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=2725)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 9aae22d..e0d792c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,4 +1,4 @@ -
    🏠 Home

    Zero Inspector Kit

    +
    🏠 Home

    Zero Inspector Kit

    A powerful Flutter plugin for in-app developer console, providing real-time debugging tools including network request inspection, logging, error aggregation, database viewing, and route tracking.

    一个功能强大的 Flutter 插件,提供应用内开发者控制台,包括网络请求检查、日志记录、异常聚合、数据库查看和路由追踪。

    ✨ Features / 功能特性

    @@ -63,7 +63,11 @@

    FeatureDescriptionZero InvasionIntegrate with 1 line of code / 一行代码集成Network InspectorReal-time HTTP request viewing (http + Dio) / 实时网络请求查看Log ViewerAuto-capture print()/debugPrint() and third-party logs / 自动捕获日志ErrorsAggregate & dedupe crashes by type + stack, with count and first/last seen / 按类型+堆栈去重聚合崩溃,记录次数与首末次时间Database ViewerSQLite inspection with table data / 数据库查看Memory ViewerTrend chart, Dart Heap, Native memory, leak detection (incl. Flutter MemoryAllocations bridge) / 内存趋势图、Dart Heap、Native 内存、泄漏检测(含官方 MemoryAllocations 桥接)FPS MonitorReal-time FPS, jank rate, trend chart / 实时 FPS、卡顿率、趋势图Route TrackerNavigation history tracking / 路由追踪Session PersistenceLogs/network/errors survive restarts via a disk ring buffer; logs & errors replay on launch; one-tap session archive export / 日志/网络/异常通过磁盘环形缓冲跨重启保留,启动时回放日志与异常;一键导出会话存档AlertsRule-based alerts (network/log/memory/FPS) with unread badge / 基于规则的告警(网络/日志/内存/FPS)与未读角标Sensitive Masking & cURLMask secrets on export; one-click cURL copy; batch ops / 导出遮蔽敏感字段、一键复制 cURL、批量操作Fuzzy SearchSearch in all viewers / 各查看器模糊搜索Cross-platformAndroid, iOS / 跨平台支持 + + + + +
    FeatureDescription
    Zero InvasionIntegrate with 1 line of code / 一行代码集成
    Network InspectorReal-time HTTP request viewing (http + Dio) / 实时网络请求查看
    Log ViewerAuto-capture print()/debugPrint() and third-party logs / 自动捕获日志
    ErrorsAggregate & dedupe crashes by type + stack, with count and first/last seen / 按类型+堆栈去重聚合崩溃,记录次数与首末次时间
    Database ViewerSQLite inspection with table data / 数据库查看
    Memory ViewerTrend chart, Dart Heap, Native memory, leak detection (incl. Flutter MemoryAllocations bridge) / 内存趋势图、Dart Heap、Native 内存、泄漏检测(含官方 MemoryAllocations 桥接)
    FPS MonitorReal-time FPS, jank rate, trend chart / 实时 FPS、卡顿率、趋势图
    Route TrackerNavigation history tracking / 路由追踪
    Session TimelineUnified view of network/logs/errors/routes/alerts by time, with ±N-second focus / 网络/日志/异常/路由/告警按时间归并,支持 ±N 秒聚焦
    Session PersistenceLogs/network/errors survive restarts via a disk ring buffer; logs & errors replay on launch; one-tap session archive export / 日志/网络/异常通过磁盘环形缓冲跨重启保留,启动时回放日志与异常;一键导出会话存档
    AlertsRule-based alerts (network/log/memory/FPS) with unread badge / 基于规则的告警(网络/日志/内存/FPS)与未读角标
    Sensitive Masking & cURLMask secrets on export; one-click cURL copy; batch ops / 导出遮蔽敏感字段、一键复制 cURL、批量操作
    Fuzzy SearchSearch in all viewers / 各查看器模糊搜索
    Cross-platformAndroid, iOS / 跨平台支持

    📚 Table of Contents / 目录

    @@ -130,7 +134,11 @@

    PageDescriptionGetting StartedQuick start guide / 快速开始InstallationHow to install / 安装方式UsageDetailed usage / 详细使用Network InspectorNetwork request viewing / 网络检查器Log ViewerLog capturing and viewing / 日志查看器ErrorsAggregated error viewing / 异常聚合查看Database ViewerDatabase inspection / 数据库查看器Route TrackerRoute tracking / 路由追踪Memory ViewerMemory monitoring & leak detection / 内存监控与泄漏检测FPS ViewerFPS monitoring & jank detection / FPS 监控与卡顿检测AlertsRule-based alerting / 基于规则的告警ConfigurationConfiguration options / 配置说明Custom Database ProviderExtend database support / 自定义数据库提供者FAQFrequently asked questions / 常见问题 + + + + +
    PageDescription
    Getting StartedQuick start guide / 快速开始
    InstallationHow to install / 安装方式
    UsageDetailed usage / 详细使用
    Network InspectorNetwork request viewing / 网络检查器
    Log ViewerLog capturing and viewing / 日志查看器
    ErrorsAggregated error viewing / 异常聚合查看
    Database ViewerDatabase inspection / 数据库查看器
    Route TrackerRoute tracking / 路由追踪
    TimelineUnified session timeline / 统一会话时间线
    Memory ViewerMemory monitoring & leak detection / 内存监控与泄漏检测
    FPS ViewerFPS monitoring & jank detection / FPS 监控与卡顿检测
    AlertsRule-based alerting / 基于规则的告警
    ConfigurationConfiguration options / 配置说明
    Custom Database ProviderExtend database support / 自定义数据库提供者
    FAQFrequently asked questions / 常见问题

    • GitHub
    • @@ -141,4 +149,4 @@

      This project is licensed under the GNU General Public License v3.0.

      本项目采用 GNU General Public License v3.0 许可证。

      This plugin is provided “as is”, without warranty of any kind. The author assumes no responsibility or liability for the functionality, security, or any consequences arising from the use of modified versions or derivative projects.

      -

      本插件按”原样”提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及任何使用后果承担责任。


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file +

    本插件按”原样”提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及任何使用后果承担责任。


    Zero Inspector Kit · MPL-2.0
    \ No newline at end of file diff --git a/example/pubspec.lock b/example/pubspec.lock index 057da3b..bf4ed0a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -738,7 +738,7 @@ packages: path: ".." relative: true source: path - version: "1.11.0" + version: "1.12.0" sdks: dart: ">=3.11.0 <4.0.0" flutter: ">=3.38.4" diff --git a/ios/zero_inspector_kit.podspec b/ios/zero_inspector_kit.podspec index 1fd2e49..4e68dfc 100644 --- a/ios/zero_inspector_kit.podspec +++ b/ios/zero_inspector_kit.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'zero_inspector_kit' - s.version = '1.11.1' + s.version = '1.12.0' s.summary = 'An in-app developer console for Flutter: network, logs, database, memory, FPS, errors, alerts and routes.' s.description = <<-DESC An in-app developer console for Flutter that inspects HTTP/Dio traffic, WebSocket and gRPC streams, logs, databases, memory and FPS (with build/raster split), aggregates and persists errors and alerts, tracks routes, and exports a shareable session archive - all auto-disabled in release builds. diff --git a/lib/src/models/blocking_event.dart b/lib/src/models/blocking_event.dart new file mode 100644 index 0000000..47c178f --- /dev/null +++ b/lib/src/models/blocking_event.dart @@ -0,0 +1,34 @@ +/// 主线程阻塞事件 / Main-thread blocking event +/// +/// 一次"心跳超时"代表 UI isolate 在 [durationMs] 内没能响应心跳定时器, +/// 通常是同步重活(大 JSON 解析、密集 build)、锁等待或平台通道阻塞。 +/// A heartbeat timeout means the UI isolate failed to answer the watchdog timer +/// for [durationMs] — usually a heavy synchronous task (big JSON parse, dense +/// build), a lock wait, or a blocked platform channel. +class BlockingEvent { + /// 阻塞开始(上一次正常心跳时刻,即阻塞区间的近似起点) + /// Blocking start (the last healthy heartbeat — approximate start of the stall) + final DateTime startAt; + + /// 阻塞结束(恢复后第一次心跳时刻)/ Blocking end (first heartbeat after recovery) + final DateTime endAt; + + /// 阻塞时长(毫秒)/ Blocking duration (ms) + final int durationMs; + + /// 阻塞窗口附近的日志摘要,用于定位"卡在做什么" + /// Log summaries around the stall window, to identify what was blocking + final List nearbyLogs; + + const BlockingEvent({ + required this.startAt, + required this.endAt, + required this.durationMs, + this.nearbyLogs = const [], + }); + + /// 时长文本(超过 1 秒用秒表示)/ Duration text (seconds above 1s) + String get durationText => durationMs >= 1000 + ? '${(durationMs / 1000).toStringAsFixed(2)}s' + : '${durationMs}ms'; +} diff --git a/lib/src/models/timeline_event.dart b/lib/src/models/timeline_event.dart new file mode 100644 index 0000000..191d948 --- /dev/null +++ b/lib/src/models/timeline_event.dart @@ -0,0 +1,74 @@ +/// 统一时间线的事件来源 / Event source on the unified timeline +/// +/// 对应面板里已有的五类数据:网络 / 日志 / 异常 / 路由 / 告警。 +/// Mirrors the five data categories already in the panel: network / log / +/// error / route / alert. +enum TimelineEventKind { network, log, error, route, alert } + +/// [TimelineEventKind] 的展示文本 / Display text for [TimelineEventKind] +extension TimelineEventKindText on TimelineEventKind { + /// 来源短标签(列表 chip 用)/ Short source label (list chip) + String get label { + switch (this) { + case TimelineEventKind.network: + return 'NET'; + case TimelineEventKind.log: + return 'LOG'; + case TimelineEventKind.error: + return 'ERR'; + case TimelineEventKind.route: + return 'ROUTE'; + case TimelineEventKind.alert: + return 'ALERT'; + } + } +} + +/// 统一时间线事件 / Unified timeline event +/// +/// 把网络请求、日志、异常、路由与告警归一成同一种记录,从而能回答 +/// 「这个请求的前后还发生了什么」——这是单个标签页无法回答的问题。 +/// Normalizes network requests, logs, errors, routes and alerts into one record +/// type so the panel can answer "what else happened around this request?" — +/// something no single tab can answer. +class TimelineEvent { + /// 事件来源 / Event source + final TimelineEventKind kind; + + /// 事件时间 / Event timestamp + final DateTime timestamp; + + /// 主标题(单行展示,UI 负责截断)/ Main title (single line; UI truncates) + final String title; + + /// 副标题(可选)/ Optional subtitle + final String? subtitle; + + /// 完整详情(展开后展示)/ Full detail (shown when expanded) + final String detail; + + /// 原始记录 id(用于回查原数据,可为空)/ Source record id (may be null) + final String? sourceId; + + const TimelineEvent({ + required this.kind, + required this.timestamp, + required this.title, + this.subtitle, + this.detail = '', + this.sourceId, + }); + + /// 展开态的稳定 key:事件对象每次归并都重建,不能用对象身份做展开态标识。 + /// Stable key for expansion state: events are rebuilt on every merge, so + /// object identity cannot identify an expanded row. + String get key => + '${kind.name}|${sourceId ?? ''}|${timestamp.microsecondsSinceEpoch}'; + + /// 时间文本(HH:mm:ss.SSS)/ Time text (HH:mm:ss.SSS) + String get timeText { + String two(int v) => v.toString().padLeft(2, '0'); + return '${two(timestamp.hour)}:${two(timestamp.minute)}:${two(timestamp.second)}' + '.${timestamp.millisecond.toString().padLeft(3, '0')}'; + } +} diff --git a/lib/src/services/blocking_watchdog_service.dart b/lib/src/services/blocking_watchdog_service.dart new file mode 100644 index 0000000..3e22f0f --- /dev/null +++ b/lib/src/services/blocking_watchdog_service.dart @@ -0,0 +1,178 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:flutter/foundation.dart'; + +import '../models/blocking_event.dart'; +import '../models/log_entry.dart'; +import 'inspector_service.dart'; + +/// 主线程阻塞看门狗 / Main-thread blocking watchdog +/// +/// FPS 监控只能看见"有帧产出"的卡顿:真正卡死时(长同步任务、锁等待、 +/// 平台通道阻塞)引擎根本不产帧,FPS 反而显示为空闲。本服务用一个低频心跳 +/// 定时器直接测量 UI isolate 的响应间隔,补上这块盲区。 +/// FPS monitoring only sees jank that still produces frames. During a real +/// stall (long synchronous work, lock wait, blocked platform channel) the engine +/// produces no frames at all, and FPS reads as "idle". This service measures the +/// UI isolate's responsiveness directly with a low-frequency heartbeat, closing +/// that blind spot. +/// +/// 心跳本身极轻(每 100ms 只做一次时间比较),默认关闭,由 FPS 页开关启动。 +/// The heartbeat is extremely cheap (one time comparison per 100ms), off by +/// default, and started from the FPS tab switch. +class BlockingWatchdogService extends ChangeNotifier { + BlockingWatchdogService._(); + + /// 单例实例 / Singleton instance + static final BlockingWatchdogService instance = BlockingWatchdogService._(); + + /// 心跳间隔 / Heartbeat interval + static const Duration tickInterval = Duration(milliseconds: 100); + + /// 默认阻塞判定阈值(毫秒):心跳间隔的 3 倍,留足定时器抖动余量。 + /// Default blocking threshold (ms): 3× the tick interval, leaving room for + /// normal timer jitter. + static const int defaultThresholdMs = 300; + + /// 阻塞记录上限(环形)/ Blocking record cap (ring) + static const int _maxEvents = 50; + + /// 每条阻塞附带的上下文日志条数 / Context logs attached per blocking event + static const int _nearbyLogCount = 3; + + /// 心跳定时器 / Heartbeat timer + Timer? _timer; + + /// 上一次心跳时刻 / Last heartbeat timestamp + DateTime? _lastTick; + + /// 阻塞记录(最新在前)/ Blocking records (newest first) + final ListQueue _events = ListQueue(); + + /// 当前阻塞判定阈值(毫秒)/ Current blocking threshold (ms) + int _thresholdMs = defaultThresholdMs; + + /// 是否在监听 / Whether watching + bool get isRunning => _timer != null; + + /// 当前阻塞判定阈值(毫秒)/ Current blocking threshold (ms) + int get thresholdMs => _thresholdMs; + + /// 阻塞记录只读视图(最新在前)/ Read-only blocking records (newest first) + UnmodifiableListView get events => + UnmodifiableListView(_events); + + /// 阻塞次数 / Blocking count + int get eventCount => _events.length; + + /// 最长阻塞时长(毫秒)/ Longest blocking duration (ms) + int get longestBlockingMs { + var max = 0; + for (final e in _events) { + if (e.durationMs > max) max = e.durationMs; + } + return max; + } + + /// 启动看门狗 / Start the watchdog + /// + /// [thresholdMs] 可覆盖判定阈值;已在运行时只更新阈值。 + /// [thresholdMs] overrides the threshold; when already running it just + /// updates the threshold. + void start({int? thresholdMs}) { + if (thresholdMs != null && thresholdMs > 0) { + _thresholdMs = thresholdMs; + } + if (_timer != null) { + notifyListeners(); + return; + } + _lastTick = null; + _timer = Timer.periodic(tickInterval, (_) => handleTick(DateTime.now())); + notifyListeners(); + } + + /// 停止看门狗 / Stop the watchdog + void stop() { + _timer?.cancel(); + _timer = null; + _lastTick = null; + notifyListeners(); + } + + /// 清空阻塞记录 / Clear blocking records + void clear() { + _events.clear(); + notifyListeners(); + } + + /// 处理一次心跳 / Handle one heartbeat + /// + /// 暴露给测试注入时间戳;定时器回调同样走这里。 + /// Exposed so tests can inject timestamps; the timer callback uses it too. + @visibleForTesting + void handleTick(DateTime now) { + final last = _lastTick; + _lastTick = now; + if (last == null) return; + + final gapMs = now.difference(last).inMilliseconds; + // 只关心"明显超过阈值"的间隔:debug 模式下定时器抖动常见,阈值已留 3 倍余量。 + // Only gaps clearly past the threshold matter: timer jitter is common in + // debug, and the threshold already leaves a 3× margin. + if (gapMs < _thresholdMs) return; + + _events.addFirst( + BlockingEvent( + startAt: last, + endAt: now, + durationMs: gapMs, + nearbyLogs: _collectNearbyLogs(last, now), + ), + ); + while (_events.length > _maxEvents) { + _events.removeLast(); + } + notifyListeners(); + } + + /// 收集阻塞窗口附近的日志摘要 / Collect log summaries around the stall window + /// + /// 主线程阻塞时拿不到阻塞点的堆栈(定时器回调有自己的栈),因此退而记录 + /// 前后 1 秒内最近的几条日志 —— 实践中这比"知道卡了 800ms"有用得多。 + /// The blocking stack is unavailable (the timer callback has its own stack), + /// so we record the nearest logs within ±1s instead — far more actionable than + /// just knowing "it stalled for 800ms". + List _collectNearbyLogs(DateTime start, DateTime end) { + try { + final from = start.subtract(const Duration(seconds: 1)); + final to = end.add(const Duration(seconds: 1)); + final found = []; + for (final LogEntry log in InspectorService.instance.logEntries) { + if (log.timestamp.isBefore(from)) break; + if (!log.timestamp.isAfter(to)) { + found.add( + '${log.timestampText} ${log.levelText} ${_oneLine(log.message)}', + ); + if (found.length >= _nearbyLogCount) break; + } + } + return found; + } catch (_) { + return const []; + } + } + + /// 取消息首行并压缩长度 / Take the first line and trim it down + static String _oneLine(String message) { + final line = message.split('\n').first.trim(); + return line.length <= 80 ? line : '${line.substring(0, 80)}…'; + } + + @override + void dispose() { + stop(); + super.dispose(); + } +} diff --git a/lib/src/services/timeline_service.dart b/lib/src/services/timeline_service.dart new file mode 100644 index 0000000..27807d4 --- /dev/null +++ b/lib/src/services/timeline_service.dart @@ -0,0 +1,173 @@ +import '../models/error_record.dart'; +import '../models/log_entry.dart'; +import '../models/network_request.dart'; +import '../models/route_entry.dart'; +import '../models/timeline_event.dart'; +import 'alert_service.dart'; +import 'error_service.dart'; +import 'inspector_service.dart'; + +/// 统一会话时间线服务 / Unified session timeline service +/// +/// 把已有的五类数据(网络 / 日志 / 异常 / 路由 / 告警)按时间归并成一条流, +/// 让面板能回答「这个请求前后还发生了什么」——单个标签页做不到这件事。 +/// Merges the five existing data categories (network / log / error / route / +/// alert) into one time-ordered stream so the panel can answer "what else +/// happened around this request?" — no single tab can. +/// +/// 无状态设计:不常驻监听任何 notifier,只在 UI 需要时归并一次(由 Timeline +/// Viewer 在数据变化通知后重算)。这样面板未打开时零开销,也避免服务成为 +/// 数据所有者的第二份真相(数据仍各自归属原服务)。 +/// Stateless by design: it listens to nothing and merges only when the UI asks +/// (the timeline viewer recomputes after a data-change notification). Zero cost +/// while the panel is closed, and no second source of truth — data still lives +/// in its own service. +class TimelineService { + TimelineService._(); + + /// 单例实例 / Singleton instance + static final TimelineService instance = TimelineService._(); + + /// 归并结果默认上限(避免长会话下列表过长)/ Default merge cap + static const int defaultLimit = 800; + + /// 上下文窗口默认时长 / Default context window + static const Duration defaultContextWindow = Duration(seconds: 10); + + /// 上下文查询时的扫描上限(需覆盖全部在库数据)/ Scan cap for context queries + static const int _contextScanLimit = 4000; + + /// 归并全部来源,按时间**倒序**(最新在前)。 + /// Merge every source, **newest first**. + /// + /// [kinds] 为空表示全部来源;[limit] 限制返回条数。 + /// A null [kinds] means every source; [limit] caps the result. + List build({ + Set? kinds, + int limit = defaultLimit, + }) { + final enabled = kinds ?? TimelineEventKind.values.toSet(); + final events = []; + final inspector = InspectorService.instance; + + if (enabled.contains(TimelineEventKind.network)) { + for (final r in inspector.networkRequests) { + events.add(_fromNetwork(r)); + } + } + if (enabled.contains(TimelineEventKind.log)) { + for (final l in inspector.logEntries) { + events.add(_fromLog(l)); + } + } + if (enabled.contains(TimelineEventKind.route)) { + for (final r in inspector.routeEntries) { + events.add(_fromRoute(r)); + } + } + if (enabled.contains(TimelineEventKind.error)) { + for (final e in ErrorService.instance.errors) { + events.add(_fromError(e)); + } + } + if (enabled.contains(TimelineEventKind.alert)) { + for (final a in AlertService.instance.events) { + events.add(_fromAlert(a)); + } + } + + events.sort((a, b) => b.timestamp.compareTo(a.timestamp)); + if (events.length > limit) return events.sublist(0, limit); + return events; + } + + /// 取 [anchor] 前后 [window] 内的事件,按时间**正序**(从早到晚,像讲故事)。 + /// Events within ±[window] of [anchor], **oldest first** (reads like a story). + /// + /// 这是时间线的核心价值:点任意一条事件,只保留它前后若干秒的全部来源事件, + /// 一眼看清因果(例如"路由跳转 → 两个请求 → 一条 error 日志 → 5xx 告警")。 + /// This is the point of the timeline: focus one event and keep only the + /// surrounding seconds across all sources, so causality is visible at a glance + /// (e.g. "route push → two requests → an error log → a 5xx alert"). + List contextAround( + TimelineEvent anchor, { + Duration window = defaultContextWindow, + Set? kinds, + }) { + final all = build(kinds: kinds, limit: _contextScanLimit); + final start = anchor.timestamp.subtract(window); + final end = anchor.timestamp.add(window); + final inWindow = + all + .where( + (e) => !e.timestamp.isBefore(start) && !e.timestamp.isAfter(end), + ) + .toList() + ..sort((a, b) => a.timestamp.compareTo(b.timestamp)); + return inWindow; + } + + /// 网络请求 → 时间线事件 / Network request → timeline event + static TimelineEvent _fromNetwork(NetworkRequest r) { + final status = r.statusCode?.toString() ?? '--'; + return TimelineEvent( + kind: TimelineEventKind.network, + timestamp: DateTime.fromMillisecondsSinceEpoch(r.requestTime), + title: '${r.method} $status · ${r.url}', + subtitle: r.durationText == '-' ? null : r.durationText, + detail: + '${r.method} ${r.url}\nstatus: $status\nduration: ${r.durationText}', + sourceId: r.id, + ); + } + + /// 日志 → 时间线事件 / Log entry → timeline event + static TimelineEvent _fromLog(LogEntry l) { + final firstLine = l.message.split('\n').first.trim(); + return TimelineEvent( + kind: TimelineEventKind.log, + timestamp: l.timestamp, + title: firstLine.isEmpty ? '(empty message)' : firstLine, + subtitle: l.tag != null ? '${l.levelText} · ${l.tag}' : l.levelText, + detail: l.message, + sourceId: l.id, + ); + } + + /// 异常聚合 → 时间线事件 / Aggregated error → timeline event + static TimelineEvent _fromError(ErrorRecord e) { + return TimelineEvent( + kind: TimelineEventKind.error, + timestamp: e.lastSeen, + title: e.count > 1 ? '${e.type} ×${e.count}' : e.type, + subtitle: e.message, + detail: e.sampleStack ?? e.message, + sourceId: e.id, + ); + } + + /// 路由 → 时间线事件 / Route entry → timeline event + static TimelineEvent _fromRoute(RouteEntry r) { + return TimelineEvent( + kind: TimelineEventKind.route, + timestamp: r.timestamp, + title: '${r.actionText} · ${r.routeName}', + subtitle: r.arguments == null || r.arguments!.isEmpty + ? null + : 'args: ${r.arguments!.keys.join(', ')}', + detail: r.arguments?.toString() ?? 'no arguments', + sourceId: r.id, + ); + } + + /// 告警 → 时间线事件 / Alert event → timeline event + static TimelineEvent _fromAlert(AlertEvent a) { + return TimelineEvent( + kind: TimelineEventKind.alert, + timestamp: a.time, + title: a.message, + subtitle: a.source, + detail: '${a.source}\n${a.message}', + ); + } +} diff --git a/lib/src/ui/blocking_watchdog_card.dart b/lib/src/ui/blocking_watchdog_card.dart new file mode 100644 index 0000000..c1ef1c7 --- /dev/null +++ b/lib/src/ui/blocking_watchdog_card.dart @@ -0,0 +1,320 @@ +import 'package:flutter/material.dart'; + +import '../models/blocking_event.dart'; +import '../services/blocking_watchdog_service.dart'; +import 'theme/inspector_theme.dart'; + +/// 主线程阻塞看门狗卡片(FPS 页内)/ Main-thread blocking watchdog card (in the FPS tab) +/// +/// FPS 只能发现"有帧产出"的卡顿;真正卡死时引擎不产帧,FPS 反而显示空闲。 +/// 这里直接按心跳间隔测量 UI isolate 的响应性,展示每次阻塞的时长、时间与 +/// 附近日志,补上 FPS 的盲区。 +/// FPS only catches jank that still produces frames; during a real stall no +/// frames are produced and FPS reads as idle. This card measures UI-isolate +/// responsiveness directly from heartbeat gaps and shows each stall's duration, +/// time and nearby logs — closing the FPS blind spot. +class BlockingWatchdogCard extends StatefulWidget { + const BlockingWatchdogCard({super.key}); + + @override + State createState() => _BlockingWatchdogCardState(); +} + +class _BlockingWatchdogCardState extends State { + @override + void initState() { + super.initState(); + BlockingWatchdogService.instance.addListener(_onChanged); + } + + @override + void dispose() { + BlockingWatchdogService.instance.removeListener(_onChanged); + super.dispose(); + } + + void _onChanged() { + if (mounted) setState(() {}); + } + + @override + Widget build(BuildContext context) { + final service = BlockingWatchdogService.instance; + final running = service.isRunning; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + color: InspectorColors.card, + borderRadius: BorderRadius.circular(InspectorDimensions.cardRadius), + border: Border.all( + color: running + ? InspectorColors.success.withValues(alpha: 0.3) + : InspectorColors.border, + width: 0.5, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + running + ? Icons.hourglass_bottom_rounded + : Icons.hourglass_empty_rounded, + size: 18, + color: running + ? InspectorColors.success + : InspectorColors.textHint, + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Main-thread Blocking', + style: TextStyle( + color: InspectorColors.textPrimary, + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 2), + Text( + running + ? 'Watching · ${BlockingWatchdogService.tickInterval.inMilliseconds}ms heartbeat / ' + '${service.thresholdMs}ms threshold' + : 'Stopped · stalls with no frames are invisible to FPS', + style: TextStyle( + color: running + ? InspectorColors.success + : InspectorColors.textHint, + fontSize: 10, + ), + ), + ], + ), + ), + Switch.adaptive( + value: running, + activeThumbColor: InspectorColors.success, + activeTrackColor: InspectorColors.success.withValues( + alpha: 0.3, + ), + inactiveThumbColor: InspectorColors.textHint, + inactiveTrackColor: InspectorColors.border, + onChanged: (value) { + if (value) { + service.start(); + } else { + service.stop(); + } + }, + ), + ], + ), + if (running) ...[ + const SizedBox(height: 10), + Row( + children: [ + _buildStat( + 'Stalls', + '${service.eventCount}', + service.eventCount > 0 + ? InspectorColors.error + : InspectorColors.success, + ), + const SizedBox(width: 18), + _buildStat( + 'Longest', + service.longestBlockingMs > 0 + ? '${service.longestBlockingMs}ms' + : '--', + service.longestBlockingMs > 0 + ? InspectorColors.error + : InspectorColors.success, + ), + const Spacer(), + GestureDetector( + onTap: () => BlockingWatchdogService.instance.clear(), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: InspectorColors.warning.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: InspectorColors.warning.withValues(alpha: 0.3), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.refresh_rounded, + size: 13, + color: InspectorColors.warning, + ), + const SizedBox(width: 3), + Text( + 'Reset', + style: TextStyle( + color: InspectorColors.warning, + fontSize: 11, + ), + ), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 10), + if (service.events.isEmpty) + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 14), + decoration: BoxDecoration( + color: InspectorColors.surface, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: InspectorColors.border, width: 0.5), + ), + child: Column( + children: [ + Icon( + Icons.check_circle_rounded, + size: 28, + color: InspectorColors.success, + ), + const SizedBox(height: 6), + Text( + 'No blocking detected', + style: TextStyle( + color: InspectorColors.textHint, + fontSize: 11, + ), + ), + ], + ), + ) + else + SizedBox( + height: 120, + child: ListView.builder( + itemCount: service.events.length, + itemBuilder: (context, index) => + _buildRow(service.events[index]), + ), + ), + ], + ], + ), + ); + } + + /// 构建统计项 / Build a stat item + Widget _buildStat(String label, String value, Color color) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle(color: InspectorColors.textHint, fontSize: 11), + ), + const SizedBox(height: 3), + Text( + value, + style: TextStyle( + color: color, + fontSize: 13, + fontWeight: FontWeight.w700, + fontFamily: 'monospace', + ), + ), + ], + ); + } + + /// 构建单条阻塞记录 / Build a single blocking record row + Widget _buildRow(BlockingEvent event) { + return Container( + margin: const EdgeInsets.only(bottom: 4), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + decoration: BoxDecoration( + color: InspectorColors.surface, + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: InspectorColors.error.withValues(alpha: 0.3), + width: 0.5, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 6, + height: 6, + decoration: BoxDecoration( + color: InspectorColors.error, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 8), + Text( + event.durationText, + style: TextStyle( + color: InspectorColors.error, + fontSize: 12, + fontWeight: FontWeight.w600, + fontFamily: 'monospace', + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + _formatTime(event.startAt), + style: TextStyle( + color: InspectorColors.textHint, + fontSize: 10, + fontFamily: 'monospace', + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + if (event.nearbyLogs.isNotEmpty) ...[ + const SizedBox(height: 4), + for (final line in event.nearbyLogs) + Padding( + padding: const EdgeInsets.only(left: 14, top: 2), + child: Text( + line, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: InspectorColors.textHint, + fontSize: 9, + fontFamily: 'monospace', + ), + ), + ), + ], + ], + ), + ); + } + + /// 格式化时间(HH:mm:ss.SSS)/ Format time (HH:mm:ss.SSS) + String _formatTime(DateTime time) { + String two(int v) => v.toString().padLeft(2, '0'); + return '${two(time.hour)}:${two(time.minute)}:${two(time.second)}' + '.${time.millisecond.toString().padLeft(3, '0')}'; + } +} diff --git a/lib/src/ui/fps_viewer.dart b/lib/src/ui/fps_viewer.dart index 033671f..2ebc8c0 100644 --- a/lib/src/ui/fps_viewer.dart +++ b/lib/src/ui/fps_viewer.dart @@ -3,11 +3,15 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import '../services/fps_service.dart'; +import 'blocking_watchdog_card.dart'; import 'theme/inspector_theme.dart'; /// FPS 查看器(Inspector 面板内详情页)/ FPS viewer (in-panel detail page) /// /// 包含以下内容 / Contains: +/// - FPS 总开关卡片 / FPS master switch card +/// - 主线程阻塞看门狗卡片(FPS 看不见的"零帧卡死")/ Main-thread blocking +/// watchdog card (the "no frames at all" stalls FPS cannot see) /// - FPS 概览卡片(当前 FPS、掉帧率、总帧数)/ FPS overview card (current FPS, jank rate, total frames) /// - FPS 趋势折线图 / FPS trend line chart /// - 掉帧记录列表 / Janky frame record list @@ -49,6 +53,11 @@ class _FpsViewerState extends State { // 总开关卡片 / Master switch card _buildMainSwitchCard(), const SizedBox(height: 12), + // 主线程阻塞看门狗:独立于 FPS 开关,因为它测的是"完全不产帧"的场景。 + // Main-thread blocking watchdog: independent of the FPS switch, + // since it measures the case where no frames are produced at all. + const BlockingWatchdogCard(), + const SizedBox(height: 12), if (service.isRunning) ...[ _buildOverviewCard(), const SizedBox(height: 12), diff --git a/lib/src/ui/inspector_panel.dart b/lib/src/ui/inspector_panel.dart index 7c67954..319718f 100644 --- a/lib/src/ui/inspector_panel.dart +++ b/lib/src/ui/inspector_panel.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'theme/inspector_theme.dart'; import '../services/fps_service.dart'; import '../services/memory_inspector_service.dart'; +import 'timeline_viewer.dart'; import 'network_viewer.dart'; import 'log_viewer.dart'; import 'error_viewer.dart'; @@ -27,9 +28,9 @@ import '../utils/formatters.dart'; enum _PersistedDataAction { export, clearDisk, clearDiskAndLists } /// 检查器面板 / Inspector panel -/// 按诊断工作流排序的网络、日志、异常、数据库、内存、FPS、路由、Widget、告警九个查看器 -/// Nine viewers ordered by diagnostic workflow: network, logs, errors, database, -/// memory, FPS, routes, widgets, alerts +/// 按诊断工作流排序的时间线、网络、日志、异常、数据库、内存、FPS、路由、Widget、告警十个查看器 +/// Ten viewers ordered by diagnostic workflow: timeline, network, logs, errors, +/// database, memory, FPS, routes, widgets, alerts class InspectorPanel extends StatefulWidget { /// 关闭面板回调 / Close panel callback final VoidCallback onClose; @@ -87,6 +88,10 @@ class _InspectorPanelState extends State label: 'Routes', child: RouteViewer(key: ValueKey('routes')), ), + InspectorErrorBoundary( + label: 'Timeline', + child: TimelineViewer(key: ValueKey('timeline')), + ), InspectorErrorBoundary( label: 'Widgets', child: WidgetTreeInspector(key: ValueKey('widgets')), @@ -106,6 +111,7 @@ class _InspectorPanelState extends State 'Memory', 'FPS', 'Routes', + 'Timeline', 'Widgets', 'Alerts', ]; @@ -119,6 +125,7 @@ class _InspectorPanelState extends State Icons.memory_rounded, Icons.speed_rounded, Icons.route_rounded, + Icons.timeline_rounded, Icons.visibility_rounded, Icons.notifications_active_rounded, ]; diff --git a/lib/src/ui/timeline_viewer.dart b/lib/src/ui/timeline_viewer.dart new file mode 100644 index 0000000..7941cb1 --- /dev/null +++ b/lib/src/ui/timeline_viewer.dart @@ -0,0 +1,533 @@ +import 'package:flutter/material.dart'; + +import '../models/timeline_event.dart'; +import '../services/alert_service.dart'; +import '../services/error_service.dart'; +import '../services/inspector_service.dart'; +import '../services/timeline_service.dart'; +import 'theme/inspector_theme.dart'; + +/// 统一会话时间线查看器 / Unified session timeline viewer +/// +/// 把网络 / 日志 / 异常 / 路由 / 告警按时间归并成一条流,并支持"聚焦某条事件 +/// 前后 N 秒"——这是单个标签页无法回答的"前后上下文"问题。 +/// Merges network / log / error / route / alert into one time-ordered stream and +/// lets you focus the ±N seconds around any event — the "what happened around +/// it?" question no single tab can answer. +/// +/// 只在本页挂载时监听各 notifier(面板用 IndexedStack 只挂载当前页), +/// 未打开时零开销;数据仍归属各自服务,这里只做归并展示。 +/// Listens to the notifiers only while mounted (the panel mounts just the +/// active page), so a closed tab costs nothing. Data still lives in its own +/// service; this view only merges it for display. +class TimelineViewer extends StatefulWidget { + const TimelineViewer({super.key}); + + @override + State createState() => _TimelineViewerState(); +} + +class _TimelineViewerState extends State { + /// 被隐藏的来源(默认全部显示)/ Hidden sources (all shown by default) + final Set _hidden = {}; + + /// 聚焦的锚点事件(为空表示普通倒序列表)/ Focused anchor (null = normal list) + TimelineEvent? _anchor; + + /// 上下文窗口时长 / Context window length + Duration _window = TimelineService.defaultContextWindow; + + /// 当前展开的事件 key / Currently expanded event key + String? _expandedKey; + + /// 缓存的归并结果 / Cached merge result + List _events = const []; + + /// 数据已变化,下次 build 需重新归并 / Data changed; re-merge on next build + bool _dirty = true; + + @override + void initState() { + super.initState(); + InspectorService.instance.networkNotifier.addListener(_markDirty); + InspectorService.instance.logNotifier.addListener(_markDirty); + InspectorService.instance.routeNotifier.addListener(_markDirty); + ErrorService.instance.addListener(_markDirty); + // AlertService 不是 ChangeNotifier,未读数变化即为新告警入队的信号。 + // AlertService is not a ChangeNotifier; the unread count changes whenever a + // new alert is queued. + AlertService.instance.unreadCount.addListener(_markDirty); + } + + @override + void dispose() { + InspectorService.instance.networkNotifier.removeListener(_markDirty); + InspectorService.instance.logNotifier.removeListener(_markDirty); + InspectorService.instance.routeNotifier.removeListener(_markDirty); + ErrorService.instance.removeListener(_markDirty); + AlertService.instance.unreadCount.removeListener(_markDirty); + super.dispose(); + } + + void _markDirty() { + if (!mounted) return; + setState(() => _dirty = true); + } + + /// 重新归并(仅在脏数据或筛选变化时)/ Re-merge (only when dirty or filters changed) + List _computeEvents() { + if (!_dirty && _events.isNotEmpty) return _events; + final kinds = TimelineEventKind.values + .where((k) => !_hidden.contains(k)) + .toSet(); + _events = _anchor == null + ? TimelineService.instance.build(kinds: kinds) + : TimelineService.instance.contextAround( + _anchor!, + window: _window, + kinds: kinds, + ); + _dirty = false; + return _events; + } + + @override + Widget build(BuildContext context) { + final events = _computeEvents(); + + return Container( + color: InspectorColors.surface, + child: Column( + children: [ + _buildToolbar(events.length), + if (_anchor != null) _buildContextBar(), + Expanded(child: events.isEmpty ? _buildEmpty() : _buildList(events)), + ], + ), + ); + } + + /// 构建工具栏(来源筛选 + 计数 + 复位)/ Build toolbar (source filter + count + reset) + Widget _buildToolbar(int count) { + return Container( + padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), + decoration: BoxDecoration( + color: InspectorColors.surface, + border: Border( + bottom: BorderSide(color: InspectorColors.border, width: 1), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.timeline_rounded, + size: 14, + color: InspectorColors.textSecondary, + ), + const SizedBox(width: 6), + Expanded( + child: Text( + _anchor == null ? 'Session timeline' : 'Focused context', + style: const TextStyle( + color: InspectorColors.textPrimary, + fontSize: 13, + fontWeight: FontWeight.w600, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Text( + '$count', + style: TextStyle( + color: InspectorColors.textHint, + fontSize: 11, + fontFamily: 'monospace', + ), + ), + const SizedBox(width: 8), + _buildActionChip( + label: 'Reset', + color: InspectorColors.warning, + onTap: _resetAll, + ), + ], + ), + const SizedBox(height: 8), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + for (final kind in TimelineEventKind.values) + _buildKindChip(kind), + ], + ), + ), + ], + ), + ); + } + + /// 构建上下文模式提示条 / Build the context-mode bar + Widget _buildContextBar() { + final anchor = _anchor!; + return Container( + padding: const EdgeInsets.fromLTRB(12, 6, 4, 6), + decoration: BoxDecoration( + color: InspectorColors.primary.withValues(alpha: 0.10), + border: Border( + bottom: BorderSide(color: InspectorColors.border, width: 1), + ), + ), + child: Row( + children: [ + Icon( + Icons.center_focus_weak_rounded, + size: 14, + color: InspectorColors.primary, + ), + const SizedBox(width: 6), + Expanded( + child: Text( + '±${_window.inSeconds}s around ${anchor.timeText}', + style: const TextStyle( + color: InspectorColors.textSecondary, + fontSize: 10, + fontFamily: 'monospace', + ), + overflow: TextOverflow.ellipsis, + ), + ), + for (final seconds in const [5, 10, 30]) + Padding( + padding: const EdgeInsets.only(right: 4), + child: _buildActionChip( + label: '${seconds}s', + color: _window.inSeconds == seconds + ? InspectorColors.primary + : InspectorColors.textHint, + onTap: () => setState(() { + _window = Duration(seconds: seconds); + _dirty = true; + }), + ), + ), + IconButton( + onPressed: () => setState(() { + _anchor = null; + _dirty = true; + }), + icon: Icon( + Icons.close_rounded, + size: 16, + color: InspectorColors.textSecondary, + ), + tooltip: 'Exit context', + constraints: const BoxConstraints(), + padding: const EdgeInsets.all(6), + ), + ], + ), + ); + } + + /// 构建来源筛选 chip / Build a source filter chip + Widget _buildKindChip(TimelineEventKind kind) { + final enabled = !_hidden.contains(kind); + final color = _kindColor(kind); + return Padding( + padding: const EdgeInsets.only(right: 6), + child: GestureDetector( + onTap: () => setState(() { + if (enabled) { + _hidden.add(kind); + } else { + _hidden.remove(kind); + } + _dirty = true; + }), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: enabled ? color.withValues(alpha: 0.14) : Colors.transparent, + borderRadius: BorderRadius.circular(InspectorDimensions.chipRadius), + border: Border.all( + color: enabled + ? color.withValues(alpha: 0.5) + : InspectorColors.border, + width: 0.5, + ), + ), + child: Text( + kind.label, + style: TextStyle( + color: enabled ? color : InspectorColors.textHint, + fontSize: 10, + fontWeight: FontWeight.w600, + fontFamily: 'monospace', + ), + ), + ), + ), + ); + } + + /// 构建通用动作 chip / Build a generic action chip + Widget _buildActionChip({ + required String label, + required Color color, + required VoidCallback onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(InspectorDimensions.chipRadius), + border: Border.all(color: color.withValues(alpha: 0.3), width: 0.5), + ), + child: Text( + label, + style: TextStyle( + color: color, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + ), + ), + ); + } + + /// 构建事件列表 / Build the event list + Widget _buildList(List events) { + return ListView.builder( + padding: const EdgeInsets.only(bottom: 12), + itemCount: events.length, + itemBuilder: (context, index) => _buildRow(events[index]), + ); + } + + /// 构建单条事件行 / Build a single event row + Widget _buildRow(TimelineEvent event) { + final color = _kindColor(event.kind); + final expanded = _expandedKey == event.key; + final isAnchor = _anchor?.key == event.key; + + return Container( + margin: const EdgeInsets.fromLTRB(12, 6, 12, 0), + decoration: BoxDecoration( + color: InspectorColors.card, + borderRadius: BorderRadius.circular(InspectorDimensions.cardRadius), + // 圆角要求边框四边同色;左侧 kind 色条改为卡片内竖条实现。 + // Uniform border color is required with borderRadius; the left kind + // accent is drawn as an inner bar below instead. + border: Border.all( + color: isAnchor + ? InspectorColors.primary.withValues(alpha: 0.6) + : InspectorColors.border, + width: isAnchor ? 1.0 : 0.5, + ), + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(InspectorDimensions.cardRadius), + onTap: () => setState(() { + _expandedKey = expanded ? null : event.key; + }), + child: Padding( + padding: const EdgeInsets.fromLTRB(10, 8, 10, 8), + // Stack 而非 Row:Row + CrossAxisAlignment.stretch + Expanded 在 ListView + // 的无限高约束下会算出无限高度并崩溃。用 Stack 让卡片高度由内容决定, + // 竖条以 Positioned(top/bottom:0) 拉伸到该高度。 + // Stack instead of Row: a Row with stretch + Expanded resolves to + // infinite height under ListView's unbounded vertical constraint and + // crashes. With Stack the card height is driven by content, and the + // bar stretches via Positioned(top/bottom:0). + child: Stack( + children: [ + Positioned( + left: 0, + top: 0, + bottom: 0, + child: Container( + width: 3, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(1.5), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 11), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + event.timeText, + style: const TextStyle( + color: InspectorColors.textHint, + fontSize: 10, + fontFamily: 'monospace', + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 5, + vertical: 1, + ), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.14), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + event.kind.label, + style: TextStyle( + color: color, + fontSize: 9, + fontWeight: FontWeight.w700, + fontFamily: 'monospace', + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + event.title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: InspectorColors.textPrimary, + fontSize: 12, + ), + ), + ), + ], + ), + if (event.subtitle != null) ...[ + const SizedBox(height: 4), + Text( + event.subtitle!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: InspectorColors.textSecondary, + fontSize: 10, + ), + ), + ], + if (expanded) ...[ + const SizedBox(height: 8), + Container( + width: double.infinity, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: InspectorColors.surface, + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: InspectorColors.border, + width: 0.5, + ), + ), + child: Text( + event.detail, + maxLines: 10, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: InspectorColors.textSecondary, + fontSize: 10, + height: 1.4, + fontFamily: 'monospace', + ), + ), + ), + const SizedBox(height: 6), + Align( + alignment: Alignment.centerRight, + child: _buildActionChip( + label: 'Focus ±${_window.inSeconds}s', + color: InspectorColors.primary, + onTap: () => setState(() { + _anchor = event; + _dirty = true; + }), + ), + ), + ], + ], + ), + ), + ], + ), + ), + ), + ), + ); + } + + /// 构建空态 / Build the empty state + Widget _buildEmpty() { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.timeline_rounded, + size: 40, + color: InspectorColors.textHint, + ), + const SizedBox(height: 8), + Text( + _hidden.length == TimelineEventKind.values.length + ? 'All sources hidden' + : 'No events captured yet', + style: TextStyle( + color: InspectorColors.textSecondary, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 4), + Text( + 'Network · logs · errors · routes · alerts\nappear here in one time order.', + textAlign: TextAlign.center, + style: TextStyle(color: InspectorColors.textHint, fontSize: 10), + ), + ], + ), + ); + } + + /// 复位全部筛选与聚焦 / Reset all filters and focus + void _resetAll() { + setState(() { + _hidden.clear(); + _anchor = null; + _expandedKey = null; + _window = TimelineService.defaultContextWindow; + _dirty = true; + }); + } + + /// 各类来源的标识色 / Identifying color per source kind + Color _kindColor(TimelineEventKind kind) { + switch (kind) { + case TimelineEventKind.network: + return InspectorColors.info; + case TimelineEventKind.log: + return InspectorColors.success; + case TimelineEventKind.error: + return InspectorColors.error; + case TimelineEventKind.route: + return InspectorColors.methodPut; + case TimelineEventKind.alert: + return InspectorColors.warning; + } + } +} diff --git a/lib/src/utils/inspector_version.dart b/lib/src/utils/inspector_version.dart index 46f03a5..bd73100 100644 --- a/lib/src/utils/inspector_version.dart +++ b/lib/src/utils/inspector_version.dart @@ -12,5 +12,5 @@ class InspectorVersion { /// 当前版本号,必须与 `pubspec.yaml` 的 `version` 字段一致 /// Current version; must match the `version` field in `pubspec.yaml` - static const String value = '1.11.0'; + static const String value = '1.12.0'; } diff --git a/lib/zero_inspector_kit.dart b/lib/zero_inspector_kit.dart index b1d74ab..235700f 100644 --- a/lib/zero_inspector_kit.dart +++ b/lib/zero_inspector_kit.dart @@ -50,6 +50,15 @@ export 'src/ui/widget_tree_viewer.dart'; export 'src/ui/alerts_viewer.dart'; export 'src/ui/network_timeline.dart'; +// 统一会话时间线与主线程阻塞看门狗(v1.12.0 新增)。 +// Unified session timeline & main-thread blocking watchdog (added in v1.12.0). +export 'src/models/timeline_event.dart'; +export 'src/services/timeline_service.dart'; +export 'src/ui/timeline_viewer.dart'; +export 'src/models/blocking_event.dart'; +export 'src/services/blocking_watchdog_service.dart'; +export 'src/ui/blocking_watchdog_card.dart'; + export 'zero_inspector_kit_platform_interface.dart'; import 'dart:async'; @@ -75,6 +84,7 @@ import 'src/services/memory_inspector_service.dart'; import 'src/services/shared_prefs_provider.dart'; import 'src/services/hive_provider.dart'; import 'src/services/widget_tree_service.dart'; +import 'src/services/blocking_watchdog_service.dart'; /// ZeroInspectorKit 插件入口类 / ZeroInspectorKit plugin entry class /// 提供一键初始化和应用包装功能,实现零侵入集成 / Provides one-click initialization and app wrapping for zero-invasion integration @@ -112,10 +122,16 @@ import 'src/services/widget_tree_service.dart'; /// - 自动显示悬浮检查器按钮(release 模式自动隐藏)/ Auto-show floating inspector button (auto-hidden in release mode) /// /// 注意 / Note: -/// - 不要在 runAppWithInspector 之前调用 WidgetsFlutterBinding.ensureInitialized() -/// 或 await 会触发 platform channel 的插件(如 SharedPreferences),否则 binding 会在 -/// 外层 Zone 初始化,触发 "Zone mismatch" 断言。/ Do NOT call ensureInitialized() or await -/// platform-channel plugins before runAppWithInspector, or Flutter throws "Zone mismatch". +/// - binding 必须在检查器 Zone 内首次初始化,print() 才能被完整捕获:不要在 +/// runAppWithInspector 之前调用 WidgetsFlutterBinding.ensureInitialized() 或 +/// await 会触发 platform channel 的插件(如 SharedPreferences)。/ The binding must +/// first initialize inside the inspector Zone for print() capture to work: do +/// NOT call ensureInitialized() or await platform-channel plugins before +/// runAppWithInspector. +/// - 若已经这么做了也不会崩溃:本方法会探测到 binding 已初始化并自动降级为直接 +/// runApp(debugPrint 日志仍捕获,仅 print() 直出不捕获)。/ Doing so no longer +/// crashes: the method detects the pre-initialized binding and degrades to a +/// plain runApp (debugPrint logs are still captured, raw print() is not). /// - 各能力可通过 init() 的命名参数按需开关 / Each capability can be toggled via init()'s named params. class ZeroInspectorKit { static bool _initialized = false; @@ -252,6 +268,7 @@ class ZeroInspectorKit { InspectorHttpInterceptor.instance.stop(); ErrorService.instance.uninstall(); FpsService.instance.stop(); + BlockingWatchdogService.instance.stop(); MemoryInspectorService.instance.stopMonitoring(); InspectorService.instance.disposeService(); await PersistenceService.instance.dispose(); @@ -334,16 +351,43 @@ class ZeroInspectorKit { enableNetworkTimeline: enableNetworkTimeline, ); + // binding 已在调用方(外层 Zone)初始化时不能再套 Zone,否则 Flutter 会抛 + // "Zone mismatch" 直接崩溃 —— 见 [_isFlutterBindingInitialized]。 + // 此时降级为直接 runApp:日志捕获仍由 debugPrint 覆写生效,仅 print() 直出 + // 的捕获失效,其余能力(网络 / 数据库 / 路由 / 面板)完全不变。 + // When the binding is already initialized by the caller (outer Zone), we + // must not wrap runApp in a Zone — Flutter would throw "Zone mismatch" and + // crash; see [_isFlutterBindingInitialized]. We degrade to a plain runApp: + // log capture still works through the debugPrint override, only raw print() + // capture is lost — network / database / route / panel are unaffected. + if (_isFlutterBindingInitialized()) { + assert(() { + debugPrint( + '[ZeroInspectorKit] The Flutter binding was already initialized ' + 'before runAppWithInspector, so the inspector skipped its Zone ' + 'wrapper to avoid a "Zone mismatch" crash. debugPrint logs are still ' + 'captured; raw print() calls are not. To capture print() too, drop ' + 'ensureInitialized() / plugin awaits before runAppWithInspector, or ' + 'use ZeroInspectorKit.init() + ZeroInspectorKit.wrapApp().', + ); + return true; + }()); + runApp(wrapApp(app, enable: enable)); + return; + } + // 用 zone 包裹 runApp 以捕获 print() 日志(InspectorLogInterceptor 的 // debugPrint 覆写 + ZoneSpecification.print 共同生效)。 // 注意:binding 必须在该 zone 内首次初始化 —— 调用方不要在 runAppWithInspector // 之前调用 WidgetsFlutterBinding.ensureInitialized() 或 await 任何会触发 // platform channel 的插件(如 SharedPreferences),否则 binding 会在外层 // zone 初始化,与 runApp 所在 zone 不一致,触发 "Zone mismatch" 断言。 + // 命中该场景时不再崩溃,而是走上面的降级分支。 // The binding must be initialized in this same zone — callers must NOT // call ensureInitialized() or await plugin/platform-channel calls before // runAppWithInspector, or the binding gets initialized in the outer zone - // and Flutter throws "Zone mismatch". + // and Flutter throws "Zone mismatch". That scenario now degrades gracefully + // via the branch above instead of crashing. runZonedGuarded( () => runApp(wrapApp(app, enable: enable)), (error, stackTrace) { @@ -364,6 +408,36 @@ class ZeroInspectorKit { ), ); } + + /// Flutter binding 是否已被初始化(在调用方、即本类之外的 Zone 中)。 + /// Whether the Flutter binding has already been initialized (by the caller, + /// outside this class's Zone). + /// + /// binding 只记录一次它初始化时所在的 Zone。若调用方在 runAppWithInspector + /// 之前调用过 `WidgetsFlutterBinding.ensureInitialized()`,或 await 了 + /// SharedPreferences 这类会触发 platform channel 的插件,binding 就已在外层 + /// Zone 初始化;此时再用 runZonedGuarded 包裹 runApp,Flutter 会断言两个 Zone + /// 不一致并抛 "Zone mismatch" 崩溃 —— 这在 debug 下是一个必崩且难以自查的坑。 + /// The binding records the Zone it was initialized in, once. If the caller + /// called `WidgetsFlutterBinding.ensureInitialized()` or awaited a + /// platform-channel plugin (e.g. SharedPreferences) before + /// runAppWithInspector, the binding lives in the outer Zone, and wrapping + /// runApp in a new Zone makes Flutter assert a Zone mismatch and crash — a + /// guaranteed, hard-to-diagnose crash in debug builds. + /// + /// 未初始化时读取 `WidgetsBinding.instance` 会抛异常(debug 为断言、 + /// release 为空检查),因此用 try/catch 探测,两种模式行为一致。 + /// Reading `WidgetsBinding.instance` throws when uninitialized (assertion in + /// debug, null check in release), so we probe with try/catch — behavior is + /// identical in both modes. + static bool _isFlutterBindingInitialized() { + try { + final Object binding = WidgetsBinding.instance; + return binding is WidgetsBinding; + } catch (_) { + return false; + } + } } /// 检查器应用包装器 / Inspector app wrapper diff --git a/pubspec.yaml b/pubspec.yaml index 774ce4d..7741ff5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: zero_inspector_kit description: One-line in-app developer console for Flutter — HTTP/WebSocket/gRPC inspection, logs, database, memory leaks, FPS jank, routes, alerts, bug reports. Auto-disabled in release. -version: 1.11.1 +version: 1.12.0 topics: - logging - debug diff --git a/test/blocking_watchdog_test.dart b/test/blocking_watchdog_test.dart new file mode 100644 index 0000000..0b86b72 --- /dev/null +++ b/test/blocking_watchdog_test.dart @@ -0,0 +1,121 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zero_inspector_kit/zero_inspector_kit.dart'; + +void main() { + late BlockingWatchdogService service; + + setUp(() { + service = BlockingWatchdogService.instance..stop(); + // 阈值是单例状态,先复位为默认值,避免上一个用例的自定义阈值泄漏过来。 + // The threshold is singleton state; reset it so a custom value from a + // previous test cannot leak into the next one. + service.start(thresholdMs: BlockingWatchdogService.defaultThresholdMs); + service.stop(); + service.clear(); + }); + + tearDown(() { + service.stop(); + service.clear(); + }); + + test('is off by default', () { + expect(service.isRunning, isFalse); + expect(service.events, isEmpty); + expect(service.thresholdMs, BlockingWatchdogService.defaultThresholdMs); + }); + + test('the first heartbeat never counts as blocking', () { + service.handleTick(DateTime.fromMillisecondsSinceEpoch(1000)); + expect(service.events, isEmpty); + }); + + test('a gap below the threshold is ignored', () { + final t0 = DateTime.fromMillisecondsSinceEpoch(1000); + service.handleTick(t0); + service.handleTick(t0.add(const Duration(milliseconds: 120))); + expect(service.events, isEmpty); + }); + + test('a gap at or above the threshold is recorded', () { + final t0 = DateTime.fromMillisecondsSinceEpoch(1000); + final t1 = t0.add(const Duration(milliseconds: 850)); + service.handleTick(t0); + service.handleTick(t1); + + expect(service.eventCount, 1); + final event = service.events.single; + expect(event.durationMs, 850); + expect(event.startAt, t0); + expect(event.endAt, t1); + expect(event.durationText, '850ms'); + }); + + test('durations above one second are formatted in seconds', () { + final t0 = DateTime.fromMillisecondsSinceEpoch(1000); + service.handleTick(t0); + service.handleTick(t0.add(const Duration(milliseconds: 1800))); + expect(service.events.single.durationText, '1.80s'); + }); + + test('nearby logs are attached to the blocking record', () { + final t0 = DateTime.fromMillisecondsSinceEpoch(1000); + InspectorService.instance.clearAll(); + InspectorService.instance.addLogEntry( + LogEntry( + id: 'near-1', + level: LogLevel.info, + message: 'parsing a huge payload', + timestamp: t0.add(const Duration(milliseconds: 100)), + tag: 'watchdog-test', + ), + ); + + service.handleTick(t0); + service.handleTick(t0.add(const Duration(milliseconds: 600))); + + expect(service.events.single.nearbyLogs, isNotEmpty); + expect(service.events.single.nearbyLogs.first, contains('huge payload')); + InspectorService.instance.clearAll(); + }); + + test('the record ring caps at 50 entries', () { + var now = DateTime.fromMillisecondsSinceEpoch(1000); + service.handleTick(now); + for (var i = 0; i < 80; i++) { + now = now.add(const Duration(milliseconds: 500)); + service.handleTick(now); + } + expect(service.eventCount, 50); + expect(service.longestBlockingMs, 500); + }); + + test('start/stop toggle the heartbeat and clear() empties records', () { + service.start(); + expect(service.isRunning, isTrue); + // 重复 start 不应重置已有记录 / Re-starting must not drop existing records + final t0 = DateTime.fromMillisecondsSinceEpoch(1000); + service.handleTick(t0); + service.handleTick(t0.add(const Duration(milliseconds: 400))); + expect(service.eventCount, 1); + + service.start(); + expect(service.eventCount, 1); + + service.clear(); + expect(service.eventCount, 0); + + service.stop(); + expect(service.isRunning, isFalse); + }); + + test('start() accepts a custom threshold', () { + service.start(thresholdMs: 1000); + expect(service.thresholdMs, 1000); + + final t0 = DateTime.fromMillisecondsSinceEpoch(1000); + service.handleTick(t0); + service.handleTick(t0.add(const Duration(milliseconds: 500))); + expect(service.events, isEmpty, reason: '500ms is below the 1000ms bar'); + }); +} diff --git a/test/timeline_service_test.dart b/test/timeline_service_test.dart new file mode 100644 index 0000000..24a9b3a --- /dev/null +++ b/test/timeline_service_test.dart @@ -0,0 +1,139 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zero_inspector_kit/zero_inspector_kit.dart'; + +void main() { + final base = DateTime.now().millisecondsSinceEpoch; + + NetworkRequest netAt(int offsetMs) => NetworkRequest( + id: 'net-$offsetMs', + method: 'GET', + url: 'https://example.com/api/$offsetMs', + requestTime: base + offsetMs, + ); + + LogEntry logAt(int offsetMs) => LogEntry( + id: 'log-$offsetMs', + level: LogLevel.info, + message: 'message $offsetMs', + timestamp: DateTime.fromMillisecondsSinceEpoch(base + offsetMs), + tag: 'timeline-test', + ); + + RouteEntry routeAt(int offsetMs) => RouteEntry( + id: 'route-$offsetMs', + routeName: '/page-$offsetMs', + timestamp: DateTime.fromMillisecondsSinceEpoch(base + offsetMs), + action: RouteAction.push, + ); + + setUp(() { + InspectorService.instance.clearAll(); + ErrorService.instance.clear(); + AlertService.instance.clearAll(); + }); + + test('build() merges every source newest-first', () { + InspectorService.instance + ..addNetworkRequest(netAt(0)) + ..addRouteEntry(routeAt(500)) + ..addLogEntry(logAt(1000)); + + final events = TimelineService.instance.build(); + + expect(events.length, 3); + expect(events.map((e) => e.kind).toList(), [ + TimelineEventKind.log, + TimelineEventKind.route, + TimelineEventKind.network, + ]); + // 标题带上方法与路由名,便于在列表里一眼识别 / Titles carry method / route name + expect(events[2].title, contains('GET')); + expect(events[1].title, contains('/page-500')); + }); + + test('build() honours the kind filter', () { + InspectorService.instance + ..addNetworkRequest(netAt(0)) + ..addLogEntry(logAt(1000)); + + final onlyNetwork = TimelineService.instance.build( + kinds: {TimelineEventKind.network}, + ); + expect(onlyNetwork.length, 1); + expect(onlyNetwork.single.kind, TimelineEventKind.network); + }); + + test('build() includes errors and alerts', () { + ErrorService.instance.report(StateError('boom'), StackTrace.current); + AlertService.instance.restore([ + AlertEvent(source: 'memory', message: '512 MB used'), + ]); + + final events = TimelineService.instance.build(); + + expect( + events.map((e) => e.kind), + containsAll([TimelineEventKind.error, TimelineEventKind.alert]), + ); + final alert = events.firstWhere((e) => e.kind == TimelineEventKind.alert); + expect(alert.title, '512 MB used'); + expect(alert.subtitle, 'memory'); + }); + + test('build() returns an empty list when nothing was captured', () { + expect(TimelineService.instance.build(), isEmpty); + }); + + test('contextAround() keeps only the window, oldest-first', () { + // 网络在 0ms、路由在 500ms、日志在 1000ms;聚焦路由 ±400ms → [100, 900], + // 网络与日志都被裁掉,只留 route。验证窗口裁剪真的生效。 + // Network at 0ms, route at 500ms, log at 1000ms; focusing the route with a + // ±400ms window → [100, 900], so both neighbors are clipped and only the + // route remains. Verifies the window clipping actually works. + InspectorService.instance + ..addNetworkRequest(netAt(0)) + ..addRouteEntry(routeAt(500)) + ..addLogEntry(logAt(1000)); + + final anchor = TimelineService.instance + .build(kinds: {TimelineEventKind.route}) + .single; + final context = TimelineService.instance.contextAround( + anchor, + window: const Duration(milliseconds: 400), + ); + + expect(context.map((e) => e.kind).toList(), [TimelineEventKind.route]); + }); + + test('contextAround() widens to cover everything at ±10s', () { + InspectorService.instance + ..addNetworkRequest(netAt(0)) + ..addRouteEntry(routeAt(500)) + ..addLogEntry(logAt(1000)); + + final anchor = TimelineService.instance + .build(kinds: {TimelineEventKind.route}) + .single; + final context = TimelineService.instance.contextAround(anchor); + + expect(context.length, 3); + // 正序:早 → 晚 / Oldest first + expect(context.first.timestamp.isBefore(context.last.timestamp), isTrue); + }); + + test('event key is stable per record and time text is HH:mm:ss.SSS', () { + InspectorService.instance.addNetworkRequest(netAt(0)); + final first = TimelineService.instance.build().single; + final second = TimelineService.instance.build().single; + + // 归并每次都重建事件对象,展开态必须依赖 key 而非对象身份。 + // Events are rebuilt on every merge, so expansion state relies on the key, + // never on object identity. + expect(first.key, second.key); + expect( + RegExp(r'^\d{2}:\d{2}:\d{2}\.\d{3}$').hasMatch(first.timeText), + isTrue, + ); + }); +} diff --git a/test/timeline_viewer_widget_test.dart b/test/timeline_viewer_widget_test.dart new file mode 100644 index 0000000..d444df3 --- /dev/null +++ b/test/timeline_viewer_widget_test.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:zero_inspector_kit/zero_inspector_kit.dart'; + +// 回归测试:TimelineViewer 在 ListView 的无限高约束下必须能正常布局, +// 不能因 Row + CrossAxisAlignment.stretch + Expanded 算出无限高度而崩溃。 +// Regression: TimelineViewer must lay out fine under ListView's unbounded +// vertical constraint — no infinite-height crash from a stretch+Expanded Row. +void main() { + final base = DateTime.now().millisecondsSinceEpoch; + + LogEntry logAt(int offsetMs) => LogEntry( + id: 'log-$offsetMs', + level: LogLevel.info, + message: 'message $offsetMs', + timestamp: DateTime.fromMillisecondsSinceEpoch(base + offsetMs), + tag: 'timeline-widget-test', + ); + + RouteEntry routeAt(int offsetMs) => RouteEntry( + id: 'route-$offsetMs', + routeName: '/page-$offsetMs', + timestamp: DateTime.fromMillisecondsSinceEpoch(base + offsetMs), + action: RouteAction.push, + ); + + setUp(() { + InspectorService.instance.clearAll(); + ErrorService.instance.clear(); + AlertService.instance.clearAll(); + }); + + testWidgets('TimelineViewer lays out event rows without unbounded height', ( + tester, + ) async { + InspectorService.instance + ..addLogEntry(logAt(0)) + ..addRouteEntry(routeAt(500)) + ..addLogEntry(logAt(1000)); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SizedBox(width: 400, height: 600, child: TimelineViewer()), + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.byType(TimelineViewer), findsOneWidget); + // 至少渲染出一条事件行(事件数 > 0 时 ListView 应有可见内容)。 + // At least one event row should be laid out when there are events. + expect(find.byType(ListView), findsOneWidget); + }); + + testWidgets('TimelineViewer shows empty state without crashing', ( + tester, + ) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SizedBox(width: 400, height: 600, child: TimelineViewer()), + ), + ), + ); + await tester.pumpAndSettle(); + expect(find.byType(TimelineViewer), findsOneWidget); + }); +} diff --git a/test/version_consistency_test.dart b/test/version_consistency_test.dart new file mode 100644 index 0000000..2a6272f --- /dev/null +++ b/test/version_consistency_test.dart @@ -0,0 +1,62 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:zero_inspector_kit/zero_inspector_kit.dart'; + +/// 版本一致性回归测试 / Version consistency regression tests +/// +/// 发版时版本号散落在多处,漏改一处就会让 pub.dev 上的包与文档自相矛盾 +/// (HAR 导出的 `creator.version` 曾长期停在旧版本)。这里以 `pubspec.yaml` +/// 的 `version` 为真值,断言其它各处同步。 +/// At release time the version lives in several places; missing one leaves the +/// published package contradicting its own docs (the HAR export's +/// `creator.version` used to sit on an old release). `pubspec.yaml`'s +/// `version` is the source of truth and every other place is asserted here. +/// +/// 更完整的校验(含逐行定位与修复提示)由 `tool/check_release.dart` 提供, +/// 由 CI 单独一步执行;这里保证 `flutter test` 也能拦住漂移。 +/// The fuller check (per-line locations and fix hints) lives in +/// `tool/check_release.dart`, run as its own CI step; this keeps `flutter test` +/// able to catch drift too. +void main() { + late String pubspecVersion; + + setUpAll(() { + final pubspec = File('pubspec.yaml').readAsStringSync(); + final match = RegExp( + r'^version:\s*([0-9]+\.[0-9]+\.[0-9]+)\s*$', + multiLine: true, + ).firstMatch(pubspec); + expect(match, isNotNull, reason: 'pubspec.yaml must declare a version'); + pubspecVersion = match!.group(1)!; + }); + + test('InspectorVersion.value matches pubspec.yaml', () { + expect(InspectorVersion.value, pubspecVersion); + }); + + test('iOS podspec version matches pubspec.yaml', () { + final podspec = File('ios/zero_inspector_kit.podspec').readAsStringSync(); + final match = RegExp(r"s\.version\s*=\s*'([^']+)'").firstMatch(podspec); + expect(match, isNotNull); + expect(match!.group(1), pubspecVersion); + }); + + for (final readme in ['README.md', 'README_zh.md']) { + test('$readme install snippets reference the current version', () { + final content = File(readme).readAsStringSync(); + expect(content, contains('zero_inspector_kit: ^$pubspecVersion')); + expect(content, contains('release/v$pubspecVersion')); + }); + } + + test('CHANGELOG.md has a section for the current version', () { + final changelog = File('CHANGELOG.md').readAsStringSync(); + final match = RegExp( + r'^##\s*([0-9]+\.[0-9]+\.[0-9]+)\s*$', + multiLine: true, + ).firstMatch(changelog); + expect(match, isNotNull); + expect(match!.group(1), pubspecVersion); + }); +} diff --git a/tool/check_release.dart b/tool/check_release.dart new file mode 100644 index 0000000..16e0e3f --- /dev/null +++ b/tool/check_release.dart @@ -0,0 +1,184 @@ +// ignore_for_file: avoid_print + +import 'dart:io'; + +/// 发版前版本一致性自检 / Pre-release version consistency self-check +/// +/// 用法 / Usage: +/// ```bash +/// dart run tool/check_release.dart +/// ``` +/// +/// 发版最容易出错的地方不是代码,而是"版本号忘了改全一处": +/// `pubspec.yaml`、`ios/zero_inspector_kit.podspec`、 +/// `lib/src/utils/inspector_version.dart`、两个 README 的安装片段与升级提示、 +/// 以及 `CHANGELOG.md` 顶部。漏掉任何一处都会让 pub.dev 上的包与文档 +/// 相互矛盾(HAR 导出里的 creator.version 就曾长期停在旧版本)。 +/// The most common release mistake is not code but a version string left +/// stale in one place: `pubspec.yaml`, `ios/zero_inspector_kit.podspec`, +/// `lib/src/utils/inspector_version.dart`, the install snippets and upgrade +/// callouts in both READMEs, and the top of `CHANGELOG.md`. Missing any of +/// them leaves the published package contradicting its own docs (the HAR +/// `creator.version` used to sit on an old release for a long time). +/// +/// 本脚本以 `pubspec.yaml` 的 `version` 为唯一真值,逐处核对, +/// 任何一处不一致即以非零退出码失败(供 CI 拦截)。 +/// `pubspec.yaml`'s `version` is the single source of truth; every other +/// place is checked against it and any mismatch fails with a non-zero exit +/// code so CI can block the release. +void main() { + final version = _readPubspecVersion(); + if (version == null) { + _fail('pubspec.yaml: cannot parse the top-level `version:` field.'); + exit(1); + } + print('Release version (from pubspec.yaml): $version'); + + final problems = [ + ..._checkPodspec(version), + ..._checkInspectorVersion(version), + ..._checkReadme('README.md', version), + ..._checkReadme('README_zh.md', version), + ..._checkChangelog(version), + ]; + + if (problems.isNotEmpty) { + print(''); + print('${problems.length} version problem(s) found:'); + for (final p in problems) { + print(' ✗ $p'); + } + print(''); + print( + 'Bump every occurrence to $version (see AGENTS.md release checklist).', + ); + exit(1); + } + print('✅ All version references are consistent with $version.'); +} + +/// 版本串正则(X.Y.Z)/ Version string pattern (X.Y.Z) +final RegExp _anyVersion = RegExp(r'\d+\.\d+\.\d+'); + +/// 读取 pubspec.yaml 顶层的 version / Read the top-level version from pubspec.yaml +String? _readPubspecVersion() { + final file = File('pubspec.yaml'); + if (!file.existsSync()) return null; + // 只取文件头部(避免命中 dependency 里的 version 约束)。 + // Only scan the header so dependency constraints are not mistaken for it. + final head = file.readAsStringSync().split('\n').take(40).join('\n'); + final match = RegExp( + r'^version:\s*([0-9]+\.[0-9]+\.[0-9]+)\s*$', + multiLine: true, + ).firstMatch(head); + return match?.group(1); +} + +/// 读取文件内容,缺失时报错 / Read a file's content, report when missing +String? _read(String path, List problems) { + final file = File(path); + if (!file.existsSync()) { + problems.add('$path: file not found.'); + return null; + } + return file.readAsStringSync(); +} + +/// 校验 iOS podspec 版本 / Check the iOS podspec version +List _checkPodspec(String version) { + const path = 'ios/zero_inspector_kit.podspec'; + final problems = []; + final content = _read(path, problems); + if (content == null) return problems; + final match = RegExp(r"s\.version\s*=\s*'([^']+)'").firstMatch(content); + if (match == null) { + problems.add('$path: no `s.version` found.'); + } else if (match.group(1) != version) { + problems.add('$path: s.version is ${match.group(1)}, expected $version.'); + } + return problems; +} + +/// 校验 lib/src/utils/inspector_version.dart / Check inspector_version.dart +List _checkInspectorVersion(String version) { + const path = 'lib/src/utils/inspector_version.dart'; + final problems = []; + final content = _read(path, problems); + if (content == null) return problems; + final match = RegExp( + r"static const String value = '([^']+)'", + ).firstMatch(content); + if (match == null) { + problems.add('$path: no `InspectorVersion.value` found.'); + } else if (match.group(1) != version) { + problems.add( + '$path: InspectorVersion.value is ${match.group(1)}, expected $version.', + ); + } + return problems; +} + +/// 校验 README 里的安装片段与升级提示 / Check README install snippets & callouts +/// +/// 只核对"版本敏感行"(含 `zero_inspector_kit:` 依赖声明或 `release/v` 归档 +/// 分支引用的行),历史性叙述(如 "since v1.9.0")不参与校验,避免误报。 +/// Only "version-sensitive" lines are checked (those declaring the +/// `zero_inspector_kit:` dependency or referencing a `release/v` archive +/// branch); historical prose such as "since v1.9.0" is intentionally ignored. +List _checkReadme(String path, String version) { + final problems = []; + final content = _read(path, problems); + if (content == null) return problems; + + final lines = content.split('\n'); + for (var i = 0; i < lines.length; i++) { + final line = lines[i]; + final isSensitive = + line.contains('zero_inspector_kit:') || + line.contains('release/v') || + line.contains('^$version') || + line.contains(RegExp(r'\^\d+\.\d+\.\d+')); + if (!isSensitive) continue; + for (final match in _anyVersion.allMatches(line)) { + final found = match.group(0)!; + if (found != version) { + problems.add('$path:${i + 1}: found $found, expected $version.'); + } + } + } + + // 硬性要求:两个安装片段必须存在,否则说明文档结构变了/被误删。 + // Hard requirement: both install snippets must exist, otherwise the doc + // structure changed (or the snippet was deleted by accident). + if (!content.contains('zero_inspector_kit: ^$version')) { + problems.add('$path: missing the `zero_inspector_kit: ^$version` snippet.'); + } + if (!content.contains('release/v$version')) { + problems.add('$path: missing the `ref: release/v$version` git snippet.'); + } + return problems; +} + +/// 校验 CHANGELOG 顶部版本 / Check the CHANGELOG top version +List _checkChangelog(String version) { + const path = 'CHANGELOG.md'; + final problems = []; + final content = _read(path, problems); + if (content == null) return problems; + final match = RegExp( + r'^##\s*([0-9]+\.[0-9]+\.[0-9]+)\s*$', + multiLine: true, + ).firstMatch(content); + if (match == null) { + problems.add('$path: no `## X.Y.Z` section found at the top.'); + } else if (match.group(1) != version) { + problems.add( + '$path: top section is ${match.group(1)}, expected $version ' + '(add a `## $version` entry before releasing).', + ); + } + return problems; +} + +/// 打印一条致命错误 / Print a fatal error +void _fail(String message) => print('✗ $message'); diff --git a/website/pages/FPS-Viewer.md b/website/pages/FPS-Viewer.md index 447f4fc..4807ed3 100644 --- a/website/pages/FPS-Viewer.md +++ b/website/pages/FPS-Viewer.md @@ -69,6 +69,28 @@ When turned off, all callbacks and timers are cancelled, leaving no residual ove "Reset" 按钮清空所有统计和历史数据,适合测量特定交互场景。 +## Main-thread Blocking Watchdog / 主线程阻塞看门狗 + +The FPS panel also includes a **main-thread blocking watchdog** that catches stalls FPS cannot see. + +FPS 面板还内置**主线程阻塞看门狗**,专门捕捉 FPS 看不见的卡死。 + +> **Available since v1.12.0** +> +> **v1.12.0 起可用** + +**Why it's needed / 为什么需要**: FPS only measures frames. When the UI isolate truly stalls (e.g. a synchronous heavy computation), it produces **zero frames**, so FPS reads as idle while the app is actually frozen. The watchdog closes that blind spot. + +FPS 只测帧。当 UI isolate 真正卡死(如同步重计算)时,它**零帧产出**,于是 FPS 显示空闲,而 App 其实已冻住。看门狗补上这个盲区。 + +**How it works / 工作原理**: + +- A low-frequency **100ms heartbeat** measures the UI isolate's responsiveness directly / 用 **100ms 低频心跳** 直接测量 UI isolate 的响应间隔 +- If a gap exceeds **300ms** with no response, it records a blocking event (duration / time / nearby logs) / 若超过 **300ms** 无响应,记录一条阻塞事件(时长 / 时间 / 附近日志) +- **Off by default** with its **own switch** (not tied to the FPS master switch) — zero overhead unless you opt in / **默认关闭**、有**独立开关**(不随 FPS 总开关联动),不开启则零开销 + +开启后,真正卡死(不产帧)也会被记成阻塞事件,配合 FPS 一起看因果。 + ## How It Works / 工作原理 FPS monitoring uses Flutter's `WidgetsBinding.instance.addTimingsCallback` to receive frame timing information from the engine. The callback is **batched** — it may return multiple `FrameTiming` objects per call, so each frame is recorded individually inside the loop to ensure accurate FPS calculation. diff --git a/website/pages/Getting-Started.md b/website/pages/Getting-Started.md index 3472d2c..9fb92d1 100644 --- a/website/pages/Getting-Started.md +++ b/website/pages/Getting-Started.md @@ -51,6 +51,10 @@ After integration, the inspector automatically does the following **without modi > > **Memory** 和 **FPS** 面板作为标签页可用,但**默认关闭**以避免性能开销。在各自面板顶部打开开关才会开始采集数据。详见 [Memory Viewer](Memory-Viewer) 和 [FPS Viewer](FPS-Viewer)。 +> **Pre-initialized binding / 提前初始化的 binding**: If you call `WidgetsFlutterBinding.ensureInitialized()` or `await` a platform-channel plugin (e.g. `SharedPreferences`) **before** `runAppWithInspector()`, the binding is already initialized. In that case `runAppWithInspector()` degrades gracefully to a plain `runApp()` (capturing `debugPrint` logs, but not raw `print()`) instead of throwing a "Zone mismatch" assertion. No extra setup is needed. +> +> **binding 已提前初始化**:如果你在 `runAppWithInspector()` **之前**调用了 `WidgetsFlutterBinding.ensureInitialized()` 或 `await` 了会触发 platform channel 的插件(如 `SharedPreferences`),binding 已被初始化。此时 `runAppWithInspector()` 会优雅降级为普通 `runApp()`(仍捕获 `debugPrint` 日志,但不捕获原始 `print()`),而非抛出 "Zone mismatch" 断言。无需额外处理。 + ## Production Build / 生产构建 The inspector is **automatically disabled** in release mode. You don't need to remove any code — Flutter's tree-shaking will remove all inspector-related code from production builds. diff --git a/website/pages/Timeline.md b/website/pages/Timeline.md new file mode 100644 index 0000000..f3352e1 --- /dev/null +++ b/website/pages/Timeline.md @@ -0,0 +1,54 @@ +# Timeline / 统一会话时间线 + +## Overview / 概述 + +The Timeline tab merges network, logs, errors, routes, and alerts into a **single time-ordered stream**, so you can see the full causal chain of a session at a glance — e.g. "route push → two requests → error log → 5xx alert". Each source keeps its own data; the Timeline view only merges them for display. + +统一会话时间线把网络、日志、异常、路由、告警归并为**一条按时间排序的流**,让你一眼看清一次会话的完整因果链——例如「路由跳转 → 两个请求 → error 日志 → 5xx 告警」。各来源的数据仍归属各自服务,时间线视图只做归并显示。 + +> **Available since v1.12.0** +> +> **v1.12.0 起可用** + +## Where to find it / 入口 + +Tap the floating inspector button → the **Timeline** tab (located after **Routes** and before **Widgets** in the panel). + +点击悬浮检查器按钮 → **Timeline** 标签页(位于面板中 **Routes** 之后、**Widgets** 之前)。 + +## Features / 功能 + +### 1. Merged stream / 归并流 + +- Network / Logs / Errors / Routes / Alerts appear interleaved by timestamp / 网络 / 日志 / 异常 / 路由 / 告警按时间戳交错排列 +- Each entry shows its source icon, a colored left accent bar, timestamp, and a concise summary / 每条显示来源图标、左侧彩色强调条、时间戳与精炼摘要 +- Tap any entry to expand details (e.g. request URL/status, log level/message, route action/name) / 点击任意条目展开详情(如请求 URL/状态、日志级别/内容、路由操作/名称) + +把五类数据按时间归并,每条带来源图标、彩色强调条、时间戳与摘要,可点击展开。 + +### 2. Per-source filtering / 逐来源筛选 + +- Toggle each source on/off to focus on the ones you care about / 逐来源开关,只看关心的来源 +- Filtering only hides entries from the merged view; underlying data is untouched / 筛选只隐藏归并视图中的条目,底层数据不受影响 + +顶部可按来源开关自由筛选,归并显示内容随之变化,底层数据不变。 + +### 3. Focus ±N seconds / ±N 秒聚焦 + +- Tap any event to enter "focus" mode: the view keeps only events within ±N seconds of the anchor / 点击任意事件进入「聚焦」模式,仅保留锚点前后 ±N 秒内的事件 +- Window selector: 5s / 10s (default) / 30s / 窗口可选:5s / 10s(默认)/ 30s +- Great for isolating the exact cause-effect around a failure (e.g. what happened right before a 5xx alert) / 适合隔离某次失败前后的精确因果(如 5xx 告警前到底发生了什么) + +点击任意事件即可「聚焦它前后 ±N 秒」,把噪音过滤掉,只留因果相关事件。 + +## How It Works / 工作原理 + +- **View-only merge** — `TimelineService.build()` reads from `InspectorService` (network / logs / errors), `RouteTrackerService`, and `AlertService`, sorts them by time, and tags each with its `TimelineEventKind`. It does **not** copy or own any data. / **仅视图归并**:`TimelineService.build()` 从各服务读取数据、按时间排序并打上来源标签,不复制、不持有数据。 +- **Zero cost when closed** — no timers, no listeners, no polling while the panel is not open; the merge runs on demand when you open the Timeline tab. / **面板关闭时零开销**:未打开面板时不跑定时器、不监听、不轮询,仅在打开 Timeline 时按需归并。 +- **`contextAround(anchor, window)`** — given an anchor event and a `Duration` window, returns the slice of events whose timestamp falls within `[anchor - window, anchor + window]`, oldest-first. / 给定锚点事件与窗口时长,返回落在 `[锚点 - 窗口, 锚点 + 窗口]` 内的事件,按时间升序。 + +## Related / 相关 + +- [Usage](Usage) — General usage guide / 通用使用指南 +- [FPS Viewer](FPS-Viewer) — Includes the main-thread blocking watchdog / 含主线程阻塞看门狗 +- [Route Tracker](Route-Tracker) — Route tracking details / 路由追踪详情 diff --git a/website/pages/Usage.md b/website/pages/Usage.md index dfc8f30..1c1ed2e 100644 --- a/website/pages/Usage.md +++ b/website/pages/Usage.md @@ -27,9 +27,9 @@ void main() { ## Inspector Panel / 检查器面板 -The inspector panel contains **9 tabs** (as of v1.9.0): +The inspector panel contains **10 tabs** (as of v1.12.0): -检查器面板包含 **9 个标签页**(v1.9.0 起): +检查器面板包含 **10 个标签页**(v1.12.0 起): | Tab | Icon | Feature | |-----|------|---------| @@ -38,8 +38,9 @@ The inspector panel contains **9 tabs** (as of v1.9.0): | **Errors** | 🚨 | Aggregated & deduped crash viewing / 去重聚合的异常查看 | | **Database** | 💾 | Database and table inspection / 数据库查看 | | **Memory** | 📊 | Memory trend, Dart Heap, Native memory, leak detection / 内存趋势、Dart Heap、Native 内存、泄漏检测 | -| **FPS** | 🎯 | Real-time FPS, jank rate, trend chart / 实时 FPS、卡顿率、趋势图 | +| **FPS** | 🎯 | Real-time FPS, jank rate, trend chart, plus main-thread blocking watchdog / 实时 FPS、卡顿率、趋势图,以及主线程阻塞看门狗 | | **Routes** | 🧭 | Route navigation tracking / 路由追踪 | +| **Timeline** | 🧵 | Unified session timeline: network / logs / errors / routes / alerts merged into one stream, with ±N-second focus / 统一会话时间线:网络/日志/异常/路由/告警按时间归并,支持 ±N 秒聚焦 | | **Widgets** | 🔍 | Widget tree snapshot for the current route / 当前路由的 Widget 树快照 | | **Alerts** | 🔔 | Rule-based alerts with unread badge / 基于规则的告警与未读角标 | diff --git a/website/pages/_meta.js b/website/pages/_meta.js index 6684390..a577e95 100644 --- a/website/pages/_meta.js +++ b/website/pages/_meta.js @@ -10,6 +10,7 @@ export default { Errors: '🚨 Errors', 'Database-Viewer': '💾 Database Viewer', 'Route-Tracker': '🧭 Route Tracker', + Timeline: '🧵 Timeline', 'Memory-Viewer': '📊 Memory Viewer', 'FPS-Viewer': '🎯 FPS Viewer', Alerts: '🔔 Alerts', diff --git a/website/pages/index.md b/website/pages/index.md index 6d9df83..ac12c9c 100644 --- a/website/pages/index.md +++ b/website/pages/index.md @@ -16,6 +16,7 @@ A powerful Flutter plugin for in-app developer console, providing real-time debu | **Memory Viewer** | Trend chart, Dart Heap, Native memory, leak detection (incl. Flutter MemoryAllocations bridge) / 内存趋势图、Dart Heap、Native 内存、泄漏检测(含官方 MemoryAllocations 桥接) | | **FPS Monitor** | Real-time FPS, jank rate, trend chart / 实时 FPS、卡顿率、趋势图 | | **Route Tracker** | Navigation history tracking / 路由追踪 | +| **Session Timeline** | Unified view of network/logs/errors/routes/alerts by time, with ±N-second focus / 网络/日志/异常/路由/告警按时间归并,支持 ±N 秒聚焦 | | **Session Persistence** | Logs/network/errors survive restarts via a disk ring buffer; logs & errors replay on launch; one-tap session archive export / 日志/网络/异常通过磁盘环形缓冲跨重启保留,启动时回放日志与异常;一键导出会话存档 | | **Alerts** | Rule-based alerts (network/log/memory/FPS) with unread badge / 基于规则的告警(网络/日志/内存/FPS)与未读角标 | | **Sensitive Masking & cURL** | Mask secrets on export; one-click cURL copy; batch ops / 导出遮蔽敏感字段、一键复制 cURL、批量操作 | @@ -34,6 +35,7 @@ A powerful Flutter plugin for in-app developer console, providing real-time debu | [Errors](Errors) | Aggregated error viewing / 异常聚合查看 | | [Database Viewer](Database-Viewer) | Database inspection / 数据库查看器 | | [Route Tracker](Route-Tracker) | Route tracking / 路由追踪 | +| [Timeline](Timeline) | Unified session timeline / 统一会话时间线 | | [Memory Viewer](Memory-Viewer) | Memory monitoring & leak detection / 内存监控与泄漏检测 | | [FPS Viewer](FPS-Viewer) | FPS monitoring & jank detection / FPS 监控与卡顿检测 | | [Alerts](Alerts) | Rule-based alerting / 基于规则的告警 |