flutter开发实战-inappwebview实现flutter与Javascript的交互JSBridge

这篇具有很好参考价值的文章主要介绍了flutter开发实战-inappwebview实现flutter与Javascript的交互JSBridge。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

flutter开发实战-inappwebview实现flutter与Javascript的交互JSBridge

在使用webview中,需要实现flutter与Javascript交互,在使用webview_flutter插件的时候,整理了一下webview与Javascript的交互JSBridge,具体可以查看

https://blog.csdn.net/gloryFlow/article/details/131683122

这里使用inappwebview插件来实现flutter与Javascript的交互JSBridge。

一、什么是JSBridge

JSBridge是一种实现webview与原生端的相互调用的能力。

在比较流行的JSBridge中,主要是通过拦截URL请求来达到 native 端和 webview 端相互通信的效果的。如WebviewJavascriptBridge。

那在inappwebview中有实现javascript交互的方式。在inappwebview中,可以使用JavaScript Handlers,来实现flutter端与javascript的交互。可以查看

https://blog.csdn.net/gloryFlow/article/details/133643136

flutter开发实战-inappwebview实现flutter与Javascript的交互JSBridge,移动开发,flutter开发实战,flutter,flutter,javascript,交互,inappwebview,HTML5,JSBridge,webview

二、修改JSBridge的JS端实现

在WebviewJavascriptBridge中,代码中使用iframe中,拦截url来达到webview与原生交互。那在inappwebview,我们可以直接嵌套使用JavaScript Handlers来实现交互。

我们定义WebviewJSBridgeReady

const String kWebviewJsBridgeReady = '''
    window.onerror = function(err) {
        log('window.onerror: ' + err)
    }

    function setupWebViewJavascriptBridge(callback) {
        if (window.AppJSBridge) {
            return callback(AppJSBridge);
        } else {
            document.addEventListener('AppJSBridgeReady', function() {
                callback(AppJSBridge);
            },false);
        }
    }

    setupWebViewJavascriptBridge(function(bridge) {
        bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
            var responseData = { 'Javascript Says':'Right back atcha!' }
            responseCallback(responseData)
        });

        bridge.registerHandler('JSHandler', function(data, responseCallback) {
            var responseData = { 'Javascript Says':'Right back atcha!' }
            responseCallback(responseData)
        });
    }
''';


修改后的WebviewJsBridge,其中定义了sendMessageQueue、messageHandlers等。其中定义了一个window.AppJSBridge,创建了事件document.createEvent(‘Event’),初始化event.initEvent(‘AppJSBridgeReady’, true, true); 触发对象dispatch触发对象可以是任何元素或其他事件目标document.dispatchEvent(event);

const String kInAppWebViewJavascriptBridge = '''
function preprocessorJS() {
    if (window.AppJSBridge) {
		return;
	}

	if (!window.onerror) {
		window.onerror = function(msg, url, line) {
			console.log("AppJSBridge: ERROR:" + msg + "@" + url + ":" + line);
		}
	}

	// var messagingIframe;
	var sendMessageQueue = [];
	var messageHandlers = {};
	
	var responseCallbacks = {};
	var uniqueId = 1;
	var dispatchMessagesWithTimeoutSafety = true;

	function registerHandler(handlerName, handler) {
		messageHandlers[handlerName] = handler;
	}
	
	function callHandler(handlerName, data, responseCallback) {
		if (arguments.length == 2 && typeof data == 'function') {
			responseCallback = data;
			data = null;
		}
		_doSend({ handlerName:handlerName, data:data }, responseCallback);
	}
    
    function call(handlerName, data, responseCallback) {
        if (arguments.length == 2 && typeof data == 'function') {
            responseCallback = data;
            data = null;
        }
        _doSend({ handlerName:handlerName, data:data }, responseCallback);
    }
    
	function disableJavscriptAlertBoxSafetyTimeout() {
		dispatchMessagesWithTimeoutSafety = false;
	}
	
	function _doSend(message, responseCallback) {
		if (responseCallback) {
			var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
			responseCallbacks[callbackId] = responseCallback;
			message['callbackId'] = callbackId;
		}
		sendMessageQueue.push(message);
		// 通过使用inappwebview的callHandler
		window.flutter_inappwebview.callHandler(message['handlerName'], message);
	}

	function _fetchQueue() {
		var messageQueueString = JSON.stringify(sendMessageQueue);
		sendMessageQueue = [];
		return messageQueueString;
	}

	function _dispatchMessageFromObjC(messageJSON) {
		if (dispatchMessagesWithTimeoutSafety) {
			setTimeout(_doDispatchMessageFromObjC);
		} else {
			 _doDispatchMessageFromObjC();
		}
		
		// 打印log
		_consoleLog("AppJSBridge: messageJSON:" + messageJSON);
		
		function _doDispatchMessageFromObjC() {
			var message = JSON.parse(messageJSON);
			var messageHandler;
			var responseCallback;
			if (message.responseId) {
				responseCallback = responseCallbacks[message.responseId];
				if (!responseCallback) {
					return;
				}
				responseCallback(message.responseData);
				delete responseCallbacks[message.responseId];
			} else {
				if (message.callbackId) {
					var callbackResponseId = message.callbackId;
					responseCallback = function(responseData) {
						_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
					};
				}
				
				var handler = messageHandlers[message.handlerName];
				if (!handler) {
					_consoleLog("AppJSBridge: WARNING: no handler for message from ObjC:", message);
				} else {
					handler(message.data, responseCallback);
				}
			}
		}
	}
	
	function _handleMessageFromObjC(messageJSON) {
        _dispatchMessageFromObjC(messageJSON);
	}


	registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);
	
    window.AppJSBridge = {
      registerHandler: registerHandler,
      callHandler: callHandler,
          call: call,
      disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
      _fetchQueue: _fetchQueue,
      _handleMessageFromObjC: _handleMessageFromObjC,
      _consoleLog: _consoleLog,
    };
    
    	// 打印log
	  function _consoleLog(message) {
	    // 显示来自flutter的回调
	    var logJSON = { 'message':message, 'logType':1 }
		  callHandler("log", JSON.stringify(logJSON));
	  }
    
    window.WeixinJSBridge = window.AppJSBridge;
    
    // 创建事件
    var event = document.createEvent('Event');
    
    // 定义事件名为'build'.
    event.initEvent('AppJSBridgeReady', true, true);

    event.bridge = window.AppJSBridge;
    
    // 触发对象可以是任何元素或其他事件目标
    document.dispatchEvent(event);
}

function preprocessorReadyJs() {
  if (!window.flutter_inappwebview.callHandler) {
      window.flutter_inappwebview.callHandler = function () {
          var _callHandlerID = setTimeout(function () { });
          window.flutter_inappwebview._callHandler(arguments[0], _callHandlerID, JSON.stringify(Array.prototype.slice.call(arguments, 1)));
          return new Promise(function (resolve, reject) {
              window.flutter_inappwebview[_callHandlerID] = resolve;
          });
      };
  }
  
  preprocessorJS();
}

preprocessorReadyJs()

''';


