背景

来自我司业务方要求,需开发一款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. docker启动异常driver not supported

    使用阿里云机器重启docker服务出现错误提示信息 [root@iz2ze5ivwiz2bnwddc20k4z ~]# systemctl restart docker Job for docker. ...

  2. TJOI2010中位数

    中位数 上面是题目链接. 这一题比较水. 思路非常显然. 用mid查询时,只要返回中间值就行了. 主要就是add操作. 我们肯定不能插在末尾,然后用系统快排,这样只有30分. 那么正确的操作应该是二分 ...

  3. 20155312 张竞予 Exp7 网络欺诈防范

    Exp7 网络欺诈防范 目录 基础问题回答 (1)通常在什么场景下容易受到DNS spoof攻击 (2)在日常生活工作中如何防范以上两攻击方法 实验总结与体会 实践过程记录 (1)简单应用SET工具建 ...

  4. 数组,arrayList和List

    数组,arrayList和List (1)数组在C#中是最早出现的.它在内存中是连续的存储的,所以索引速度很快,而且赋值与修改元素也很简单.可以利用偏移地址访问元素,时间复杂度为O(1);可以用折半查 ...

  5. 爬取QQ音乐(讲解爬虫思路)

    一.问题描述: 本次爬取的对象是QQmusic,为自己后面做django音乐网站的开发获取一些资源. 二.问题分析: 由于QQmusic和网易音乐的方式差不多,都是讲歌曲信息放入到播放界面播放,在其他 ...

  6. IOS 视频.图片上传服务器

    //上传视频 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];    manager.requestSerializer. ...

  7. linux (ubuntu) 命令学习笔记

    1, md5sum 输出字符串的MD5值 echo -n 123456 | md5sum //-n表示不打印回车符 2, ubuntu设置dock任务栏鼠标点击效果 16.04: 调整位置:gsett ...

  8. IDEA鼠标显示javadoc的设置

    IDEA鼠标显示javadoc的设置 从Eclipse切换到IDEA中以后最不适用的就是鼠标放到方法上没有注释弹出来给我看,很难受,每次使用ctrl+Q来看总觉得不开心,找了一下设置,设置方法如下: ...

  9. while read line 查找指定日期的文件夹

    #!/bin/bash dir=/root/tmp targetdir=/root/tmp/tmp filenametxt=/tmp/filename.txt commandtxt=/tmp/comm ...

  10. IDEL中easyui使用jstl和el出现传值不显示的问题

    <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding=& ...