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

相关推荐
go_caipu17 分钟前
Vben Admin管理系统集成qiankun微服务(二)
前端·javascript
幻云201020 分钟前
Next.js指南:从入门到精通
开发语言·javascript·人工智能·python·架构
唐叔在学习20 分钟前
insertAdjacentHTML踩坑实录:AI没搞定的问题,我给搞定啦
前端·javascript·html
小王和八蛋36 分钟前
前端存储与离线应用实战:Cookie、LocalStorage、PWA 及 Service Worker 核心知识点
前端·javascript
小雨下雨的雨1 小时前
Flutter 框架跨平台鸿蒙开发 —— Flex 控件之响应式弹性布局
flutter·ui·华为·harmonyos·鸿蒙系统
军军君011 小时前
Three.js基础功能学习七:加载器与管理器
开发语言·前端·javascript·学习·3d·threejs·三维
cn_mengbei1 小时前
Flutter for OpenHarmony 实战:CheckboxListTile 复选框列表项详解
flutter
哈__1 小时前
React Native 鸿蒙开发:内置 Share 模块实现无配置社交分享
javascript·react native·react.js
cn_mengbei1 小时前
Flutter for OpenHarmony 实战:Switch 开关按钮详解
flutter
奋斗的小青年!!1 小时前
OpenHarmony Flutter实战:打造高性能订单确认流程步骤条
flutter·harmonyos·鸿蒙