三、在flutter端使用InAppWebViewController实现调用方法的InAppWebJSHandlerManager

在flutter端使用InAppWebViewController实现调用方法,使用InAppWebViewController来调用evaluateJavascript来调用js的方法。我们可以在flutter端实现调用flutter_inappwebview插件的JavaScript Handlers。这里我定义了消息队列,定义了消息的JSMessage,与responseCallbacks回调。

// flutter_inappwebview插件的JavaScript Handlers
class InAppWebJSHandlerManager {
// flutter_inappwebview插件
  InAppWebViewController? inAppWebViewController;

  BuildContext? context;

  // 存储的消息messageHandler
  Map<String, dynamic> messageHandlers = {};

  // 存储的回调callback, responseCallback
  Map<String, dynamic> responseCallbacks = {};

  // 开启的消息队列,发送的消息均会存储到该队列中
  List<JSMessage>? startupMessageQueue = [];

  // 消息的标识
  int _uniqueId = 0;

  JSChannelManager() {
    // TODO: implement JSChannelManager
  }

  void updateController({
    required BuildContext context,
    InAppWebViewController? inAppWebViewController,
  }) {
    this.context = context;
    this.inAppWebViewController = inAppWebViewController;
  }

  // 处理消息队列
  void flutterFlushMessageQueue() async {
    // 获取h5发送的列表
    // 处理H5存的消息队列发送的MessageQueue
    String? messageQueueString;
    if (inAppWebViewController != null) {
      messageQueueString = await inAppWebViewController
          ?.evaluateJavascript(source: webViewJavascriptFetchQueyCommand());
    }

    LoggerManager().debug("flutterFlushMessageQueue:${messageQueueString}");
    flushMessageQueue(messageQueueString);
  }

  // 处理来自H5的消息列表
  void flushMessageQueue(String? messageQueueString) {
    if (!(messageQueueString != null && messageQueueString.isNotEmpty)) {
      return;
    }

    dynamic? aFromH5Messages = jsonDecode(messageQueueString);
    if (aFromH5Messages != null && aFromH5Messages is List) {
      for (dynamic aMsgJson in aFromH5Messages) {
        if (aMsgJson is Map<String, dynamic>) {
          JSMessage jsMessage = JSMessage.fromJson(aMsgJson);
          LoggerManager().debug(
              "flushMessageQueue aFromH5Messages aMsgJson:${aMsgJson} jsMessage:${jsMessage}");

          // 从H5获取或者接收到的消息,如果responseId不为空,则为flutter调用H5方法,H5给flutter的回调
          if (jsMessage.responseId != null &&
              jsMessage.responseId!.isNotEmpty) {
            // 如果responseId不为空,则为flutter调用H5方法,H5给flutter的回调
            ResponseCallback? responseCallback =
            responseCallbacks[jsMessage.responseId];
            if (responseCallback != null) {
              // 处理H5返回给flutter的回调
              responseCallback(jsMessage.responseData);
            }
          } else {
            ResponseCallback? responseCallback;
            // 如果responseId为空时候,则是来自H5发送的flutter的消息
            // 获取H5传过来的标识callbackId
            String? callbackId = jsMessage.callbackId;
            if (callbackId != null && callbackId.isNotEmpty) {
              // 接收到来自H5的消息
              JSMessage aMessage = JSMessage();
              aMessage.copy(aNewMessage: aMessage, aOldMessage: jsMessage);

              responseCallback = (dynamic responseData) {
                // flutter回调给H5
                // 将H5传过来的callbackId作为responseId回调传递给H5
                aMessage.responseId = callbackId;
                aMessage.responseData = responseData;
                _queueMessage(aMessage);
              };
            } else {
              responseCallback = (dynamic responseData) {
                // callbackId为空,不做任何处理
              };
            }

            // 从flutter已经注册Register方法中找出对应的方法
            JSBridgeHandler? jsBridgeHandler =
            messageHandlers[jsMessage.handlerName];
            if (jsBridgeHandler != null) {
              // 在flutter该handlerName的方法已经注册register
              jsBridgeHandler(jsMessage.data, responseCallback);
            } else {
              // 在flutter该handlerName没有注册,则不做任何处理
            }
          }
        }
      }
    }
  }

