在混合开发中,我们经常需要H5开发一些活动页面或内嵌的H5页面。当页面比较重,或网络较差时,经常出现加载缓慢,对客户体验不好。那么如果改善这个问题呢?
先看一下webview加载流程,webview加载通常可以分为以下几个过程:
Webview加载流程
![](https://i-blog.csdnimg.cn/blog_migrate/e62b0fc4154bdb098ed9c501e58b2ef0.png)
大致可以分为以下几个阶段:
1、Webview初始化
2、下载和解析Html/js/css
3、和app交互(不一定有)
4、JS请求数据
5、更新数据,页面完成渲染
其中耗时比较多的阶段为1,2
那么我们的解决方案就是分而治之,寻找每个阶段的优化点,每个阶段都把消耗时间降低,总的消耗时间也就降低了不是吗?
各阶段优化方案
Webview初始化
1、webview预初始化
当App首次打开时,默认是不初始化浏览器内核的;只有当创建WebView实例的时候,才会创建WebView的基础框架。所以App中打开WebView的第一步并不是建立连接,而是启动浏览器内核。
因此,我们可以在进入webview之前,比如app初始化之后,进行webview预加载。等进入webview之后就不用再初始化了,从而缩短页面首次打开时间。
这一步主要由手机端同学完成。可以新建一个预加载类。进行webview的创建、存储于缓存池中、需要再从池中获取。例如:
private WebView createWebView() {
WebView webView = new WebView(new MutableContextWrapper(BaseApplication.application));
webView.setWebViewClient(new WebViewClient());
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.loadDataWithBaseURL(H5首页地址, "", "text/html", "utf-8",null);
return webView;
}
public WebView getWebView(Context context) {
WebView webView = null;
if (mCachedWebViewStack == null || mCachedWebViewStack.isEmpty()) {
webView = createWebView();
} else {
webView = mCachedWebViewStack.pop();
}
MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();
contextWrapper.setBaseContext(context);
return webView;
}
2、开启缓存、硬件加速
主要用于页面有较多图形绘制场景。
使用方式:在AndroidManifest中<activity>元素中使用android:hardwareAccelerated属性,启用或禁止Activity硬件加速。
下载和解析Html/js/css
1、不生成map文件(H5端)
config文件夹,index.js中,sourceMap设为false
productionSourceMap: false,
这样打包出来的文件不包括页面代码,整体体积会小一些
2、代码压缩(H5端)
build文件夹下,webpack.prod.js,开启html,css,js文件压缩
const HtmlWebpackPlugin = require('html-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
new HtmlWebpackPlugin({
filename: process.env.NODE_ENV === 'testing'
? 'index.html'
: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
}
}),
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),
3、CDN加速(服务端)
4、DNS采用和客户端API相同的域名(服务端)
DNS会在系统级别进行缓存,对于WebView的地址,如果使用的域名与native的API相同,则可以直接使用缓存的DNS而不用再发起请求图片。这一步优化大概可以缩小几十ms。
5、app离线包(服务端、app端)
页面发布到CDN上面,WebView发起网络请求去拉取。当用户在弱网络或者网速比较差的环境下,这个加载时间会很长。于是可以通过离线预推的方式把页面的资源提前拉取到本地,当用户加载资源的时候,相当于从本地加载,即使没有网络,也能展示首屏页面。
6、服务端渲染,直出首屏html(服务端)
关于这一块,操作起来有点难度,对后端也不太懂,详见第二篇参考博客。
和app交互
这一步不一定会有。如果通过dsbridge或者jsbridge等实现app和web端的交互,进入页面,请求需要app参数才能发起请求的话,才会有这一步。这一步的优化,目前方案是,精简交互协议的数据,只传必要的参数。
JS请求数据
1、Gzip压缩(服务端、H5端)
H5端开启gzip开关
//index.js
productionGzip: true,
//webpack.pro.js
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
若执行报错,缺少compression-webpack-plugin,则需执行如下安装语句(注意要指明版本1.1.12,版本太高依然会报错)
npm install --save compression-webpack-plugin@1.1.12
2、客户端初始化期间代理数据请求(app端)
顾名思义,先提前确定H5页面的请求,手机端进入webview初始化页面时执行这些请求。待webview页面初始化完成后,手机端代理H5页面请求,直接返回已请求回来的接口数据。
这是个行之有效,但对于请求不多的页面改善效果有限的方法。同时也会增加手机端和H5端的代码复杂度。
更新数据,页面完成渲染
1、图片懒加载
安装插件
npm install vue-lazyload --save
使用
<img v-lazy="imageUrl" class=""/>
2、路由懒加载
常用的懒加载方式有两种:1、import方式(推荐) 2、异步方式
懒加载之前:
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component:HelloWorld
}
]
})
import方式
import Vue from 'vue'
import Router from 'vue-router'
const HelloWorld = ()=>import("@/components/HelloWorld")
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component:HelloWorld
}
]
})
异步方式:
import Vue from 'vue'
import Router from 'vue-router'
//import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component:resolve=>(require(["@/components/HelloWorld"],resolve))
}
]
})
3、组件懒加载
和路由懒加载同理
懒加载之前:
<template>
<div>
<HelloWorld></HelloWorld>
</div>
</template>
<script>
import HelloWorld from './HelloWorld'
export default {
components:{
"HelloWorld":HelloWorld
}
}
</script>
import方式:
<template>
<div>
<HelloWorld></HelloWorld>
</div>
</template>
<script>
const HelloWorld = ()=>import("./HelloWorld");
export default {
components:{
"HelloWorld":HelloWorld
}
}
</script>
异步方式:
<template>
<div>
<HelloWorld></HelloWorld>
</div>
</template>
<script>
export default {
components:{
"HelloWorld":resolve=>(['./HelloWorld'],resolve)
}
}
</script>