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. 经过上述操作后,就可以在日志打印中看到我们收到的消息了。

相关推荐
程序员大阳11 小时前
副队长JavaScript教程(50)-DOM:网页开发的起点
javascript·dom·es·节点·概念·document·关系
香芋芋圆15 小时前
iD-editor 详解|OSM官方在线编辑器,基于D3.js的轻量地图编辑器
开发语言·javascript·编辑器
晗夜未央17 小时前
任务队列反复失败,先分清重试、阻塞和结果待核对
开发语言·javascript
jktantan18 小时前
每个节点 41 字节:把一棵百万节点树拆进 14 个 typed array
前端·javascript·vue.js·算法·typescript
我命由我1234518 小时前
访客系统形式(有用户概念、无用户概念)
java·开发语言·前端·javascript·python·产品运营·产品经理
程序员大阳19 小时前
副队长JavaScript教程(65)-详解JSON:轻量级数据交换格式
javascript·json·序列化·反序列化·stringify·parse
paopaokaka_luck21 小时前
基于springboot+vue的益暖微光公益服务平台(DeepSeek公益助手、善款信息追溯、志愿服务积分闭环、ECharts运营分析、活动状态自动流转)
java·前端·javascript·spring boot·mysql·架构·echarts
栀椩21 小时前
Electron 内嵌页面跨域问题(CORS/PNA)解决全记录
前端·javascript·electron
SEO_juper21 小时前
Chrome 扩展一键查看当前页面 SEO 信息:不用切页面,点一下全出来
前端·javascript·chrome
Highcharts.js21 小时前
CSUN 2026|Highcharts无障碍数据与AI分享
javascript·人工智能·数据分析·highcharts·图表开发