  // 处理从H5收到的消息
  void _dispatchMessage(JSMessage message) async {
    String messageJSON = jsonEncode(message.toJson());

    messageJSON = messageJSON.replaceAll("\\", "\\\\");
    messageJSON = messageJSON.replaceAll("\"", "\\\"");
    messageJSON = messageJSON.replaceAll("\'", "\\\'");
    messageJSON = messageJSON.replaceAll("\n", "\\n");
    messageJSON = messageJSON.replaceAll("\r", "\\r");
    messageJSON = messageJSON.replaceAll("\f", "\\f");
    messageJSON = messageJSON.replaceAll("\u2028", "\\u2028");
    messageJSON = messageJSON.replaceAll("\u2029", "\\u2029");

    String javascriptCommand =
    webViewJavascriptHandleMessageFromObjCCommand(messageJSON);

    if (inAppWebViewController != null) {
      await inAppWebViewController?.evaluateJavascript(source: javascriptCommand);
    }
  }

  // 注入js
  void injectJavascript(String javascript) async {
    if (inAppWebViewController != null) {
      await inAppWebViewController?.evaluateJavascript(source: javascript);
    }
  }

  // 注入js
  void injectJavascriptReady() async {
    if (inAppWebViewController != null) {
      await inAppWebViewController?.evaluateJavascript(source: '$kWebviewJsBridgeReady');
    }
  }

  // 注入js
  void injectBridgeJavascript() async {
    if (inAppWebViewController != null) {
      await inAppWebViewController?.evaluateJavascript(source: '$kInAppWebViewJavascriptBridge');
    }

    LoggerManager().debug("injectJavascript");

    // 处理flutter发送的消息队列
    if (startupMessageQueue != null && startupMessageQueue!.isNotEmpty) {
      List<JSMessage> tmpList = startupMessageQueue!;
      startupMessageQueue = null;
      for (JSMessage message in tmpList) {
        _dispatchMessage(message);
      }
    }
  }

  // 向H5发送消息
  void _sendData(String handleName,
      {dynamic? data, ResponseCallback? responseCallback}) {
    String callbackId = "flutter_cb_${++_uniqueId}";

    JSMessage jsMessage = JSMessage();
    jsMessage.callbackId = callbackId;
    jsMessage.handlerName = handleName;
    jsMessage.data = data;

    // 将callbackId存储到responseCallbacks中,callbackId会被H5通过responseId返回
    if (responseCallback != null) {
      responseCallbacks[callbackId] = responseCallback;
    }

    _queueMessage(jsMessage);
  }

  // 将发送给H5的消息存到startupMessageQueue中
  void _queueMessage(JSMessage jsMessage) {
    if (startupMessageQueue != null) {
      startupMessageQueue!.add(jsMessage);
    }

    _dispatchMessage(jsMessage);
  }

  // 注入js的command
  String webViewJavascriptCheckCommand() {
    return "typeof window.AppJSBridge == \'object\';";
  }

  String webViewJavascriptFetchQueyCommand() {
    return "AppJSBridge._fetchQueue();";
  }

  String webViewJavascriptHandleMessageFromObjCCommand(String messageJSON) {
    return "AppJSBridge._handleMessageFromObjC('${messageJSON}');";
  }

  // 判断AppJSBridge
  Future<String?> checkJavascriptBridge() async {
    String? result;
    if (inAppWebViewController != null) {
      bool jsBridge = await inAppWebViewController
          ?.evaluateJavascript(source: webViewJavascriptCheckCommand());
      result = (jsBridge?"true":"false");
    }
    LoggerManager().debug("checkJavascriptBridge result:${result}");
    return result;
  }

  /// flutter开放出去的方法,flutter调用H5方法统一使用该callHandler
  /// callHandler
  void callHandler(String handleName,
      {dynamic? data, ResponseCallback? responseCallback}) {
    if (handleName.isNotEmpty) {
      _sendData(handleName, data: data, responseCallback: responseCallback);
    }
  }

  /// flutter注册方法
  /// flutter注册方法,提供给H5调用
  void registerHandler(String handlerName, JSBridgeHandler jsBridgeHandler) {
    if (handlerName.isNotEmpty) {
      messageHandlers[handlerName] = jsBridgeHandler;
    }
  }

  void addJSBridgeHandlers() {
    if (inAppWebViewController != null) {
      messageHandlers.forEach((handlerName, jsBridgeHandler) {
        inAppWebViewController?.addJavaScriptHandler(handlerName: handlerName, callback: (List<dynamic> arguments) {
          LoggerManager().debug("inAppWebViewController.addJavaScriptHandler arguments:${arguments}");
          flutterFlushMessageQueue();
        });
      });
    }
  }

  // 移除注册的方法
  void removeHandler(String handleName) {
    if (handleName.isNotEmpty) {
      messageHandlers.remove(handleName);
    }
  }

