H5在WebView上開發小結


背景

來自我司業務方要求,需開發一款APP。但由於時間限制,只能采取套殼app方式,即原生app內嵌webview展示前端頁面。本文主要記述JavaScript與原生app間通信,以及內嵌webview開發時,前端方面可能踩的一些坑。

技術架構

前端:vue+vuex+vue-router+webpack全家桶開發
后端:Node(express框架)簡單轉發接口至java-真后端接口。

js與原生通信

采用jsBridge技術和原生APP通信
android 傳送門 和ios 傳送門,因為兩個平台初始化方式不同,因此在開發過程中,需針對每個平台做對應操作。 具體做法

  1. 按照庫要求,聲明好初始化函數
//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) }) }) 復制代碼
  1. 初始化,得到bride對象。則可調用原生app已定義方法或注冊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對象。需要在callHandler時,對返回值進行封裝處理或統一規定好數據格式。
  • 完整業務代碼文末給出

踩坑

  1. 調用bridge屬性方法registerHandler,callHandler,在回調函數內處理頁面邏輯時,最好避免使用this
  2. vue組件下,在registerHandler,callHandler回調函數內使用vue實例時,無法獲取實例對象。正確做法是在回調函數內調用window對象下方法,再通過該方法去使用vue實例對象。
//vue 組件 mounted(){ window['handleServicePushMessage'] = (res) => { vm.handleServicePushMessage(res) }; bridge.registerHandler("servicePushMessage", function (data, responseCallback) { handleServicePushMessage(data) responseCallback(data) //可傳值到App }) } 復制代碼
  1. 桌面推送消息點擊跳轉至App內詳情情況下,js注冊方法供調用時,可能會引起重復調用的問題。故在方法內需做好重復調用判斷
  2. IOS-12.0版本下,在有輸入框的頁面,輸入時軟鍵盤會頂起webview,當失去焦點時,webview不會自動回彈。需調用APP做處理拉回界面。
//解決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調用app不存在的橋時,無法捕獲異常,頁面不會報錯
6.導航欄顯示問題,由於項目時間緊迫,並且app開發人員不承載太多開發任務,所以路由控制放在前端處理。此時就有導航欄電池時間欄的適配問題。本項目采用頂部下調20PX處理,電池時間欄字體顏色的控制也是通過橋調用來設置;另外iPhone X適配另外處理。
7.當app加載完網頁時,js立即調用原生方法橋時,可能出現原生方法橋未注冊完情況。故特殊情況需延遲調用橋操作。

完整代碼

/*判斷平台*/ 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; }) } 復制代碼
關注下面的標簽,發現更多相似文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM