预加载说小了就是个提前load,说大了,就是HybridApp的最基础功能。
Html5官方有个prefetch的attribute,用来做页面间的prefetch。其实在native跳h5容器的过程中更需要这种流程,否则很容易导致体验劣化。
其实方案蛮多的:
直接下载url + WebView#loadDataWithBaseURL
优点是简单,缺点就是只能预加载下个页面的html内容,这样一来,实际效果很差,常常只能预加载1%不到的数据量用隐藏的WebViewFragment加载,需要时做动画显示
优点也是速度最快,坑就非常多了:- html加载过程完整,控制行为需要在WebChromeClient/WebViewClient做非常多的分支判断
- 需要兼容动画效果
- 需要控制back
- 最重要的,会导致js中的window#onload之类的函数不合理的触发,引发逻辑问题
用WebView提前load,靠WebView的cache
仍然不复杂,因为在预加载时可以禁掉js,真正加载的时候允许js,上一个方案的控制部分就解决了。问题在于,WebView的Cache是一体的,一清全清,一旦需要清理缓存那就会出现各种各样的全局问题了。用WebView提前load,手写缓存
最复杂的方案,但是目前看没什么坑。
具体来说:- prefetch阶段,重写WebViewClient#onLoadResource,将所有资源都下载到本地
- 本地缓存的文件结构:用baseUrl#hashCode生成一个文件夹,每一个资源都缓存到baseUrl#hashCode/resourceUrl#hashCode的文件中
- 缓存过程中,要标记一下,保证未完成的资源不被读取
- mime和charset通过HttpURLConnection的content-type来解析
- 复用阶段,重写WebViewClient#shouldInterceptRequest,重定向所有资源请求到本地stream
- 清理阶段,交给外部,外部通过baseUrl,或者根目录来清理对应的缓存
- prefetch阶段,重写WebViewClient#onLoadResource,将所有资源都下载到本地
上面的思路发现了一个问题:所有预加载的网页会被下载两遍,因为并没有阻断WebView自己的下载。解决这个最好办法就是给WebView加一个proxy,在proxy中存储所有数据。乍看之下,WebView没有Proxy接口,所以网上就奔放了。然而,细想一下,shouldInterceptRequest不就是Proxy吗?只需要在原始的InputStream上wrap一层,在wrapper中做想做的事情就好了。所以最终方案是:
- prefetch阶段,重写WebViewClient#shouldInterceptRequest,为每一个请求生成HttpUrlConnection,为得到的InputStream注册一个onValueRead回调,回调中存储读取到的数据
- mime会有不严谨的情况,导致显示错误,解决方法
其他部分和上一个方法一致。
细说起来,还有几个值得改进的地方:
- fat32对于多文件支持很差。所以最好能做自己的资源定位方法
- VasSonic对于流的处理更加细致,可以做一个adapter层,如果文件只下载了一半也能用
- 目前所有的东西都是基于md5,其实改成原始文件路径可能也不错
- 部分资源是可以做预置的,需要的只是在stream的来源上进行区分(File或net)