一、在原生安卓中的使用

在原生安卓中一般採用的是@JavascriptInterface註解和WebView的addJavascriptInterface方法實現橋接

  1. 實現一個@JavascriptInterface的註解。
class JavaScriptCall() {

    @JavascriptInterface
    fun CallApp(string: String) {
        //{"action":"geolocationGet","jsCallBackId":"func1703575966310","params":{"coordinate":1,"withReGeocode":false}}
        LogUtils.e("CallApp" + string)
    }
}
  1. 繼承WebView使用addJavascriptInterface添加橋接方法。
class BridgeWebView : WebView{
    var bridgeJs = JavaScriptCall(this)

    constructor(context: Context) : super(context) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
        init()
    }

    private fun init() {
        addJavascriptInterface(bridgeJs, "ISANDNative")
    }
}
  1. js端調用橋接方法。
// JavaScript代碼調用Android原生
ISANDNative.CallApp('{"action":"closeWebView","jsCallBackId":"...","params":{...}}');

二、在flutter端的使用

  1. 在pubspec.yaml中引用webview的包。
webview_flutter: ^4.0.7
  1. 創建一個widget實現WebViewController和WebViewWidget(老版的WebView已不建議使用)。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';

class UserAgreementWebPage extends StatefulWidget {
  final String url;
  final String title;

  const UserAgreementWebPage({Key? key, required this.url, this.title = ''}) : super(key: key);

  @override
  State<UserAgreementWebPage> createState() => _UserAgreementWebPageState();
}

class _UserAgreementWebPageState extends State<UserAgreementWebPage> {
  late final WebViewController _controller;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();

    // 初始化WebViewController
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..addJavaScriptChannel(
        'ISANDNative',
        onMessageReceived: (JavaScriptMessage message) {
          print('接收到JS消息: ${message.message}');
          Get.back();
        },
      )
    // 添加額外的錯誤處理通道,便於調試
      ..addJavaScriptChannel(
        'debugChannel',
        onMessageReceived: (JavaScriptMessage message) {
          print('調試信息: ${message.message}');
        },
      )
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageStarted: (String url) {
            setState(() {
              _isLoading = true;
            });
          },
          onPageFinished: (String url) {
            setState(() {
              _isLoading = false;
            });
          },
          onWebResourceError: (WebResourceError error) {
            setState(() {
              _isLoading = false;
            });
            // 處理錯誤
            print('Web錯誤: ${error.description}');
          },
          onNavigationRequest: (NavigationRequest request) async {
            print("onNavigationRequest:${request.url}");
            return NavigationDecision.navigate;
          },
        ),
      )
      ..loadRequest(Uri.parse(widget.url));
  }

  @override
  Widget build(BuildContext context) {
    return AnnotatedRegion<SystemUiOverlayStyle>(
      value: SystemUiOverlayStyle(
        statusBarColor: Colors.white, // 設置狀態欄背景色
      ),
      child: SafeArea(
        child: Stack(
          children: [
            WebViewWidget(controller: _controller),
            if (_isLoading)
              const Center(
                child: CircularProgressIndicator(),
              ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    // 恢復狀態欄的默認樣式
    SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
      statusBarColor: Colors.transparent, // 根據您應用的默認樣式設置
      statusBarIconBrightness: Brightness.dark, // 根據您應用的默認樣式設置
    ));
    super.dispose();
  }
}

其中的addJavaScriptChannel即是添加橋接的方法,ISANDNative為通道名稱,onMessageReceived中為收到的消息。

  1. 通過上述配置後會發現實際還是收不到通道的消息,這是因為原生安卓和flutter調用的差異導致的。

Android原生調用方式:

// JavaScript代碼調用Android原生
ISANDNative.CallApp('{"action":"closeWebView","jsCallBackId":"...","params":{...}}');

Flutter調用方式:

// JavaScript代碼調用Flutter
ISANDNative.postMessage('{"action":"closeWebView","jsCallBackId":"...","params":{...}}');

如果Web端採用了flutter的調用方式,我們就可以正常收到通道的消息了。

  1. 如果您無法修改Web頁面的JavaScript代碼,可以在Flutter中注入一個適配腳本,將CallApp調用轉換為postMessage。完整代碼如下:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';

class UserAgreementWebPage extends StatefulWidget {
  final String url;
  final String title;

  const UserAgreementWebPage({Key? key, required this.url, this.title = ''}) : super(key: key);

  @override
  State<UserAgreementWebPage> createState() => _UserAgreementWebPageState();
}

class _UserAgreementWebPageState extends State<UserAgreementWebPage> {
  late final WebViewController _controller;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();

    // 初始化WebViewController
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..addJavaScriptChannel(
        'ISANDNative',
        onMessageReceived: (JavaScriptMessage message) {
          print('接收到JS消息: ${message.message}');
          Get.back();
        },
      )
    // 添加額外的錯誤處理通道,便於調試
      ..addJavaScriptChannel(
        'debugChannel',
        onMessageReceived: (JavaScriptMessage message) {
          print('調試信息: ${message.message}');
        },
      )
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageStarted: (String url) {
            setState(() {
              _isLoading = true;
            });
          },
          onPageFinished: (String url) {
            // 注入適配腳本,將CallApp調用轉換為postMessage
            _controller.runJavaScript('''
            // 創建一個包裝器來捕獲CallApp調用
            window.ISANDNative_Original = window.ISANDNative;
            window.ISANDNative = {
              CallApp: function(jsonString) {
                // 轉換為Flutter的調用方式
                window.ISANDNative_Original.postMessage(jsonString);
              }
            };
          ''');

            setState(() {
              _isLoading = false;
            });
          },
          onWebResourceError: (WebResourceError error) {
            setState(() {
              _isLoading = false;
            });
            // 處理錯誤
            print('Web錯誤: ${error.description}');
          },
          onNavigationRequest: (NavigationRequest request) async {
            print("onNavigationRequest:${request.url}");
            return NavigationDecision.navigate;
          },
        ),
      )
      ..loadRequest(Uri.parse(widget.url));
  }

  @override
  Widget build(BuildContext context) {
    return AnnotatedRegion<SystemUiOverlayStyle>(
      value: SystemUiOverlayStyle(
        statusBarColor: Colors.white, // 設置狀態欄背景色
      ),
      child: SafeArea(
        child: Stack(
          children: [
            WebViewWidget(controller: _controller),
            if (_isLoading)
              const Center(
                child: CircularProgressIndicator(),
              ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    // 恢復狀態欄的默認樣式
    SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
      statusBarColor: Colors.transparent, // 根據您應用的默認樣式設置
      statusBarIconBrightness: Brightness.dark, // 根據您應用的默認樣式設置
    ));
    super.dispose();
  }
}
  1. 經過上述操作後,就可以在日誌打印中看到我們收到的消息了。

Flutter 中 WebView 的使用以及與 JS 交互_flutter