  // 重置,将responseCallbacks、startupMessageQueue重置
  void reset() {
    startupMessageQueue = [];
    responseCallbacks = {};
    _uniqueId = 0;
  }
}

InAppWebJSHandlerManager中使用inAppWebViewController?.addJavaScriptHandler来处理接收H5端的JS消息,并且将处理回调返回给H5。
当收到H5消息,flutter根据callbackId将处理后的结果回调给H5。

四、InAppWebJSBridgeRegister:appBridge调用的方法,flutter注册的方法

InAppWebJSBridgeRegister实现处理flutter注册的方法,提供相应的方法,H5端JS可以方法调用。

// appBridge调用的方法,flutter注册的方法
class InAppWebJSBridgeRegister {
  late InAppWebJSHandlerManager _inAppWebJSHandlerManager;

  // 支付
  final ChannelPayPlatform _channelPayPlatform = ChannelPayPlatform();

  // 打开app等
  final ChannelLauncher _channelLauncher = ChannelLauncher();

  // 弹窗
  final ChannelDialog _channelDialog = ChannelDialog();

  // 扫码或者识别二维码
  final ChannelQrScanner _channelQrScanner = ChannelQrScanner();

  InAppWebJSBridgeRegister({required InAppWebJSHandlerManager inAppWebJSHandlerManager}) {
    _inAppWebJSHandlerManager = inAppWebJSHandlerManager;
  }

  // 注册handlers
  void registerHandlers({JSChannelRegisterHandler? jsChannelRegisterHandler}) {
    // 设置标题
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.setTitle,
            (data, responseCallback) {
          if (data != null && data is String) {
            String title = data;
            if (jsChannelRegisterHandler != null) {
              jsChannelRegisterHandler(JSChannelRegisterMethod.setTitle, title);
            }
          }
        });

    // 获取用户昵称
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.getUsername,
            (data, responseCallback) {
          UserModel userModel =
          Provider.of<UserModel>(OneContext().context!, listen: false);
          String userNickName = userModel.userNickName ?? "";
          if (responseCallback != null) {
            responseCallback(userNickName);
          }
        });

    // 获取定位
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.getLoc,
            (data, responseCallback) {
          // TODO 获取定位
        });

    // 获取App名称
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.getAppName,
            (data, responseCallback) {
          PackageInfo.fromPlatform().then((packageInfo) {
            String appName = "${packageInfo.appName}";
            if (responseCallback != null) {
              responseCallback(appName);
            }
          });
        });

    // 获取版本号
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.getVersion,
            (data, responseCallback) {
          PackageInfo.fromPlatform().then((packageInfo) {
            String version = "${packageInfo.buildNumber}";
            String versionCode = version.replaceAll(".", "");
            if (responseCallback != null) {
              responseCallback(versionCode);
            }
          });
        });

    // 获取用户id
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.getUserId,
            (data, responseCallback) {
          UserModel userModel =
          Provider.of<UserModel>(OneContext().context!, listen: false);
          String userId = userModel.userId ?? "";
          if (responseCallback != null) {
            responseCallback(userId);
          }
        });

    // 获取用户登录认证token
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.getAuthorization,
            (data, responseCallback) {
          UserModel userModel =
          Provider.of<UserModel>(OneContext().context!, listen: false);
          String token = userModel.token ?? "";
          if (responseCallback != null) {
            responseCallback(token);
          }
        });

    // 调用支付(微信支付/支付宝支付)原生
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.setPayPlatform,
            (data, responseCallback) {
          _channelPayPlatform.openUniPay(data, responseCallback);
        });

    // 打开扫一扫
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.openScan,
            (data, responseCallback) {
          // 打开扫一扫界面
          _channelQrScanner.openScanner(
              JSChannelRegisterMethod.openScan, data, responseCallback);
        });

    // 打开扫一扫
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.scanQrCode,
            (data, responseCallback) {
          // 打开扫一扫界面
          _channelQrScanner.openScanner(
              JSChannelRegisterMethod.scanQrCode, data, responseCallback);
        });

    // 打系统电话
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.callTelPhone,
            (data, responseCallback) {
          _channelLauncher.openLauncher(
              JSChannelRegisterMethod.callTelPhone, data, responseCallback);
        });

    // 发送短信
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.sendSms,
            (data, responseCallback) {
          _channelLauncher.openLauncher(
              JSChannelRegisterMethod.sendSms, data, responseCallback);
        });

    // 对话框 showDialog
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.showDialog,
            (data, responseCallback) {
          _channelDialog.openShowDialog(data, responseCallback);
        });

    // 底部选择框
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.showCheckBox,
            (data, responseCallback) {
          _channelDialog.openShowSheetBox(data, responseCallback);
        });

    // 保存图片到相册
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.saveImage,
            (data, responseCallback) {
          // 保存图片到相册
          if (data != null && data is String && data.isNotEmpty) {
            FlutterLoadingHud.showLoading(message: "保存中...");
            SaveToAlbumUtil.saveImage(data, onCallback: (bool result, String message) {
              FlutterLoadingHud.dismiss();
              if (result) {
                // 保存成功
                FlutterLoadingHud.showToast(message: message);
              } else {
                // 保存失败
                FlutterLoadingHud.showToast(message: message);
              }
            });
          }
        });

    // 识别二维码
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.detectorQRCode,
            (data, responseCallback) {
          // 识别图片中的二维码
          _channelQrScanner.openScanner(
              JSChannelRegisterMethod.detectorQRCode, data, responseCallback);
        });

    // 打开App
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.openApp,
            (data, responseCallback) {
          _channelLauncher.openLauncher(
              JSChannelRegisterMethod.openApp, data, responseCallback);
        });

    // log
    _inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.log,
            (data, responseCallback) {
          Map<String, dynamic> dataJson = jsonDecode(data);
          int loggerType = dataJson["logType"];
          String message = dataJson["message"];
          if (LoggerMode.debug == loggerType) {
            LoggerManager().debug("registerHandlers log data: ${message}");
          } else if (LoggerMode.verbose == loggerType) {
            LoggerManager().verbose("registerHandlers log data: ${message}");
          } else if (LoggerMode.info == loggerType) {
            LoggerManager().info("registerHandlers log data: ${message}");
          } else if (LoggerMode.warning == loggerType) {
            LoggerManager().warning("registerHandlers log data: ${message}");
          } else if (LoggerMode.error == loggerType) {
            LoggerManager().error("registerHandlers log data: ${message}");
          }
        });


    _inAppWebJSHandlerManager.addJSBridgeHandlers();
  }

  // 处理是否跳转,true可跳转,false不可跳转
  bool shouldOverrideUrlLoading(Uri uri) {
    ///在页面跳转之前调用 isForMainFrame为false,页面不跳转.导致网页内很多链接点击没效果
    String url = uri.toString();
    return webViewNavigationAllowed(url);
  }

  // 处理是否跳转,true可跳转,false不可跳转
  bool webViewNavigationAllowed(String url) {
    LoggerManager().debug('navigationDelegate decode $url');
    String telPrefix = "tel://";
    String smsPrefix = "sms://";
    String appPrefix = "app://";
    if (url.startsWith(telPrefix)) {
      String data = url.substring(telPrefix.length);
      _channelLauncher.openLauncher(
          JSChannelRegisterMethod.callTelPhone, data, null);
      // 不可跳转
      return false;
    }

    if (url.startsWith(smsPrefix)) {
      String data = url.substring(smsPrefix.length);
      _channelLauncher.openLauncher(
          JSChannelRegisterMethod.sendSms, data, null);
      // 不可跳转
      return false;
    }

    if (url.startsWith(appPrefix)) {
      // app://close
      _channelLauncher.openappUrl(url);
      return false;
    }

    if (url == "about:blank") {
      // 空页面进行跳转
      return true;
    }

    // 可跳转
    return true;
  }
}


