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

相关推荐
扶我起来还能学_6 小时前
Flutter 轮播图封装
flutter
2401_878454536 小时前
浏览器工作原理
前端·javascript
by__csdn8 小时前
Vue3 setup()函数终极攻略:从入门到精通
开发语言·前端·javascript·vue.js·性能优化·typescript·ecmascript
结局无敌8 小时前
Flutter工程化实战:从单人开发到团队协作的规范与效率指南
flutter
遝靑8 小时前
Flutter 状态管理进阶:从 Provider 到 Riverpod 2.0(原理 + 实战 + 性能优化)
flutter
Luna-player9 小时前
在前端中,<a> 标签的 href=“javascript:;“ 这个是什么意思
开发语言·前端·javascript
lionliu05199 小时前
js的扩展运算符的理解
前端·javascript·vue.js
小草cys9 小时前
项目7-七彩天气app任务7.4.2“关于”弹窗
开发语言·前端·javascript
结局无敌10 小时前
Flutter状态管理实战:从新手到进阶的选型与落地指南
flutter
hh.h.11 小时前
开源鸿蒙生态下Flutter的发展前景分析
flutter·开源·harmonyos