在这之前虽然看过一些博客介绍 hybrid,但是始终没有具体应用场景,想象的就是我现在做好了一个网站,然后 native 直接在 webview 中打开我的网站,类似浏览器中打开网站一样,头部添加一个类似浏览器的返回按钮,如果只是考虑到安卓或许这一步都没必要。
以下是以一个面描述了一个点的整个过程。
检索
认真接触 hybrid
是在入职后的第一个项目,项目是基于 Hybrid/webapp at gh-pages · yexiaochai/Hybrid · GitHub 其中视图部分在原项目基础上引入了 vue
,目录结构等都是和之前项目一致,页面和组件可以直接使用 vue
语法,hybrid
协议有些许变化。
没有 webpack
项目的启动依赖 Charles map local
预览项目,mock 工具当时使用的是 easy-mock
中间跨域的解决方案是 Charles map remote
。
硬件交互
我们常见的智能零售或者说其他的智能硬件的交互屏,一般是一个类似 树莓派 的板子,然后装载系统。可以是 win,android,也可以是 ios。开发需要 native 初始化一个 webview,然后定义一些通用协议,加载一个页面似乎就完活了。
通讯协议
开发过程中我们需要 native 赋予我们一些能力,我们需要约定一下通讯协议的格式,其中参数 tagname:协议名称;param:协议参数;callback:协议回掉(native处理成功以后可能需要返回一些参数信息)
。
格式如下:
{ tagname: '', // param: {}, callback() {} }
远程操作
例如用户扫码购买了一瓶饮料,我想在零售屏上切换一个欢迎使用的页面,这个时候需要在 用户-零售屏 上建立关系,这中间可能需要一个服务端。用户一开始和服务端建立联系,正常的下单购买,购买成功后,服务端推送一条消息给零售机,告诉他有用户买了瓶饮料,你现在给他推一个饮料出来。
用户 -> 扫码 -> 购买 -> 服务 -> 出货
用户扫描机器上的二维码,这个行为会触发一个与当前售卖机绑定的操作,绑定成功后服务可以推送一个消息给零售屏,零售屏接收到消息后会切换到欢迎界面,后续的交互包括支付成功,出货成功这些消息如果要表示到零售屏上都是同样的道理。用户与服务,服务与零售屏他们时如何通讯的呢?
用户与服务之间是数据请求,服务与零售之间为了通讯保持,建立的是 websocket 连接,零售机内部是 hybrid ,native 调用硬件能力。
问题分析
开发过程中遇见过一些问题,应该是 hybrid 应用开发也会遇到这些问题,相对而言这些问题对比一个成熟的 hybrid 方案要解决的问题要轻很多。
websocket 心跳重连问题
这个问题是消息推送相关。websocket 初始化会后,页面都是按照如下处理业务
const ws = new WebSocket('url'); ws.addEventListener('error', e => { // 去维护页面 }) ws.addEventListener('message', event => { // 按消息内容处理事件逻辑 }) ws.addEventListener('open', event => { // 定时发送消息 4min NAT }) ws.addEventListener('close', event => { // 去维护页面 }) ws.addEventListener('disconnect', event => { // 去维护页面 })
单页应用容器内维持状态,组件内展示业务状态。远程操作时提到的服务与零售屏之间的通讯是 websocket ,零售屏中通过监听 message 来调用对应的方法处理相应的逻辑。有指定的故障页面,故障会跳转到故障页面,故障有:机器断网(native捕捉处理),请求超时,服务端主动更新断开,运维维护断开等等情况,如果是 websocket 断开会定时刷新页面重新连接,如果是 websocket 没有断开,数据请求超时等,定时再次请求,如果请求成功切换页面。
这里有另外一个问题,零售屏页面更新的问题,之前是进入维护页面会定时刷新页面,这样如果页面更新下次刷新肯定可以更新到新的页面,现在更改成只有断开 websocket 才会刷新,这个断开操作可能会干扰到用户操作。
对于 app 来说有一个用户重启 app 的概念,我们也可以加一个这个功能,机器会断电,断电后可以更新或者线下运维人员可以在运维的时候重启一下。
还有一种方案就是服务向最近10分钟无状态的机器推送消息,机器收到消息后强制刷新页面。
定时发送业务心跳消息。在这之前没有定时去发送消息,会出现一种情况是受其他原因被关闭了,websocket 并不会监听到关闭或断开或错误,这样就不会去重新连接,这时用户的操作零售屏是收不到任何消息的。服务推送消息是正常推送会提示已经断开。
Pings have an opcode of 0x9, and pongs have an opcode of 0xA
心跳的发送都是底层协议来做的,但是会涉及到一个问题就是NAT超时链路会被断开,这个时候如果业务没有数据传递,客户端不会重新建立连接,如果零售屏没有消息发送就不能出发关闭时间,如果零售机就失联了,按照Android微信智能心跳方案设置了一个业务心跳,维持长链接的活动,这样以来不会触发用户扫码无响应机器失联的情况。
服务可以根据扫码加推送消息日志来判断有没有具体失联,如果失联可以调整业务心跳的触发时间。
缓存方案
一开始 Native 认为缓存问题很难处理,直接就在配置中没有使用缓存,每次页面加载都会直接重新从线上加载资源(LOAD_CACHE_ONLY
),我的每一个状态页面切换也都会耗费资源流量,首页加了一个视频啊,1G的流量卡如何是好?网络不好的情况下如何是好?
搜索发现 android 在 webview 中可以有几种形式设置缓存
// 缓存模式如下:// LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据// LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。// LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.// LOAD_CACHE_ELSE_NETWORK: 只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
通过 nginx 配置 http 的缓存协议头,可以使用 LOAD_DEFAULT
,通过缓存协议来控制。页面的发布每一次都会根据内容以 hash 的名字命名编译文件,每次发布都能保证修改后的文件会重新从走线上资源拉取。
meta标签,浏览器在请求文档资源时会在请求头上携带 content 信息,可以处理缓存问题,但是对于非浏览器缓存的情况下这种形式就失效了
meta 标签是走的 http 协议头来传递,直接设置 http 协议头不是也可以解决问题吗
Last-modified/If-Modified-Since
Etag/If-None-Match
详细学习可以参考资料HTTP 缓存机制一二三Expires HTTP/1.0 指缓存过期的时间,超过了这个时间点就代表资源过期
Cache-Control HTTP/1.1 指定一个时间长度,在这个时间段内缓存是有效的,单位是s
强缓存
协商缓存
本地资源缓存,可能我们有时需要更新 app 资源包的形式来更新一些资源,是直接使用
file://
的形式来读取资源好一些呢?还是直接走http://
的形式好一些呢?很明显是后者,如果缓存有,强缓存直接用本地。协商缓存或者缓存没有 native 有直接拦截请求做响应,如果没有,直接走线上请求。
编译项目以后,项目静态资源可以按需打包到 app 中随 app 的更新升级做升级。
图片资源渲染问题
零售屏的二维码信息时动态更新的,有一个很诡异的bug是二维码偶尔会出现缓存的问题。浏览器渲染机制以及vue内部运行机制,没有深入没有发言权,后续如何提升这也是一些方向。
为了解决图片的缓存的问题,请求的时候我们都会在图片的地址上带上时间戳。诡异的问题描述如下(以下数据都是通过日志记录所得):
1. 生成:11:20:34 130281 2. 生成:11:20:50 399538 3. 生成:11:35:09 123391 4. 使用:11:35:15 123391 5. 生成:11:35:30 117602 6. 使用:11:35:37 399538 7. 使用:11:35:44 399538 8. 使用:11:38:29 399538 9. 使用:11:38:42 399538
11:20:50生成一个二维码,11:35:09生成一个二维码并正常使用,说明渲染争取,11:35:30服务日志记录最新生成的码上的信息和用户上传上来的信息不一致,如果说请求还没响应,图片应该是上一次的正常使用时的情况,不会出现上上次的二维码。代码逻辑时按也许需求去续改src。
<img src="" />
能力有限没能找到问题所在,解决方案不能没有啊,刷新时等待图片加载成功后再插入整个图片元素。不是单纯的更新一个属性,而是整个元素。
系统字体大小修改影响网页字体大小
实际零售屏都是一个标准,这个需求是一个相对而言的伪需求,建立在不会有人去修改系统配置的情况下,如果有要么 native 控制,要么页面用相对尺寸,px 会按照设置的字体受影响。
多小程序扫同一个码的需求
零售对应用户,维护对应运营。运营有一些特殊的权限,不想影响到用户,单独需要做一个小程序,用户端和运维端同时去扫描用户端的小程序,运维端是拿不到任何小程序上的数据信息。
小程序也考虑到了兼容用户之前的二维码的情况,于是有了一个自定义二维码的规则,按照自己的业务规则配置链接,然后生成二维码,无论是微信扫一扫,用户或者运维小程序内部扫一扫都可以拿到二维码上的业务信息,同时为了兼容之前的小程序码的扫码需要做一个扫码收口处理。
如何进阶
这个是我一直在思考的问题,一年前刚刚入职的时候给的目标是一年对比之前的两年,现在看来一年不如一年,之前还能尽量保证按时学习新的知识点,唯一有出入的是现在可以学而实践之。从某些方面来说这一年是没有达到预期的。
沉淀
从入职到现在项目起起伏伏很多次,hybrid 技术方案也有 blade,blade-vue,blade-scripts,react-hybrid,rn(名字我按照自己的实际使用划分),小程序也有使用原生小程序,wepy,mpvue,也从 javascript 也部分到 typescript ,如果只是把自己局限在某一个语言或者某一个框架,这样始终是了解如何使用这个框架,学习从文档开始都可以上手。
Hybrid 从 blade 演进到现在已经是第四个版本了,基本的思想都是在继续沿用,改进的只是技术方案。这也是我想到在实际工作中不能局限某种语言或方案,能想办法把当前工作用的技术方案沉淀下来,延续到以后的工作,不能每一次开局一把刀,装备全靠捡,积累很重要。
基础服务
在不断升级过程中也暴露了一些问题,中间也说明了基础服务的重要性,刚开始时是0,计划的是半年内趋于稳定,后面加班的时候会越来越少,后面发现不加班能解决的问题就是换家工作,差别在于加班时你在做的是什么?
如果是没有沉淀的情况下你可能需要2天,沉淀后1天或者半天就能完成的工作,剩下的时间让你加班你是不是可以继续折腾了?但这些都需要建立在基础服务完善的基础之上,没事的时候就完善通用组件,工具函数,基础样式类等,相应的服务端也需要配合演进,不断打磨中才能更好的完善稳定。
干
上去就是干!