五、定义JSMessage:H5与flutter交互的消息体

class JSMessage {
  // {handlerName: getSessionID, data: , callbackId: cb_2_1665631238605}
  // handlerName
  String? handlerName;

  // data
  // flutter发送给H5的data,参数
  dynamic? data;

  /// callbackId,
  /// H5发送给flutter的callbackId,
  /// flutter处理后将调用 AppJSBridge._handleMessageFromObjC('%@');
  /// H5从responseCallbacks中根据callbackId找到callback回调方法进行执行
  String? callbackId;

  /// responseId
  /// flutter发送给H5的responseId,
  /// responseId和callbackId是一样的
  /// 如果是H5调用flutter时候,从H5过来的callbackId作为responseId回调给H5
  /// 如果是flutter调用H5,从flutter过来的callbackId作为responseId回调给flutter
  String? responseId;

  /// 回调的数据
  /// 如果是H5调用flutter时候,从flutter传给H5的responseData作为回调数据
  /// 如果是flutter调用H5,从H5传给flutter的responseData作为回调数据
  dynamic? responseData;

  JSMessage();

  JSMessage.fromJson(Map<String, dynamic> json) {
    callbackId = json['callbackId'];
    data = json['data'];
    handlerName = json['handlerName'];
    responseId = json['responseId'];
    responseData = json['responseData'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['callbackId'] = this.callbackId;
    data["data"] = this.data;
    data["handlerName"] = this.handlerName;
    data['responseId'] = this.responseId;
    data['responseData'] = this.responseData;
    return data;
  }

  void copy({required JSMessage aNewMessage, required JSMessage aOldMessage}) {
    aNewMessage.callbackId = aOldMessage.callbackId;
    aNewMessage.data = aOldMessage.data;
    aNewMessage.handlerName = aOldMessage.handlerName;
    aNewMessage.responseId = aOldMessage.responseId;
    aNewMessage.responseData = aOldMessage.responseData;
  }
}



六、封装inwebview中使用InAppWebJSBridgeRegister、InAppWebJSHandlerManager

我们封装inwebview中,使用InAppWebJSBridgeRegister、InAppWebJSHandlerManager。

定义InAppWebJSHandlerManager

// JS与Flutter调用的message Queue
  final InAppWebJSHandlerManager _inAppWebJSHandlerManager =
      InAppWebJSHandlerManager();

定义InAppWebJSBridgeRegister

// flutter注册供H5调用的方法
  late InAppWebJSBridgeRegister _inAppWebJSBridgeRegister;

_inAppWebJSBridgeRegister = InAppWebJSBridgeRegister(
        inAppWebJSHandlerManager: _inAppWebJSHandlerManager);

我们在onWebViewCreated中,可以得到InAppWebViewController,更新_inAppWebJSHandlerManager的controller

_inAppWebJSHandlerManager.updateController(
                context: context,
                inAppWebViewController: webViewController,
              );

在onWebViewCreated回调中注入ready代码

              // 注入jsReady
              _inAppWebJSHandlerManager.injectJavascriptReady();

在onWebViewCreated回调中注册flutter端的方法,

