简体中文 | English


offline_webview

A lightweight high-performance Flutter SDK for offline web package loading.

Features

  • Offline Package Management: Download, cache, and serve web resources locally
  • URL Matching: Flexible rule-based URL to bisName matching (param mode & rule mode)
  • Flow Pipeline: Extensible chain-of-responsibility processing pipeline
  • WebView Integration: Seamless InAppWebView (iOS/Android/HarmonyOS) integration with screenshot cache and vConsole
  • Performance Monitor: Built-in debug floating panel showing loading timeline comparison
  • Monitoring & Reporting: Built-in logging, monitoring, and data reporting
  • Local Server: Per-package HTTP server on localhost, serving resources without file:// hacks
  • Fastlane CI/CD: One-command build & distribute to Pgyer for iOS/Android
  • HarmonyOS Support: Runs on HarmonyOS (OHOS) via the community-adapted flutter_inappwebview_ohos

Installation

dependencies:
  offline_webview: ^1.1.0

Quick Start

1. Start the test server

cd tool
python3 server.py
# Server auto-detects LAN IP and runs at http://<your-lan-ip>:18730

2. Initialize the SDK

import 'package:offline_webview/offline_webview.dart';

// Build config (preDownloadAll downloads all available packages from server)
final config = OfflineConfigBuilder()
    .isOpen(true)
    .preDownloadAll(true)
    .build();

// Build params
final params = OfflineParams()
    .config(config)
    .isDebug(true)
    .logBlock((level, message) => print(message))
    .reportBlock((event, bisName, params) => print('Report: $event'))
    .monitorBlock((type, data) => print('Monitor: $type'))
    .requestServer(YourRequest());

await OfflineWebClient.init(params);

3. Use OfflineWebView

// Param mode: URL contains ?offweb=bisName
OfflineWebView(
  initialUrl: 'https://example.com/page?offweb=my-bis-name',
  enableVConsole: true,  // inject vConsole debug panel
)

// With controller for reload
final controller = OfflineWebViewController();
OfflineWebView(
  initialUrl: 'https://example.com/page?offweb=my-bis-name',
  controller: controller,
  onLoadTiming: (totalMs) => print('Loaded in ${totalMs}ms'),
)

// Reload offline web
controller.reloadOfflineWeb();

4. Implement IOfflineRequest

class YourRequest extends IOfflineRequest {
  @override
  void requestPackageInfo(
    String bisName,
    String version,
    RequestCallback<OfflinePackageInfo> callback,
  ) async {
    final url = Uri.parse('http://your-server:18730/offweb').replace(
      queryParameters: {'bisName': bisName, 'offlineZipVer': version},
    );
    final response = await http.get(url);
    final json = jsonDecode(response.body);
    callback.onSuccess(OfflinePackageInfo.fromJson(json));
  }

  @override
  void requestAllBisNames(RequestCallback<List<String>> callback) async {
    final response = await http.get(Uri.parse('http://your-server:18730'));
    final json = jsonDecode(response.body);
    final packages = (json['packages'] as List).map((e) => e.toString()).toList();
    callback.onSuccess(packages);
  }
}

Creating Offline Packages

An offline package is a standard .zip file containing your web resources and a version manifest.

Package structure

my-package.zip
├── .offweb.json      # Required: version manifest
├── index.html        # Entry point
├── css/
│   └── style.css
├── js/
│   └── app.js
└── images/
    └── logo.png

Version manifest (.offweb.json)

Place this file at the root of the zip:

{
  "bisName": "my-package",
  "version": "v1"
}

Requirements

  • .offweb.json must exist at the zip root with bisName and version fields
  • index.html must exist as the entry point
  • All resource paths (CSS, JS, images) should use relative paths
  • H5 pages must support null-origin for cookie/storage (loaded from localhost)

Upload to server

Place the zip file in the server's packages/ directory, or use the built-in upload page at http://<server-ip>:18730/upload.

Server query API

When the SDK queries the server, the response format is:

{
  "bisName": "my-package",
  "result": 1,
  "url": "http://server:18730/package?bisName=my-package",
  "refreshMode": 0,
  "version": "v2"
}
Field Description
result -1 = disabled, 0 = same version, 1 = update available
url Download URL for the zip package
refreshMode 0 = normal, 1 = force refresh
version Latest version string

URL Matching

Param mode

Append ?offweb=<bisName> to the URL. The SDK automatically intercepts it:

OfflineWebView(
  initialUrl: 'https://example.com/page?offweb=my-bis-name',
)

Rule mode

Configure OfflineRuleConfig to match URLs by host/path patterns without modifying the URL:

final params = OfflineParams()
    .config(OfflineConfigBuilder().isOpen(true).build())
    .setRule(OfflineRuleConfig(
      rules: [
        OfflineRuleItem(host: 'example.com', path: '/app/*', bisName: 'my-app'),
      ],
    ))
    .requestServer(YourRequest());

