58同城Hybrid框架的点点滴滴
Hybrid框架简介
采用Hybrid模式的原因:
- 纯Native的迭代太慢,不能动态更新,且不能跨平台
- 纯Web页,有很功能无法实现,有些动画效果实现其体验太差
整体框架结构图
WebView加载流程
- 在Step1里有两个作用:
- 可以拦截html请求,对Html请求进行白名单的判断,只有规定域名的请求才能通过
- 转发一些如拨打电话请求,如tel:xxx
- 在Step2里主要是显示Loading加载框
- Step3:shouldInterceptRequest()
- 此方法在Api为11时才有,即3.0以后才有此方法,所以在2.x系统里,无法劫持资源请求
- 主要用于拦截资源请求,让其走本地资源缓存,实现Native资源缓存机制
- Step4:onPageFinished()要等所有的资源都加载完成后,才会进行回调,但此时,界面早已经渲染出来了。
- Loading界面消失的机制:
- 在html界面渲染完后,js马上回调一个PageFinished的Action通知Native,提前消失掉Loading界面
- 如果没有等到PageFinished的Action,就在onPageFinished()方法里,把Loading界面消失掉
跳转协议
现在的跳转协议是一个json格式,如下所示:
1 | { |
由于web页的Title是Native实现的,所以其标题需要从跳转协议里得到。
建议使用URL来做跳转协议,如下所示:
1 | jump://action/pagetype?url=xxx&title=xxx |
好处:外部调起时,其协议就可以统一
html拦截机制
Native实现缓存的思路是:通过shouldInterceptRequest()拦截html的请求。
js,css,image拦截机制
机制和Html的一致,都是通过shouldInterceptRequest()拦截请求。
但并不是所有的请求都会进行拦截走缓存,满足如下两种规则走缓存:
- 标准方式,通过在URL后面添加cachevers参数,如下所示:
1
http://xxx/xxx?cachevers=xx
- cdn的方式,URL满足cdn的格式也会走缓存,如下所示:
1
http://xxx/xxx_v版本号.xx
注意:整个缓存框架里,只认第一种格式,第二种cdn格式,会在shouldInterceptRequest()方法里进行转化为第一种格式,请求时,再转化为第二种格式
html,js,css,image的缓存框架
异步加载图片
虽然shouldInterceptRequest()方法是在后台线程里执行的,但如果直接在此方法里,请求图片资源,那所有的图片资源都将是同步的方式加载,影响最终的加载速度,也会阻塞shouldInterceptRequest()方法的执行,从而阻塞webview的渲染。
解决思路:创建新的线程来请求图片资源,马上返回shouldInterceptRequest()方法,但如何实现呢?通过查看WebView的源码,找到了一种方式:使用管道,代码如下:
1 |
|
缓存资源的版本号管理
缓存资源是通过其版本号来更新的,那资源的版本号应该存在哪里了?最直接的解决办法是:创建一个数据库,里面存储文件名与版本号的对应关系。我们最早也是这样实现的,这样会带来维护成本,还有其出错的概率。
最好的方案:把版本号与缓存文件存储在一起。
实现思路:不管缓存文件是文本文件,还是图片,在文件的开始位置写入一些Byte字节,这些Byte字节就存储了对应的版本号。
1 | /** |
相关的类
- WebResLoader:资源加载类,负责:异步加载,同步加载
- WebResCacheManager:资源管理类,负责:资源保存,加载,资源版本管理
交互框架
现在的交互方式有:
- 通过webview的addJavascriptInterface()方法交互
优点:简单,Js可以获取返回值,从Api 1开始支持。
缺点:不安全,js可以通过此漏洞调用用户手机里的很多功能 - 使用会在shouldInterceptRequest()方法交互
优点:安全
缺点:从Api11(即3.0)才支持,不支持js获取返回值
交互协议如下:
1 | { |
使用的是json协议,其中的action区分事件类型
具体的交互框架:
- 每一个Action协议会有对应的Bean, Parser, ActionCtrl。都是一一对应的
- ActionCtrl都在在具体的Fragment载体页进行注册,只有先注册过的Action,才会有相应的处理
- 在MessageBaseFragment里注册的Action为通用Action,所有的载体页都支持
Bean对象合法检测:在action协议解析完成后会生成一个Bean对象,所有的Bean对象都继承自ActionBean基类,在ActionBean类中新增checkWebAction()方法,以及check()抽象方法,由子类实现check()方法实现子类自己的协议检测。checkWebAction()方法执行所有ActionBean的通用检测,并在checkWebAction()方法中调用check()方法,执行子类自检。
WebView的载体页
- 按业务分,创建了不同的载体页,即有多个MessageBaseFragment的子类。(58当前使用的方式)
优点:App开发载体页简单,单个载体页不会变的非常庞大,易于维护
缺点:- 载体页过多,前端人员在写跳转协议时,要区分跳转到哪个web载体页
- 每个载体页支持的action协议是不一样的,造成很多不兼容问题,影响了后期的扩展性
- 维护成本加大了
- 一个载体页,支持所有的Action协议,支持所有的业务。(Hybrid二期会改为此种方式)
优点和缺点刚好和上面的方式相反,推荐使用此种方式
Cookie,Header
通过webview加载html的方式,有下面两种方法:
1 | // 直接加载url |
通过上面的方法直接加载Html页面时,会自动把cookie添加,那我们带一些参数给Server的方式就有两种:
- 通过cookie来带数据
- 2.2以后,通过Header带数据
经验:
- 两个同时都带,cookie和header都带相同的数据
- 在有一些Android手机里,其cookie总是上传不成功,通过抓包发现根本没有cookie信息。(之后证实发现用户其他app也无法使用cookie)
- Header是完全可以保证数据不丢失的方式,但由于javascript发出的请求,都无法带上header,所以还是要使用cookie
白名单
所谓的白名单是指:不在白名单内的请求,不进行加载,或者弹出一个Dialog,提示用户。
实现思路:
- 本地有一个白名单列表,可以更新此列表。注意:列表里指保存域名
- 在WebViewClient的shouldOverrideUrlLoading()方法里,进行拦截判断。注意:判断时,要考虑一级域名,二级域名等等。
WebView添加额外功能
WebView默认情况下缺少很多功能:
- 不能图片上传
- 不能进行文件下载
- 不能拨打电话等等调用系统其他组件
图片上传功能:分为两种,一种通过相册选择,再上传;一种是拍照后,再上传。这两种都能支持,方法可以直接搜索就可以了。问题:有部分手机无法调启上传,机型支持问题,解决方案:通过Action,由native来做上传
文件下载:原生不支持下载的URL,把下载URL,支持转发到浏览器,进行下载。最好不要支持url支持下载。58现在不支持
调用通用组件:在shouldOverrideUrlLoading()进行通用处理,如下所示:
1 | public boolean shouldOverrideUrlLoading(WebView view, String url) { |