H5 Hybrid性能優化方案

在混合開發中,我們經常需要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>

 

 

參考博客:

美團技術方案

騰訊手Q Hybrid框架VasSonic

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章