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
学习记录,每天不停进步。