本文阅读的前提是您对Weex已有所了解.
本文的缓存管理主要是由Native端去实现的(99%), Web端可能需要配合做相应的处理(1% 后面会提到).
本文的缓存管理是基于我对Weex的理解而建立的.
前言:
在做缓存之前也是网上看了很多资料, 大体的思路就是将所有JS打包成JS Bundle下载到本地, 通过服务端返回的ETag去判断是否有更新, 决定是否更新本地JS Bundle.
参考资料:
Weex的JS缓存实现 - Android
可能是史上最全的weex踩坑攻略
但是我觉得没有必要将JS打包成Bundle统一下载, 一是慢, 二是如果某一个页面发生改动则需要连同下载所有的JS, 这样不是很好~
首先说下我的缓存思路~
在WeexSDK加载Url之前(RenderUrl), 对这个Url进行缓存处理和判断, 针对页面进行缓存处理和更新判断
比如iOS代码~:
// 拿到要渲染的http://xxx.js路径 在渲染前对这个js进行处理
[[WeexCacheManager defaultManager] getRenderUrlWithRequestUrl:self.urlString callBack:^(NSURL *URL) {
dispatch_async(dispatch_get_main_queue(), ^{
// WeexSDK 渲染 js
[self.wxInstance renderWithURL:URL options:@{@"bundleUrl":URL.absoluteString} data:nil];
});
}];
这样的话会很方便管理缓存~
缓存思路
目的:
1.仅对.js文件进行缓存处理, 降级后显示的.html无需考虑~
2.将服务端.js文件下载保存至本地沙盒, Weex管理并只加载缓存JS文件
3.若从服务端下载.js文件失败则不考虑加载缓存,直接渲染网络文件
基础配置:
1.在做缓存管理之前. 我是将JS文件放在服务端的, 本地渲染的是服务端上的JS地址.
2.图片静态资源也都是放到服务端部署的. 所以不存在加载Native端的图片
3.页面跳转我是分开写的,如果是Web环境使用Weex自带的Navigator.Push(), 否则我是在Native中注册跳转的Module, 实现Native端可控的跳转方法.JS代码如下:
// push操作
$push (path) {
if (this.$isNativePlateform() && weex.supports && weex.supports('@module/navigation.push')) {
weex.requireModule('navigation').push({
h5Url: this.$getPushPath(true, path),
fallbackUrl: this.$getPushPath(false, path),
animated: true
})
} else {
weex.requireModule('navigator').push({
url: this.$getPushPath(this.$isNativePlateform(), path),
animated: 'true'
})
}
},
navigation是我在Native端自己注册的方法, 方法也就是创建一个自己的WeexViewController, 当前页面Push过去~ 我的这篇文章有关于页面跳转的思路说明
4.跳转参数传递
goOrderDetail (status) {
if (status || this.isOrderList) {
this.$push('views/order/order-detail?applyId=' + this.dataItem.applyId)
}
}
上面JS代码可以看到我跳转页面的参数传递, 我只传了页面的相对地址和需要的参数,在真正跳转之前我会对这个path进行处理:
$getPushPath (path) {
// 用于处理path, 返回正确完整的Path, 主要工作是判断当前环境是否是Web环境, 如果是则返回.html + Query, 否则就是 .JS+Query 的形式
}
具体实现:
这个地址也就是对应我们写的一个Vue页面~ 参数部分暂不考虑, 需要保存的文件名称应该是:(URL.scheme + “://“ + URL.host + URL.relativePath 再去掉.js后缀)
http://baidu.com/blife-weex/dist/views/home/home
这就能保证文件名(地址)的唯一性了, 因为需要支持文件系统命名, 所以我将.js前面的部分用MD5转换一下, 这样文件名称就变成了 0d86bb3022272b658d2b14374173e497.js
只要是这个地址, 所生成的MD5就是一样的! 这也就是我们下在下来需要缓存的文件
保存在本地沙盒中某一指定的位置吧~
下一次再访问这个地址, 如果发现本地存在这个MD5.js的文件就说明之前已经做了缓存了~ 如果没有当然就下载这个.js文件 以MD5.js形式保存就行了
接下来考虑下得到了已经缓存的本地JS后改怎么办~
我们需要判断服务端的JS是否有更新, 如果更新了我们需要删除本地JS并重新下载新的JS并缓存到本地,再加载最新的JS文件, 方法是通过Headers中的ETag值.
ETag: 可以理解成服务端文件对应的hash,当文件内容变动了这个hash也会随之变化,ETag也会变, ETag值存在请求服务端文件(.js)的网络请求返回的Headers里
这样的话我们就需要存储ETag值去标识对应的缓存JS文件了~
我的做法是在当前沙盒目录中再创建一个Plist/Map本地文件, 该Plist/Map文件只有一个, 构造是Dictionary/Map套Dictionary/Map形式,
最外层Dictionary/Map以存储的MD5(下载的JS文件名)作为Key, Value是一个Dictionary/Map, 这个子Dictionary/Map用于存Etag等值,
这样的话在之前下载JS完成后, 存储下载JS的请求的ETag值, 作为下次加载JS前判断服务端是否更新的参照.
所以再加载本地JS前, 先发送一个Header请求, 拿到ETag值之后,通过Key/Value的形式找到本地存储的Plist/Map中对应的MD5的对象, 再拿到上次请求JS的ETag标识, 如果不一样则说明服务端更新了,需要重新下载~.
最后在只需要将本地JS路径拼接上原URL的Query部分 返回给Weex让其加载就可以了~
大致是这样的URL:
file:///User/xxx/0d86bb3022272b658d2b14374173e497.js?tokenServer=27abb15db479989f841c365e
以上就是基本的缓存策略~
额外扩展:
1.简化请求次数
在上述基本的缓存策略中, 我略加修改了一些东西 , 再下载后存储ETag值的同时, 我同时保存了下当前的时间戳(秒), 我这边设置了两个小时(某一时间)内,不用判断服务端是否更新, 不然的话每一次加载本地JS我都要去发送Header请求去判断服务端更新这样并不是很好~. 所以在加载本地JS时我判断如果上次存储的时间在两个小时(某一时间)内, 则不发送Header请求, 直接加载本地JS. 如果超过两个小时,则发送Header请求, 但如果发现服务端ETag值没变 ,则更改这个时间戳为当前时间戳, 保证下一个两小时(某一时间)内依然不发送Header请求, 直接加载.
2.关于获取域名和资源加载问题
其实一般都会出现关于域名更变的情况, 因为都会有测试服,正式服的部署.或者说在加载图片的时候都是通过当前域名加相对途径的形式去加载, 页面跳转也是这样,所以这就会遇到一个问题:
在加载本地JS资源时,路径都是file://的形式,没有办法获取到真正的域名地址
解决方法:
同样在下载JS保存ETag值的同时,保存下当前URL的域名, 在返回本地JS完整路径的时候将这个域名以一个参数的形式拼接在Query里传给前端, 这样前端就可以通过获取Query里的参数判断出用什么域名去显示静态资源了(1%的修改在这里~)
3.关于加载本地JS缓存后 weex-ui 中 wxc-icon组件显示错误
解决方法:
我在这里提了issue, 我是Copy了他们的源码放在本地,改了下请求就好了
iOS的小礼物
我将整理好的缓存管理类上传到GitHub(记得点个✨)了, iOS的同学可以直接用或者修改, Android的...我不会哈
PS:以上思路可能不是最好哈, 如果有可以改进的地方还请留言交流讨论