谈一谈前端多容器(多webview平台)处理方案
文中是我个人的一些开发经验,希望对各位有用,也希望各位多多支持讨论,指出文中不足以及提出您的一些建议。
双容器
得益于近几年移动端的发展,前端早已今非昔比,从大型框架来说angularJS、react、VueJS都有其应用场景,从工程化来说各种配套构建工具也纷纷出世,而从前端复杂度来说,最近几年的前端代码难度着实提升不少,从模块化的必须,到MVC的必要、再到组件化编程,一种分而治之的思想逐渐侵入前端领域,而这种种迹象均表明一个问题,前端代码现在不好写了!!!
抛开近几年前端交互加重而导致的难度,我们今天主要探讨下前端跨平台一块的痛点,也就是Hybrid多容器解决方案。
Hybrid是一种混合开发模式,最简单的理解就是,Native会提供一个webview容器(确实不明白可以理解为iframe),然后在里面加载你的H5站点。
在大约三年前,当时Hybrid平台还比较少,如果一个公司前端团队比较强的话可以做到一套代码三端运行就很不错了,也就是一个H5页面同时运行在:
① 浏览器
② 公司IOS APP Webview容器
③ APP Andriod Webview容器
再这里有个和简单iframe不同的是,处于Native中的话,那么很多H5的表现便不太一样了,比如header一部分的UI是Native的,比如获取定位信息直接由Native给H5,在这里面会有些差异化处理,一般来说只有保持应用层API一致,底层稍作修改即可;但也有一些特殊场景需要判断,比如,一个按钮的回调在H5站点的处理和处于Native中不一样,这个时候可能就需要if else判断处理了。
总的来说,双容器时代持续了一阵子,而因为条件仍然比较单一,无非只是判断H5站点或者自身APP容器,所以问题也就不大。
多容器
量变到一定阶段便不再一样了,简单从携程来说,Hybrid的频道从最初的一个发展到现在APP中80%都是Hybrid频道,携程APP本身有一套完整的Hybrid交互规范,俨然已经不再简单是个APP了,而是一个Hybrid平台,开发规范一旦制定,一旦进入工厂化开发就很难更改了,除了携程各个业务团队依赖这个APP外,还有很多携程子公司乃至第三方公司依赖这个APP,那么这个时候底层若是不稳定,那么导致的问题将是连锁的、不可控的。
这种平台化的APP产品远不止携程一家,已知的就有:
① 微信APP平台
② 淘宝APP平台
③ 手机百度APP平台
④ 糯米平台
⑤ 手机QQ平台
......
国内这些“平台”都有各自问题,不论是微信一些版本不支持flex、手机百度IOS、Andriod Webview容器各种不一致,还是糯米Native默认后退不处理导致假死,都可以看出为了抢占市场,各个团队走的太急,考虑的应用场景过少,推出产品后后宣传网站写的漂亮,API看似丰富,但是光鲜的只是表面,真正形成平台后,各个业务方接入会形成各种小概率场景,而Native发版是无力的,Native不动就只能业务开发代码适配,这个时候受苦的总是各个接入方,而导致骂声一片。
各个平台不稳定、考虑场景太少其实也无可厚非,毕竟Hybrid才火不到几年,各个公司真正的经验场景又很难被其它公司吸收,所以这种现象还得持续一段时间......
当然,APP底层的问题不是我们今天思考的重点,我们还是回到前端应用层。
多容器与前端
上述平台产品虽然有各自的问题,但是其流量优势是无可比拟的!所以很多业务方、第三方公司都会接入,对于前端来说难度便增加了不少,以百度为例:
最初是前端代码运行在浏览器即可,而现在一套前端代码却需要运行在:
① 浏览器
② 自身APP
③ 百度地图APP
④ 手机百度APP
⑤ 糯米APP
而各个APP平台的Hybrid交互又完全不一致,更有甚者后期还需要微信APP、手机QQ等Hybrid平台,那么就简单一个按钮的交互都会令人头疼的!因为我们的代码中可能会出现这种东东:
if (shoujibaidu) {
//手机百度逻辑 } else if (baiduditu) {
//百度地图逻辑 } else if (nuomi) {
//糯米逻辑
}
//......其它平台逻辑
这种代码十分令人头疼,所以我们一般会封装一个方法在底层,哪个平台有差异就做特殊处理:
hybridCallback({
//默认回调
callback: function() {
},
//手机百度回调
shoubaicallback: function () {
},
//......
});
这个方法就是用于处理Hybrid差异而生,只有处于某一个环境,才会执行其中的回调,这其实只是一个语法糖,将判断的逻辑封装了,所以这个方案依旧很烂,如果哪天你要多一个容器或者少一个容器,整个站点的代码要如何处理呢?如果代码量超过万行,这个代码可不好处理!
更好的解决方案是抽离共性,是继承,一般来说,Hybrid还是有一个很大的特点:主要逻辑与H5一致,一些差异往往是显示什么,不显示什么(比如糯米中不显示H5推荐下载APP的广告),更多的是一些点击回调的响应,于是我们找到了更好的方案:
多容器解决方案
容器判断
解决多容器的第一步是容器判断,一般来说,不同的Webview容器会有不同的userAgent:
//微信中UA为:
Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Mobile/11D257 MicroMessenger/6.1.5 NetType/WIFI //浏览器中为:
Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53 //糯米
Mozilla/5.0 (iPhone; CPU iPhone OS 9_2_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13D15 BDNuomiAppIOS
手机百度也会包含关键字:bdbox_x.x(x.x一般是版本号),根据ua我们可以知道当前处于什么环境(ios还是Andriod)与什么平台。
前端实现
如果是页面片的开发模式,一个页面往往会有一个js文件,做的好的团队这个js文件会是一个类,通过requireJS可以轻易拿到该文件,我们这里不做无用功,直接在之前代码的基础上做,有疑问的朋友请移步该文章:
在上文中,我们将一个个页面以组件化的方式打散了,我们这里新增一个index页面,并且新增一个按钮,点击按钮弹出一个提示:
define([
'AbstractView',
'text!IndexPath/tpl.layout.html'
], function (
AbstractView,
layoutHtml
) {
return _.inherit(AbstractView, {
propertys: function ($super) {
$super();
this.template = layoutHtml;
this.events = {
'click .js_clickme': 'clickAction'
};
}, clickAction: function () {
this.showMessage('显示消息');
}, initHeader: function (name) {
var title = '多Webview容器';
this.header.set({
view: this,
title: title,
back: function () {
console.log('回退');
}
});
}
});
});
propertys: function ($super) {
$super();
this.template = layoutHtml;
this.events = {
'click .js_clickme': 'clickAction'
};
}, clickAction: function () {
this.showMessage('显示消息');
},
首先我们看看这个回调,假如我们需要做到在糯米容器中使用Native的弹出提示的话,代码便有所不同了:
我们使用的应该是:
/**
* 使用BNJS之前,必须声明如下BNJSReady函数,确保BNJS相关属性信息及页面加载准备就绪
* BNJSReady直接复制使用,请勿改动
*/
var BNJSReady = function (readyCallback) {
if(readyCallback && typeof readyCallback == 'function'){
if(window.BNJS && typeof window.BNJS == 'object' && BNJS._isAllReady){
readyCallback();
}else{
document.addEventListener('BNJSReady', function() {
readyCallback();
}, false)
}
}
}; BNJSReady(function(){ // 显示确定和取消按钮
BNJS.ui.dialog.show({
title: '测试Dialog',
message: '我是测试Dialog~~~~',
ok: '确定',
cancel: '取消',
onConfirm: function() {
BNJS.ui.toast.show('您刚刚点击了确定按钮');
},
onCancel: function() {
BNJS.ui.toast.show('您刚刚点击了取消按钮');
}
}); // 仅显示'ok'按钮
BNJS.ui.dialog.show({
title: '测试Dialog',
message: '我是测试Dialog~~~~',
ok: 'ok',
onConfirm: function() {
BNJS.ui.toast.show('您刚刚点击了ok按钮');
}
}); });
// 仅显示'ok'按钮
BNJS.ui.dialog.show({
title: '测试Dialog',
message: '我是测试Dialog~~~~',
ok: 'ok',
onConfirm: function() {
BNJS.ui.toast.show('您刚刚点击了ok按钮');
}
});
于是我们在index目录中新增了一个nuomi.index.js的文件,继承自index.js,并且在入口文件main_webviews(原main.js文件)中做更改:
define([
'IndexPath/index'
], function (
IndexView
) {
return _.inherit(IndexView, { clickAction: function () {
BNJS.ui.dialog.show({
title: '测试Dialog',
message: '我是测试Dialog~~~~',
ok: 'ok',
onConfirm: function () {
BNJS.ui.toast.show('您刚刚点击了ok按钮');
}
});
} });
});
如此,在一般浏览器中点击按钮便是H5的UI组件,在糯米中便是使用的糯米组件了,如果哪天不需要糯米这个平台将nuomi.js删除即可:
可以看到,按钮的点击已经不一样了,当然还有很多不足,比如糯米中header部分便没有做处理。
header组件
header这种组件与上述问题又不一致,这种不一致主要体现在两个方面:
① 由于底层实现问题,做不到一致
比如手机百度就不支持返回按钮定制,就连最简单的title改变都是直接监听的document.title的变化,并且Andriod还有BUG,像这种底层实现直接就抹杀的基本没法,一般来说就是把原来的header换个方式显示在页面中,可以是弧形按钮,可以是其它方式。
② header是系统级别的操作,不应该由用户控制
如同该文中对header组件的处理:浅谈Hybrid技术的设计与实现,像header这一类组件,这类组件必须满足在H5站点与Hybrid中API使用一致,而底层实现各异,与之前不同的是,这里的header组件要考虑的可不止2个平台那种问题了,他可能是这样的:
ui.eader //H5站点使用
nuomi.ui.header //糯米使用
xx.ui.header //......
我们这里将场景变小,暂时只考虑糯米与H5的实现,于是会在底层多出一个header的实现:
我这里工作做的多一些,考虑了微信时候的场景,但是这里业务代码暂时只考虑糯米,对应糯米的文档:
define([], function () {
'use strict'; return _.inherit({ propertys: function () {
}, //全部更新
set: function (opts) {
if (!opts) return;
var i, len, item; var scope = opts.view || this; //处理返回逻辑
if (opts.back && typeof opts.back == 'function') {
BNJS.page.onBtnBackClick({
callback: $.proxy(opts.back, scope)
});
} else { BNJS.page.onBtnBackClick({
callback: function () {
if (history.length > 0)
history.back();
else
BNJS.page.back();
}
});
} //处理title
if (typeof opts.title == 'string') {
BNJS.ui.title.setTitle(opts.title);
} //删除右上角所有按钮【1.3】
//每次都会清理右边所有的按钮
BNJS.ui.title.removeBtnAll(); //处理右边按钮
if (typeof opts.right == 'object' && opts.right.length) {
for (i = 0, len = opts.right.length; i < len; i++) {
item = opts.right[i];
BNJS.ui.title.addActionButton({
tag: _.uniqueId(),
text: item.value,
callback: $.proxy(item.callback, scope)
});
}
}
}, show: function () { }, hide: function () { }, //只更新title
update: function (title) { }, initialize: function () {
//隐藏H5头
$('#headerview').hide();
this.propertys();
} }); });
代码实现很简单,只要保持与H5使用API一致即可,这个时候再简单改下入口文件,便能适配了。
PS:注意,这里的适配只是简单实现,考虑多场景的话不能这样写代码!!!
于是我们在糯米中便能很好的运行了
结语
代码地址
https://github.com/yexiaochai/mvc
demo地址
http://yexiaochai.github.io/mvc/webapp/bus/index.html
测试糯米时请扫描第二个二维码:
这里抛出了前端多Webview容器会遇到的一些问题,并提出了一个解决思路,后续可能会有更加完整解决方案与demo出来,希望对各位有用,若是有已经涉及到这块业务的朋友可以私下交流下。
谈一谈前端多容器(多webview平台)处理方案的更多相关文章
- 转:浅谈CSS在前端优化中一些值得注意的关键点
前端优化工作中要考虑的元素多种多样,而合理地使用CSS脚本可以在很大程度上优化页面的加载性能,以下我们就来浅谈CSS在前端优化中一些值得注意的关键点: 当谈到Web的“高性能”时,很多人想到的是页面加 ...
- 从一张图开始,谈一谈.NET Core和前后端技术的演进之路
从一张图开始,谈一谈.NET Core和前后端技术的演进之路 邹溪源,李文强,来自长沙.NET技术社区 一张图 2019年3月10日,在长沙.NET 技术社区组织的技术沙龙<.NET Core和 ...
- 谈一谈Java8的函数式编程(二) --Java8中的流
流与集合 众所周知,日常开发与操作中涉及到集合的操作相当频繁,而java中对于集合的操作又是相当麻烦.这里你可能就有疑问了,我感觉平常开发的时候操作集合时不麻烦呀?那下面我们从一个例子说起. 计 ...
- 谈一谈泛型(Generic)
谈一谈泛型 首先,泛型是C#2出现的.这也是C#2一个重要的新特性.泛型的好处之一就是在编译时执行更多的检查. 泛型类型和类型参数 泛型的两种形式:泛型类型( 包括类.接口.委托和结构 没有泛型枚 ...
- 谈一谈Elasticsearch的集群部署
Elasticsearch天生就支持分布式部署,通过集群部署可以提高系统的可用性.本文重点谈一谈Elasticsearch的集群节点相关问题,搞清楚这些是进行Elasticsearch集群部署和拓 ...
- 谈一谈iOS事件的产生和传递
谈一谈iOS事件的产生和传递 1.事件的产生 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中. UIApplication会从事件队列中取出最前面的事件,并将事件 ...
- 谈一谈对MySQL InnoDB的认识及数据库事物处理的隔离级别
介绍: InnoDB引擎是MySQL数据库的一个重要的存储引擎,和其他存储引擎相比,InnoDB引擎的优点是支持兼容ACID的事务(类似于PostgreSQL),以及参数完整性(有外键)等.现在Inn ...
- 谈一谈APP版本号问题
如题:谈一谈APP版本号问题 为什么要谈这个问题,周五晚上11~12点,被微信点名,说APP有错,无效的版本号,商城无法下单.我正在准备收拾东西,周末回老家,结果看到这样问题,菊花一紧.我擦,我刚加的 ...
- 谈一谈深度学习之semantic Segmentation
上一次发博客已经是9月份的事了....这段时间公司的事实在是多,有写博客的时间都拿去看paper了..正好春节回来写点东西,也正好对这段时间做一个总结. 首先当然还是好好说点这段时间的主要工作:语义分 ...
- 蓝的成长记——追逐DBA(5):不谈技术谈业务,恼人的应用系统
***************************************声明*************************************** 个人在oracle路上的成长记录,当中 ...
随机推荐
- CentOS7之按时间段截取指定的Tomcat日志到指定文件的方法
CentOS7之按时间段截取指定的Tomcat日志到指定文件的方法 sed -n '/2016-11-02 15:00:/,/2016-11-02 15:05:/p' catalina.out > ...
- SqlServer简单数据分页
手边开发的后端项目一直以来都用的.NET MVC框架,访问数据库使用其自带的EF CodeFirst模式,写存储过程的能力都快退化了 闲来无事,自己写了条分页存储过程,网上类似的文章多的是,这里只列了 ...
- Linux网络属性配置
目录 IP地址分类 如何将Linux主机接入到网络中 网络接口的命名方式 ifcfg系列命令 如何配置主机名 如何配置DNS服务器指向 iproute2系列命令 Linux管理网络服务 永久生效配置路 ...
- OpenGL ES: Array Texture初体验
[TOC] Array Texture这个东西的意思是,一个纹理对象,可以存储不止一张图片信息,就是说是是一个数组,每个元素都是一张图片.这样免了频繁地去切换当前需要bind的纹理,而且可以节省系统资 ...
- FineReport如何用JDBC连接阿里云ADS数据库
在使用FineReport连接阿里云的ADS(AnalyticDB)数据库,很多时候在测试连接时就失败了.此时,该如何连接ADS数据库呢? 我们只需要手动将连接ads数据库需要使用到的jar放置到%F ...
- 端盘子的服务生到月薪一万五的IT精英,你能相信吗
一直以来,我都觉得自己不是一个有故事的人. 以前的我,是个乖宝宝,对父母言听计从,特别内向,甚至一度感觉到自卑.不上学之后,我干过送货员,去工地除泥搬砖,当过油漆工,去过工厂,还去饭店当过端盘子的服务 ...
- 技术笔记:Indy控件发送邮件
工作中有个需求需要发送邮件,因为使用的delphi6,所以自然就选择了indy组件,想想这事挺简单的.实现的过程倒是简单,看着Indy的demo很快就完了,毕竟也不是很复杂的功能. 功能要求: 1.压 ...
- 如何让我们的PHP在Jexus中跑起来
最近一段时间,经常看到不少的朋友在问,应该怎么设置才能够让Jexus支持PHP.其实,Jexus在很早之前就已经是可以支持PHP,像Apache或Nginx一样充当PHP的Web服务器的.不过由于没有 ...
- Linux学习日记-MVC的部署(三)
一.Mvc与wcf 相对WCF的部署MVC还是有点麻烦,我们要考虑哪些dll是不需要的,哪些是要拷贝到本地的. 而WCF因为有些配置文件不支持,我们只需要在配置wcf时不使用配置文件而直接使用代码就行 ...
- ABP源码分析三十一:ABP.AutoMapper
这个模块封装了Automapper,使其更易于使用. 下图描述了改模块涉及的所有类之间的关系. AutoMapAttribute,AutoMapFromAttribute和AutoMapToAttri ...