              // register a JavaScript handler with name "myHandlerName"
              _inAppWebJSBridgeRegister.registerHandlers(
                  jsChannelRegisterHandler: (handlerName, data) {
                if (JSChannelRegisterMethod.setTitle == handlerName) {
                  setWebPageTitle(data);
                }
              });

在onLoadStop回调中注入kInAppWebViewJavascriptBridge

            onLoadStop: (controller, url) async {
              // 注入
              _inAppWebJSHandlerManager.injectBridgeJavascript();

              _inAppWebJSHandlerManager.checkJavascriptBridge();

              // 加载完成
              widget.onLoadFinished(url.toString());
            },

完整代码如下

class WebViewInAppScreen extends StatefulWidget {
  const WebViewInAppScreen({
    Key? key,
    required this.url,
    this.onWebProgress,
    this.onWebResourceError,
    required this.onLoadFinished,
    required this.onWebTitleLoaded,
    this.onWebViewCreated,
  }) : super(key: key);

  final String url;
  final Function(int progress)? onWebProgress;
  final Function(String? errorMessage)? onWebResourceError;
  final Function(String? url) onLoadFinished;
  final Function(String? webTitle)? onWebTitleLoaded;
  final Function(InAppWebViewController controller)? onWebViewCreated;

  @override
  State<WebViewInAppScreen> createState() => _WebViewInAppScreenState();
}

class _WebViewInAppScreenState extends State<WebViewInAppScreen> {
  final GlobalKey webViewKey = GlobalKey();

  InAppWebViewController? webViewController;
  InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
    crossPlatform: InAppWebViewOptions(
      useShouldOverrideUrlLoading: true,
      mediaPlaybackRequiresUserGesture: false,
      applicationNameForUserAgent: "app-webview",
    ),
    android: AndroidInAppWebViewOptions(
      useHybridComposition: true,
    ),
    ios: IOSInAppWebViewOptions(
      allowsInlineMediaPlayback: true,
    ),
  );

  // JS与Flutter调用的message Queue
  final InAppWebJSHandlerManager _inAppWebJSHandlerManager =
      InAppWebJSHandlerManager();

  // cookie
  final InAppWebJSCookieConfig _inAppWebViewJSCookieConfig =
      InAppWebJSCookieConfig();

  // flutter注册供H5调用的方法
  late InAppWebJSBridgeRegister _inAppWebJSBridgeRegister;

  bool _isDisposed = false;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _isDisposed = false;

    _inAppWebJSBridgeRegister = InAppWebJSBridgeRegister(
        inAppWebJSHandlerManager: _inAppWebJSHandlerManager);
  }

  @override
  void dispose() {
    // TODO: implement dispose
    _isDisposed = true;
    _inAppWebJSHandlerManager.reset();
    webViewController?.clearCache();
    // _inAppWebViewJSCookieConfig.clear();
    super.dispose();
  }

  // 设置页面标题
  void setWebPageTitle(data) {
    if (widget.onWebTitleLoaded != null) {
      widget.onWebTitleLoaded!(data);
    }
  }

  // flutter调用H5方法
  void callJSMethod() {
    _inAppWebJSHandlerManager.callHandler("JSAPPHandler",
        data: {"id": "a18c9fe0d"}, responseCallback: (dynamic responseData) {
      LoggerManager().debug("callJSMethod responseData:${responseData}");
      FlutterLoadingHud.showToast(message: jsonEncode(responseData));
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Expanded(
          child: InAppWebView(
            key: webViewKey,
            initialUrlRequest: URLRequest(url: Uri.parse(widget.url)),
            initialUserScripts: UnmodifiableListView<UserScript>([
              UserScript(
                  source:
                      "document.cookie='token=${ApiAuth.getToken()};domain='.inice.cn';path=/'",
                  injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START),
            ]),
            initialOptions: options,
            onWebViewCreated: (controller) {
              webViewController = controller;
              _inAppWebViewJSCookieConfig.setCookie(widget.url);
              _inAppWebJSHandlerManager.updateController(
                context: context,
                inAppWebViewController: webViewController,
              );
              // 注入jsReady
              _inAppWebJSHandlerManager.injectJavascriptReady();

              // register a JavaScript handler with name "myHandlerName"
              _inAppWebJSBridgeRegister.registerHandlers(
                  jsChannelRegisterHandler: (handlerName, data) {
                if (JSChannelRegisterMethod.setTitle == handlerName) {
                  setWebPageTitle(data);
                }
              });

              String filePre = "file://";
              if (widget.url.startsWith(filePre)) {
                String html = widget.url.substring(filePre.length);
                webViewController?.loadFile(
                    assetFilePath: 'assets/htmls/${html}');
              } else {
                if (widget.url.startsWith("http://") ||
                    widget.url.startsWith("https://")) {
                  webViewController?.loadUrl(
                      urlRequest: URLRequest(url: Uri.parse(widget.url)));
                }
              }

              if (widget.onWebViewCreated != null) {
                widget.onWebViewCreated!(controller);
              }
            },
            onTitleChanged: (controller, title) {
              if (widget.onWebTitleLoaded != null) {
                widget.onWebTitleLoaded!(title);
              }
            },
            onLoadStart: (controller, url) {},
            androidOnPermissionRequest: (controller, origin, resources) async {
              return PermissionRequestResponse(
                  resources: resources,
                  action: PermissionRequestResponseAction.GRANT);
            },
            shouldOverrideUrlLoading: (controller, navigationAction) async {
              var uri = navigationAction.request.url!;
              bool canNavigate =
                  _inAppWebJSBridgeRegister.shouldOverrideUrlLoading(uri);
              // 允许路由替换
              return canNavigate
                  ? NavigationActionPolicy.ALLOW
                  : NavigationActionPolicy.CANCEL;
            },
            onLoadStop: (controller, url) async {
              // 注入
              _inAppWebJSHandlerManager.injectBridgeJavascript();

              _inAppWebJSHandlerManager.checkJavascriptBridge();

              // 加载完成
              widget.onLoadFinished(url.toString());
            },
            onProgressChanged: (controller, progress) {
              if (widget.onWebProgress != null) {
                widget.onWebProgress!(progress);
              }
            },
            onLoadError: (controller, Uri? url, int code, String message) {
              if (widget.onWebResourceError != null) {
                widget.onWebResourceError!(message);
              }
            },
            onUpdateVisitedHistory: (controller, url, androidIsReload) {
              print("onUpdateVisitedHistory:${url}");
            },
            onConsoleMessage: (controller, consoleMessage) {
              print("consoleMessage:${consoleMessage}");
            },
          ),
        ),
        Container(
          height: ScreenUtil().bottomBarHeight + 50.0,
          color: Colors.white,
          child: Column(
            children: [
              Expanded(
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[
                    ElevatedButton(
                      child: Icon(Icons.arrow_back),
                      onPressed: () {
                        webViewController?.goBack();
                      },
                    ),
                    SizedBox(
                      width: 25.0,
                    ),
                    ElevatedButton(
                      child: Icon(Icons.arrow_forward),
                      onPressed: () {
                        webViewController?.goForward();
                      },
                    ),
                    SizedBox(
                      width: 25.0,
                    ),
                    ElevatedButton(
                      child: Icon(Icons.refresh),
                      onPressed: () {
                        // callJSMethod();
                        webViewController?.reload();
                      },
                    ),
                  ],
                ),
              ),
              Container(
                height: ScreenUtil().bottomBarHeight,
              ),
            ],
          ),
        ),
      ],
    );
  }
}

