谈一谈前端多容器(多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路上的成长记录,当中 ...
随机推荐
- Linux基础介绍【第五篇】
linux权限位 Linux文件或目录的权限位是由9个权限位来控制,每三位为一组,它们分别是文件属主权限.属组权限.其他用户权限. r:read可读权限,对应数字4: w:write可写权限,对应数字 ...
- swift 可选类型(optional)--- swift 入门
一.思维导图 二.代码 //这样无形中就会让代码很丑陋 if x != nil && y != nil { print("x或y都不等于空") } print(&q ...
- 工大助手(C#与python交互)
工大助手(爬虫--C#与python交互) 基本内容 工大助手(桌面版) 实现登陆.查成绩.计算加权平均分等功能 团队人员 13070046 孙宇辰 13070003 张帆 13070004 崔巍 1 ...
- java中文乱码解决之道(九)-----总结
乱码,我们前台展示的杀手,可能有些朋友和我的经历一样:遇到乱码先按照自己的经验来解决,如果没有解决就google,运气好一搜就可以解决,运气不好可能够你折腾一番了.LZ之所以写这个系列博客就是因为遇到 ...
- ABP源码分析四:Configuration
核心模块的配置 Configuration是ABP中设计比较巧妙的地方.其通过AbpStartupConfiguration,Castle的依赖注入,Dictionary对象和扩展方法很巧妙的实现了配 ...
- 拉格朗日插值法——用Python进行数值计算
插值法的伟大作用我就不说了.... 那么贴代码? 首先说一下下面几点: 1. 已有的数据样本被称之为 "插值节点" 2. 对于特定插值节点,它所对应的插值函数是必定存在且唯一的(关 ...
- Android点滴
1,View中getWidth(),getLayoutParams.width,getMeasureedWidth()的区别 2,setCompoundDrawables和setCompoundDra ...
- 百度地图API的使用
------------------自说自话----------------------------- 好奇怪,习惯性使用有道云笔记记录心得与知识后就很少用博客园来记录了. 但是后来想想,有些东西还是 ...
- lua解析赋值类型代码的过程
我们来看看lua vm在解析下面源码并生成bytecode时的整个过程: foo = "bar" local a, b = "a", "b" ...
- 了解HTML表单之form元素
前面的话 表单是网页与用户的交互工具,由一个<form>元素作为容器构成,封装其他任何数量的表单控件,还有其他任何<body>元素里可用的标签 表单能够包含<input& ...