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<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();
    }
    }

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

相关推荐
911hzh13 小时前
Flutter 音视频通话集成实战:WebSocket 做信令,WebRTC 传音视频,附详细事件时序图
websocket·flutter·音视频
一粒黑子20 小时前
【实战解析】阿里开源 PageAgent:纯前端 GUI Agent,一行JS让网页支持自然语言操控
前端·javascript·开源
IT枫斗者20 小时前
前端部署后如何判断“页面是不是最新”?一套可落地的版本检测方案(适配 Vite/Vue/React/任意 SPA)
前端·javascript·vue.js·react.js·架构·bug
里欧跑得慢20 小时前
15. Web可访问性最佳实践:让每个用户都能平等访问
前端·css·flutter·web
Beginner x_u21 小时前
链表专题:JS 实现原理与高频算法题总结
javascript·算法·链表
我叫汪枫1 天前
在后台管理系统中,如何递归和选择保留的思路来过滤菜单
开发语言·javascript·node.js·ecmascript
_.Switch1 天前
东方财富股票数据JS逆向:secids字段和AES加密实战
开发语言·前端·javascript·网络·爬虫·python·ecmascript
软件技术NINI1 天前
webkit简介及工作流程
开发语言·前端·javascript·udp·ecmascript·webkit·yarn
Brendan_0011 天前
JavaScript的Stomp.over
开发语言·javascript·ecmascript
念2341 天前
f5 shape分析
开发语言·javascript·ecmascript