在混合開發中,我們經常需要H5開發一些活動頁面或內嵌的H5頁面。當頁面比較重,或網絡較差時,經常出現加載緩慢,對客戶體驗不好。那麼如果改善這個問題呢?
先看一下webview加載流程,webview加載通常可以分爲以下幾個過程:
Webview加載流程
大致可以分爲以下幾個階段:
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 [email protected]
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>