задний план
По запросу нашей деловой стороны нам необходимо разработать приложение. Однако из-за нехватки времени можно принять только метод приложения оболочки, то есть собственное приложение встраивает веб-представление для отображения интерфейсной страницы. В этой статье в основном описывается взаимодействие между JavaScript и нативными приложениями, а также некоторые подводные камни, на которые может наступить внешний интерфейс при разработке встроенных веб-представлений.
Технологическая архитектура
Передняя часть: разработка семейства vue+vuex+vue-router+webpack
Бэкенд: Node (экспресс-фреймворк) просто перенаправляет интерфейс на бэкэнд-интерфейс java-true.
JS и родное общение
Jsbridge с использованием собственного приложения и технологии связи
android портали iOSпортал, так как методы инициализации двух платформ разные, поэтому в процессе разработки необходимо выполнять соответствующие операции для каждой платформы.
конкретные методы
- В соответствии с требованиями библиотеки объявите функцию инициализации
//android
function connectWebViewJavascriptBridge{
if (window.WebViewJavascriptBridge) {
//do your work here
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady'
, function() {
//do your work here
},
false
);
}
}
//ios
setupWebViewJavascriptBridge(function(bridge) {
/* Initialize your app here */
bridge.registerHandler('JS Echo', function(data, responseCallback) {
console.log("JS Echo called with:", data)
responseCallback(data)
})
bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {
console.log("JS received response:", responseData)
})
})
- Инициализируйте, чтобы получить объект невесты. Затем вы можете вызвать собственные методы, определенные приложением, или зарегистрировать методы js для собственных вызовов.
setupWebViewJavascriptBridge(function(bridge) {
/* Initialize your app here */
bridge.registerHandler('JS Echo', function(data, responseCallback) {
console.log("JS Echo called with:", data)
responseCallback(data) //
})
bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {
console.log("JS received response:", responseData)
})
})
Tips:
- Android и IOS имеют разные методы инициализации, вам нужно оценить платформу, прежде чем звонить. Кроме того, при инициализации Android необходимо ввести некоторые дополнительные методы.
- При вызове метода, определенного Android, возвращаемое значение может быть только строкой. И IOS может быть объектом JSON. Необходимо инкапсулировать возвращаемое значение или единообразно указать формат данных при вызове Handler.
- Полный бизнес-код приведен в конце статьи.
Ступай на яму
- При вызове метода атрибута моста registerHandler, callHandler лучше не использовать его при обработке логики страницы в функции обратного вызова.
- В компоненте vue, когда экземпляр vue используется в функциях обратного вызова registerHandler и callHandler, объект экземпляра не может быть получен. Правильный способ — вызвать метод под объектом окна в функции обратного вызова, а затем использовать объект экземпляра vue через этот метод.
//vue 组件
mounted(){
window['handleServicePushMessage'] = (res) => {
vm.handleServicePushMessage(res)
};
bridge.registerHandler("servicePushMessage", function (data, responseCallback) {
handleServicePushMessage(data)
responseCallback(data) //可传值到App
})
}
- Когда push-сообщение на рабочем столе щелкает, чтобы перейти к сведениям в приложении, когда вызывается метод регистрации js, это может вызвать проблему повторных вызовов. Следовательно, в методе необходимо проводить повторные оценки вызовов.
- В версии IOS-12.0 на странице с полем ввода программная клавиатура будет подталкивать веб-просмотр во время ввода.Когда фокус потерян, веб-просмотр не вернется автоматически. Приложение должно быть вызвано для обработки и возврата интерфейса.
//解决ios 12版本 ui不自动回拉问题
document.addEventListener('focusout', function (event) {
let curTarget = event.target || event.srcElement;
let isInput= ['input', 'textarea'];
//处理页面连续点击都为输入框的情况
let curTargetTagName= curTarget.tagName.toLowerCase();
if (isInput.includes(curTargetTagName)) {
//事件处理
//延迟获取activeElement再进行判断
setTimeout(function () {
let activeEle = document.activeElement;
let activeEleTagName= activeEle.tagName.toLowerCase();
if (!isInput.includes(activeEleTagName)) {
// console.log(document.activeElement.tagName);
//调用app桥拉回webview
performMethod('scrollTotop', null);
}
}, 200);
}
}, true);
5. Когда js вызывает мост, которого нет в приложении, исключение не может быть перехвачено, и страница не сообщит об ошибке
6. Есть проблема с отображением навигационной панели, так как сроки проекта сжаты, а разработчики приложения не несут слишком много задач по разработке, управление маршрутизацией вынесено на фронтенд-обработку. На данный момент есть проблема с адаптацией полосы времени батареи в панели навигации. В этом проекте верхняя часть опущена на 20PX, а управление цветом шрифта шкалы времени работы батареи также задается через бридж-вызов, кроме того, адаптация iPhone X обрабатывается отдельно.
7. Когда приложение загружает веб-страницу, а js немедленно вызывает мост нативного метода, мост нативного метода может быть не зарегистрирован. Поэтому в особых случаях необходимо отложить работу call bridge.
полный код
/*判断平台*/
function (window) {
window.device = {};
var ua = navigator.userAgent;
var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/);
var ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/);
device.ios = device.android = device.iphone = device.ipad = device.androidChrome = false;
if (android) {
device.os = 'android';
device.osVersion = android[2];
device.android = true;
device.androidChrome = ua.toLowerCase().indexOf('chrome') >= 0
}
if (ipad || iphone || ipod) {
device.os = 'ios';
device.ios = true
}
}(window)
/*引入Android需要的初始化,IOS不执行,如执行IOS端桥调用会受影响*/
(function () {
if (window.WebViewJavascriptBridge || device.ios) {
return false;
}
var messagingIframe;
var sendMessageQueue = [];
var receiveMessageQueue = [];
var messageHandlers = {};
var CUSTOM_PROTOCOL_SCHEME = 'yy';
var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/';
var responseCallbacks = {};
var uniqueId = 1;
function _createQueueReadyIframe(doc) {
messagingIframe = doc.createElement('iframe');
messagingIframe.style.display = 'none';
doc.documentElement.appendChild(messagingIframe);
}
/*set default messageHandler*/
function init(messageHandler) {
if (WebViewJavascriptBridge._messageHandler) {
throw new Error('WebViewJavascriptBridge.init called twice');
}
WebViewJavascriptBridge._messageHandler = messageHandler;
var receivedMessages = receiveMessageQueue;
receiveMessageQueue = null;
for (var i = 0; i < receivedMessages.length; i++) {
_dispatchMessageFromNative(receivedMessages[i]);
}
}
function send(data, responseCallback) {
_doSend({data: data}, responseCallback);
}
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
function callHandler(handlerName, data, responseCallback) {
_doSend({handlerName: handlerName, data: data}, responseCallback);
}
/*sendMessage add message, 触发native处理 sendMessage*/
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message.callbackId = callbackId;
}
sendMessageQueue.push(message);
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
/* 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容*/
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
/*android can't read directly the return data, so we can reload iframe src to communicate with java*/
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
}
/*提供给native使用,*/
function _dispatchMessageFromNative(messageJSON) {
setTimeout(function () {
var message = JSON.parse(messageJSON);
var responseCallback;
/*java call finished, now need to call js callback function*/
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({responseId: callbackResponseId, responseData: responseData});
};
}
var handler = WebViewJavascriptBridge._messageHandler;
if (message.handlerName) {
handler = messageHandlers[message.handlerName];
}
/*查找指定handler*/
try {
handler(message.data, responseCallback);
} catch (exception) {
if (typeof console != 'undefined') {
console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);
}
}
}
});
}
/*提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以*/
function _handleMessageFromNative(messageJSON) {
if (receiveMessageQueue && receiveMessageQueue.length > 0) {
receiveMessageQueue.push(messageJSON);
} else {
_dispatchMessageFromNative(messageJSON);
}
}
var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
init: init,
send: send,
registerHandler: registerHandler,
callHandler: callHandler,
_fetchQueue: _fetchQueue,
_handleMessageFromNative: _handleMessageFromNative
};
var doc = document;
_createQueueReadyIframe(doc);
var readyEvent = doc.createEvent('Events');
readyEvent.initEvent('WebViewJavascriptBridgeReady');
readyEvent.bridge = WebViewJavascriptBridge;
doc.dispatchEvent(readyEvent);
})();
/*Android端初始化函数*/
function connectWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
callback(WebViewJavascriptBridge)
} else {
document.addEventListener('WebViewJavascriptBridgeReady', function () {
callback(WebViewJavascriptBridge)
}, false);
}
}
/*IOS端初始化函数*/
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
return callback(WebViewJavascriptBridge)
} else {
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback)
}
window.WVJBCallbacks = [callback];
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function () {
document.documentElement.removeChild(WVJBIframe)
}, 0)
}
if(device.ios){
setupWebViewJavascriptBridge(function(bridge){
/*挂载上全局对象*/
window.BRIDGE= brige;
})
}
if(device.android){
connectWebViewJavascriptBridge(function(bridge){
/*挂载上全局对象*/
window.BRIDGE= brige;
})
}