七、使用inappwebview的page

最后,我们使用inappwebview,使用一个页面打开对应的需要的链接地址,这里使用的本地测试页面

class InAppWebViewPage extends StatefulWidget {
  const InAppWebViewPage({Key? key, this.arguments}) : super(key: key);

  final Object? arguments;

  @override
  State<InAppWebViewPage> createState() => _InAppWebViewPageState();
}

class _InAppWebViewPageState extends State<InAppWebViewPage> {
  String title = "";
  String? url;

  double webProgress = 0.0;

  @override
  void initState() {
    // TODO: implement initState
    if (widget.arguments != null && widget.arguments is Map) {
      Map obj = widget.arguments as Map;
      url = obj["url"];
    }

    LoggerManager().debug("_WebViewPageState arguments:${widget.arguments}");

    LoggerManager().debug("_WebViewPageState url:${url}");

    super.initState();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: WebAppBar(
        toolbarHeight: 44.0,
        backgroundColor: Theme.of(context).primaryColor,
        centerWidget: Text(
          "${title}",
          textAlign: TextAlign.center,
          overflow: TextOverflow.ellipsis,
          style: TextStyle(
            fontSize: 17,
            color: ColorUtil.hexColor(0xffffff),
            fontWeight: FontWeight.w600,
            fontStyle: FontStyle.normal,
            decoration: TextDecoration.none,
          ),
        ),
        leadingWidget: Row(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            IconButton(
              padding: EdgeInsets.all(0.0),
              onPressed: () {
                navigatorBack();
              },
              icon: Icon(
                Icons.close_rounded,
                color: Colors.white,
                size: 30.0,
              ),
            ),
          ],
        ),
        trailingWidget: Row(
          mainAxisAlignment: MainAxisAlignment.end,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            SizedBox(
              width: 28.0,
            ),
            SizedBox(
              width: 28.0,
            ),
          ],
        ),
      ),
      body: Stack(
        children: [
          WebViewInAppScreen(
            url: url ?? "",
            onWebProgress: (int progress) {
              if (mounted) {
                // TODO onWebProgress
                double precent = progress / 100.0;
                if (precent > 1.0) {
                  precent = 1.0;
                }

                if (precent < 0.0) {
                  precent = 0.0;
                }

                setState(() {
                  webProgress = precent;
                  LoggerManager().debug("webProgress:${webProgress}");
                });
              }
            },
            onLoadFinished: (String? url) {
              if (mounted) {
                // TODO onLoadFinished
              }
            },
            onWebTitleLoaded: (String? webTitle) {
              if (mounted) {
                setState(() {
                  title = webTitle ?? "";
                });
              }
            },
          ),
          buildProgressIndicator(context),
        ],
      ),
    );
  }

  Widget buildProgressIndicator(BuildContext context) {
    return (webProgress != 1.0)
        ? LinearProgressIndicator(
            backgroundColor: Colors.transparent,
            valueColor: AlwaysStoppedAnimation(ColorUtil.hexColor(0x3b93ff)),
            value: webProgress,
            minHeight: 2,
          )
        : Container();
  }

  void navigatorBack() {
    NavigatorPageRouter.pop();
  }
}

