flutter中WebView的使用及JavaScript桥接的问题记录

一、在原生安卓中的使用

在原生安卓中一般采用的是@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)
     }

    }

  2. 继承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")
     }

    }

  3. js端调用桥接方法。

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

二、在flutter端的使用

  1. 在pubspec.yaml中引用webview的包。

    webview_flutter: ^4.0.7

  2. 创建一个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 createState() => _UserAgreementWebPageState();
    }

    class _UserAgreementWebPageState extends State {
    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(
    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 createState() => _UserAgreementWebPageState();
    }

    class _UserAgreementWebPageState extends State {
    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(
    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();
    }
    }

  2. 经过上述操作后,就可以在日志打印中看到我们收到的消息了。

相关推荐
kyriewen14 小时前
我手写了一个 EventEmitter,面试官追问了 6 个问题——第 4 个我没答上来
前端·javascript·面试
山河木马15 小时前
矩阵专题2-怎么创建视图矩阵(uViewMatrix)
javascript·webgl·计算机图形学
tangdou36909865516 小时前
AI真好玩系列-2分钟快速了解DeepAgents | Quick Guide to DeepAgents in 2 Minutes
前端·javascript·后端
张元清16 小时前
React useIntersectionObserver Hook:懒加载与可见性检测(2026)
javascript·react.js
彭于晏爱编程17 小时前
纯 JS + Node,一个下午手搓了能读懂公司代码的 AI 助手,老板以为我转行了
前端·javascript
妙码生花18 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十四):眨眼小人登录页制作
前端·javascript·ai编程
妙码生花18 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十三):前端路由初始化
前端·javascript·ai编程
PBitW18 小时前
GPT训练我的第四天,被打惨了!!!😭😭😭
前端·javascript·面试
DarkLONGLOVE18 小时前
快速上手 Pinia!Vue3 极简状态管理使用教程
javascript·vue.js
mackbob18 小时前
.eslintrc.js详细配置说明
javascript