Performance Monitor

The SDK includes a debug-mode performance monitor with a draggable floating panel:

FloatingPerformancePanel(
  child: OfflineWebView(initialUrl: '...'),
)

// Or standalone (OfflineWebView embeds it internally)
FloatingPerformancePanel()
Metric Description
webViewCreatedMs Time from widget creation to WebView ready
firstPaintMs Time to first visual paint
loadCompleteMs Time to page fully loaded
queryMs Offline query phase (offline mode only)
downloadMs Package download phase (offline mode only)
unzipMs Package extraction phase (offline mode only)

Color coding: green (<50ms), blue (<200ms), yellow (<500ms), red (>=500ms).

Configuration

final config = OfflineConfigBuilder()
    .isOpen(true)                      // Enable/disable offline feature
    .preDownloadAll(true)              // Pre-download all packages from server
    .addPreDownload('bis-name-1')      // Pre-download specific packages
    .addPreDownload('bis-name-2')
    .addDisable('bis-name-3')          // Disable specific packages
    .build();

Local Development Server

A Python HTTP server is provided for local testing:

cd tool
python3 server.py

Server endpoints:

  • GET / - Server info & registered packages
  • GET /health - Health check
  • GET /offweb?bisName=xxx&offlineZipVer=xxx - Query package update
  • GET /package?bisName=xxx - Download package zip
  • GET /upload - Web upload page
  • POST /upload - Upload zip package
  • GET /demo - Demo HTML page

Architecture

+-------------------------------------------------------------+
|                     OfflineWebClient                         |
+-------------------------------------------------------------+
                            |
                            v
+-------------------------------------------------------------+
|                    OfflineWebManager                         |
+-------------------------------------------------------------+
                            |
              +-------------+-------------+
              |                           |
              v                           v
+---------------------------+ +-----------------------------+
|   ResourceFlow Pipeline   | |   PerformanceMonitor         |
|  FetchPackageFlow         | |   (singleton, Stream-based)  |
|    -> DownloadFlow        | |   ↳ FloatingPerformancePanel |
|    -> ParsePackageFlow    | +-----------------------------+
|    -> ReplaceResFlow      |
+---------------------------+
              |
              v
+-------------------------------------------------------------+
|                   OfflineWebView                             |
|   (InAppWebView + LocalServer + WebViewPreloadPool)          |
+-------------------------------------------------------------+

CI/CD with Fastlane

bundle install

# Build & upload iOS
bundle exec fastlane build_ios

# Build & upload Android
bundle exec fastlane build_android

Platform Support

  • iOS (InAppWebView / WKWebView)
  • Android (InAppWebView / WebView)
  • HarmonyOS / OHOS (InAppWebView / ArkWeb, via community adaptation — see HarmonyOS Support)
  • Flutter Web (limited)

HarmonyOS Support

HarmonyOS (OHOS) is supported through the OpenHarmony community adaptation of flutter_inappwebview. The official plugin does not yet declare the ohos platform, so this package overrides it via dependency_overrides in pubspec.yaml:

dependency_overrides:
  flutter_inappwebview:
    git:
      url: https://gitcode.com/openharmony-sig/flutter_inappwebview.git
      path: flutter_inappwebview
      ref: br_v6.1.5_ohos
  path_provider:
    git:
      url: https://gitcode.com/openharmony-tpc/flutter_packages.git
      path: packages/path_provider/path_provider
      ref: br_path_provider-v2.1.5_ohos

The community fork is based on the official v6.1.5 plus an OHOS implementation, so it stays fully compatible with Android/iOS/macOS/Web — existing platforms are unaffected. The git repository uses Git LFS; install git-lfs and set GIT_LFS_SKIP_SMUDGE=1 on first flutter pub get (the SDK's Dart code does not depend on the LFS-tracked binaries).

Running the example on HarmonyOS

  1. Install the Flutter for OpenHarmony SDK and DevEco Studio (see flutter_flutter_ohos).
  2. cd example && flutter pub get
  3. flutter build hap (or flutter run on a connected HarmonyOS device/emulator).
  4. The OHOS app declares ohos.permission.INTERNET (required by the local HTTP server and package downloads) in example/ohos/entry/src/main/module.json5.

The OHOS adaptation of flutter_inappwebview marks a few APIs as not supported (e.g. keepAlive, onLoadResource, onCreateWindow, onDownloadStartRequest). This SDK only forwards those callbacks to the host app, so they simply won't fire on OHOS unless the app passes them — no breakage. See the support matrix.

Publishing note: dependency_overrides with git references cannot be published to pub.dev. Remove the dependency_overrides block before publishing; HarmonyOS consumers add it back in their own app. See docs/harmonyos_support.md for details.

License

MIT License - see LICENSE file

Libraries

offline_webview