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

二、修改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基本内容已经完成了。

查看效果

八、小结

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

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

学习记录,每天不停进步。

相关推荐
bug丸29 分钟前
v8引擎垃圾回收
前端·javascript·垃圾回收
&活在当下&31 分钟前
ref 和 reactive 的用法和区别
前端·javascript·vue.js
web Rookie38 分钟前
React 高阶组件(HOC)
前端·javascript·react.js
云白冰1 小时前
hiprint结合vue2项目实现静默打印详细使用步骤
前端·javascript·vue.js
匹马夕阳2 小时前
详细对比JS中XMLHttpRequest和fetch的使用
开发语言·javascript·ecmascript
长风清留扬2 小时前
小程序开发实战项目:构建简易待办事项列表
javascript·css·微信小程序·小程序·apache
程序员_三木2 小时前
从 0 到 1 实现鼠标联动粒子动画
javascript·计算机外设·webgl·three.js
点点滴滴的记录3 小时前
Java的CompletableFuture实现原理
java·开发语言·javascript
web Rookie3 小时前
React 中 createContext 和 useContext 的深度应用与优化实战
前端·javascript·react.js
男孩123 小时前
react高阶组件及hooks
前端·javascript·react.js