到此,inappwebview实现flutter与Javascript的交互JSBridge基本内容已经完成了。
查看效果
flutter开发实战-inappwebview实现flutter与Javascript的交互JSBridge,移动开发,flutter开发实战,flutter,flutter,javascript,交互,inappwebview,HTML5,JSBridge,webview
flutter开发实战-inappwebview实现flutter与Javascript的交互JSBridge,移动开发,flutter开发实战,flutter,flutter,javascript,交互,inappwebview,HTML5,JSBridge,webview

八、小结

inappwebview实现flutter与Javascript的交互JSBridge。描述可能不是特别准确,请见谅。

https://blog.csdn.net/gloryFlow/article/details/133667017

学习记录,每天不停进步。文章来源地址https://www.toymoban.com/news/detail-713861.html

到了这里,关于flutter开发实战-inappwebview实现flutter与Javascript的交互JSBridge的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • flutter开发实战-MethodChannel实现flutter与iOS双向通信

    flutter开发实战-MethodChannel实现flutter与iOS双向通信 最近开发中需要iOS与flutter实现通信,这里使用的MethodChannel 如果需要flutter与Android实现双向通信,请看 https://blog.csdn.net/gloryFlow/article/details/132218837 这部分与https://blog.csdn.net/gloryFlow/article/details/132218837中的一致,这里实现一下

    2024年02月13日
    浏览(36)
  • flutter开发实战-事件总线EventBus实现

    flutter开发实战-事件总线EventBus实现 在开发中,经常会需要一个广播机制,用以跨Widget事件通知。 事件总线 实现了订阅者模式,订阅者模式包含发布者和订阅者两种角色,可以通过事件总线来触发事件和监听事件。 实现eventBus 在工程的pubspec.yaml引入库 1.使用event_bus库 创建一

    2024年02月15日
    浏览(31)
  • flutter开发实战-MethodChannel实现flutter与原生Android双向通信

    flutter开发实战-MethodChannel实现flutter与原生Android双向通信 最近开发中需要原生Android与flutter实现通信,这里使用的MethodChannel MethodChannel:用于传递方法调用(method invocation)。 通道的客户端和宿主端通过传递给通道构造函数的通道名称进行连接 一个应用中所使用的所有通道名称

    2024年02月13日
    浏览(27)
  • flutter开发实战-dio文件下载实现

    flutter开发实战-dio文件下载实现 在开发中,需要下载文件,这里使用的是dio dio 是一个强大的 Dart HTTP 请求库,支持全局配置、Restful API、FormData、拦截器、 请求取消、Cookie 管理、文件上传/下载、超时以及自定义适配器等。 在工程中pubspec.yaml引入dio 我们对dio进行封装 文件下

    2024年02月11日
    浏览(36)
  • flutter开发实战-Universal Links配置及flutter微信分享实现

    flutter开发实战-Universal Links配置及flutter微信分享实现 在最近开发中碰到了需要实现微信分享,在iOS端需要配置UniversalLink,在分享使用fluwx插件来实现微信分享功能。 1.1、什么是UniversalLink Universal link 是Apple在iOS9推出的一种能够方便的通过传统HTTPS链接来启动APP的功能,可以使

    2024年01月19日
    浏览(39)
  • flutter开发实战-指纹、面容ID验证插件实现

    flutter开发实战-指纹、面容ID验证插件实现 在iOS开发中,经常出现需要指纹、面容ID验证的功能。 指纹、面容ID是一种基于用生物识别技术,通过扫描用户的面部特征来验证用户身份。 在iOS中实现指纹、面容ID验证功能,步骤如下 2.1 info.plist配置 在info.plist中配置允许访问FAC

    2024年02月13日
    浏览(37)
  • flutter开发实战-RepaintBoundary实现Widget截图功能

    flutter开发实战-RepaintBoundary实现Widget截图功能 在开发中,遇到需要使用截图,像iOS可以截图UIView获取到UIImage,在flutter中可以使用RepaintBoundary实现截图功能 相机拍摄的图片: RepaintBoundary截图后的图片 RepaintBoundary是绘制边界。 如果CustomPaint有子节点,为了避免子节点不必要的

    2024年02月15日
    浏览(34)
  • flutter开发实战-实现首页分类目录入口切换功能

    。 在开发中经常遇到首页的分类入口,如美团的美食团购、打车等入口,左右切换还可以分页更多展示。 在pubspec.yaml引入 由于我这里按照一页8条展示,两行四列展示格式。 当列表list传入的控件时候,一共的页数为 通过列表,一页数量计算每一页应该展示多少个按钮。 一

    2024年02月14日
    浏览(37)
  • flutter开发实战-实现推送功能Push Notification

    flutter开发实战-实现推送功能Push Notification 推送服务现在可以说是所有 App 的标配了,最近在Flutter工程项目上实现推送功能。flutter上实现推送功能需要依赖原生的功能,需要插件实现,这里使用的是极光推送的服务。 效果图如下 在使用极光推送功能时,需要使用的是极光提

    2024年02月16日
    浏览(36)
  • flutter开发实战-flutter_spinkit实现多种风格进度指示器

    flutter开发实战-flutter_spinkit实现多种风格进度指示器 最近开发过程中flutter_spinkit,这个拥有多种种风格加载指示器 引入flutter_spinkit 效果示例 代码如下 flutter开发实战-flutter_spinkit实现多种风格进度指示器. 学习记录,每天不停进步。

    2024年02月14日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包