背景

来自我司业务方要求,需开发一款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;
})
} 复制代码
关注下面的标签,发现更多相似文章

H5在WebView上开发小结的更多相关文章

  1. iOS之H5和Native混合开发

    今天需要用到一个H5和Native 混合开发的项目,简单的写一点入门的东西,很简答: 先介绍一下简单的配置步骤: 1.新建项目:SB拖一个UIWebView 按住Ctrl 拖线delegate 设置为 ...

  2. 关于Android WebView上传文件的解决方案

    我们在开发需求的时候,难免会接入一下第三方的H5页面,有些H5页面是具有上传照片的功能,Android 中的 WebView是不能直接打开文件选择弹框的 接下来我讲简单提供一下解决方案,先说一下思路 ...

  3. 《H5+移动应用实战开发》已出版

    <H5+移动应用实战开发>终于出版了,最近在忙着Vue和Webpack相关的前端书籍写稿.本书面向的读者为:从后端转前端,或零基础开始学习移动端开发的人.前后端完全分离的开发方式越来越成为 ...

  4. pdfjs viewer 开发小结

    此文已由作者吴家联授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 1. pdfjs库简介 PDF.js 是由Mozilla 主导推出的可以将PDF文件转换为H5页面进行展示的 ...

  5. vue开发小结(下)

    前言 继前几天总结了vue开发小结(上)后,发现还有很多的点没有能列举出来,于是还是打算新建一个下篇,再补充一些vue开发中需要注意的细节,确实还是都是细节的问题,我只是在这里强调下,希望对大家有帮助 ...

  6. h5行情k线开发

    前言         由于公司项目需要,要做港股行情的H5版本,经过分析需求,大致有两块难点: 一是行情的推送接收,二是行情K线的生成及相关操作.本文章主要分析行情K线的相关实现,由于我们前端团队之前 ...

  7. Android 即时通讯开发小结(二)

    <Android 即时通讯开发小结>基于IM Andriod 开发的各种常见问题,结合网易云信即时通讯技术的实践,对IM 开发做一个全面的总结. 相关推荐阅读:. Android 即时通讯 ...

  8. Android 即时通讯开发小结(一)

    <Android 即时通讯开发小结>基于IM Andriod 开发的各种常见问题,结合网易云信即时通讯技术的实践,对IM 开发做一个全面的总结. 相关推荐阅读:. Android 即时通讯 ...

  9. Appium如何查看webview上元素

    现在大部分app都是混合式的native+webview,对应native上的元素通过uiautomatorviewer很容易定位到,webview上的元素就无法识别了: 那么如何定位webview上 ...

随机推荐

  1. GUI学习之九——QLineEdit的学习总结

    我们在前面学习了各种按钮控件,从这一章开始就是各种输入控件的学习. 首先要用的就是QLineEdit——单行编辑器, 一描述 QLineEdit是一个单行文本编辑器,允许用户输入和编辑单行纯文本.自带 ...

  2. RT-thread内核对象--事件集

    rt-thread 线程的同步:线程同步是指多个线程通过特定的机制(如互斥量,事件对象,临界区)来控制线程之间的执行顺序 1.事件集:(可以实现一对多,多对多的同步)   RT-Thread 定义的事 ...

  3. Vmware 无法启动虚拟机 -VMware Workstation and Device/Credential Guard are not compatible.

    因为在学习Linux,起初尝试用Hyper-V安装Linux进行学习,之后为了方便和老师的设置一样,所以改装了VMware,所有初始设置先好后发现,虚机机无法启用. VMware也提示不支持CPU虚拟 ...

  4. 关于Eclipse的一些简单设置

    1.加入eclipse没有编辑的文件 例如:想用html类型打开*.jetx文件,在window-preferences-General-Content Types-Text-Html加入*.jetx ...

  5. 在 ASP.NET Core 中发送邮件遇到的坑_学习笔记

    功能需求 因为项目需要有个忘记密码验证邮箱再重新修改密码的功能,然后我选用了很简单的一个方案,通过验证登录用户的邮箱然后发送邮件,通过这个邮件发送的链接地址来最后实现密码修改的小功能. 项目环境及实现 ...

  6. 渗透测试的理论部分3——ISSAF的详细描述

    ISSAF即信息系统安全评估框架(Information Systems Security Assessment Framework)是另外一种开放源代码的安全性测试和安全分析框架.为了解决安全评估工 ...

  7. selenium之复选框操作

    HTML源码: <!DOCTYPE html> <div lang="en"></div></div> <head> & ...

  8. python3中报错:TypeError: 'range' object doesn't support item deletion

    1.源代码 以下代码执行时会报  range' object does not support item assignment 的错误,问题出现在第17行的runge(10): import unit ...

  9. Win7 VS2017编译magnum及例子

    magnum是一个开源的图形中间件 Lightweight and modular C++11/C++14 graphics middleware for games and data visuali ...

  10. java 项目相关 学习记录

    一位资深程序员大牛给予Java初学者的学习路线建议  [任何时期都可以好好看看] https://www.imooc.com/article/8993 https://www.jianshu.com/ ...