Hybrid移動應用在多頁面大數據複雜業務背景下的優化實踐方案

這裏寫圖片描述

前言

對於混合應用而言,性能問題一直被吐槽,雖然設備的內存的不斷增大,很大程度上緩解了這個一問題,但是和原生應用來講還是有很大區別,本人從Phonegap2.x開始,一直的探索和使用混合應用技術。

當時的2.x性能真是不怎麼樣,首次加載時間也比較長,後來phonegap被apache納入旗下以後,更名爲Cordova,可以說從此以後,性能問題得到了很大的改善,佔用內存也越來越小,到如今使用的版本已經變爲6.3.0,看得出cordova也一直在嘗試縮小與native的差距。

不管一個平臺如何,是好,是壞?很大程度上都取決開發人員的技術水平,所以如何開發一個高性能的混合應用成爲了至關重要的問題。

對於大部分項目而言,都不是一錘子買賣,很多都是長期項目,長期項目就免不了版本的一次一次的更新迭代,可以業務邏輯變得越來越複雜,頁面變的越來越多,動畫越來越炫酷,數據量越來越大,面對種種問題,我們都需要解決,所以一個好的優化方案勢在必得。

下面,就談一下,這幾年我再混合應用開發過程中嘗試的優化方案。

部分內存數據移動至離線存儲

影響速度的根本因素,幾乎都在內存上,如果內存消耗過高,應用就會卡頓,甚至閃退現象。

所以建議內存方面應注意, 不要存大量的應用數據在內存中,我們可以選擇離線數據庫,比如pouchdb,當然部分必要數據還是要存的,看實際需求。

目前離線數據庫框架都是基於websql,運行速度非常快, 所以必要情況下,可以選擇數據離線存儲,避免佔用內存,因爲混合應用的webview渲染本身已經消耗部分內存了。

加載速度優化

由於項目不斷變大,勢必會導致js bundle文件的不斷擴充,文件大了,加載速度也就會變慢, 首要解決方案大概如下:

  1. JS壓縮整合 (壓縮所以js,整合一個文件)
  2. 圖片壓縮整合 (壓縮圖片,保證清晰度即可)
  3. CSS壓縮整合 (壓縮整合一個文件,減小大小)
  4. 巧用Splash 頁面(設置3秒延遲,保證html加載完全)

如果不足以優化,就需要講JS 文件整合成兩個文件,從而使文件變小,不必要的的js bundle 可以使用h5 異步加載,提高效率。

例如:

HTML頁面與原生View的無縫銜接

對於混合應用而言,很難達到htmL view和原生view的無縫對接,也就是說,使用起來恰似是同樣的實現方式,在一個棧裏邊。

但事實, 原生view屬於native的棧中,而html view屬於另一棧中,兩種形式的view分屬於不同的世界, 而且在使用js調用原生應用,cordova存在線程阻塞問題。

如果下面這個需求,就不好實現了:

這裏寫圖片描述

大概是,view1中點擊按鈕,調用掃描插件view2, 掃描出結果以後啓動view3,當在view3點擊back按鈕,需要在返回掃描產檢view2, 掃描插件view2點擊back退回view1.

對於這個需求,無論是native 還是 html, 在一個棧中,都不是問題,非常簡單,但是,不同棧裏,就不太好實現了,當在view3點擊back調用插件會阻塞掉線程,即便執行back,也無法回退的,最後,在view2點back 回退時候發現, view3閃一下,馬上退回view1。

爲了解決這個問題,需要用timeout簡單實現一個異步的操作,問題就解決了:

setTimeout(function(){
    // to execute the native plugin action. eg. call view2
},100)

動畫的使用

動畫效果,是用戶交互體驗的根本,如果動畫處理不好,就會使很大的問題。

比如進入一個頁面後,需要執行一個大概三秒的動畫效果,在這三秒內,可能無法觸發任何click事件。

比如transform動畫。

所以,當你的app中在進入頁面兩秒內發現點擊事件不生效,你就要考慮一下這個頁面是否有沒有執行完的動畫了。

巧用Cache緩存

對於angular這樣的框架而言是沒有頁面緩存技術的, 但是部分應用場景中,頁面緩存又在所難免,比如像ios tab view這樣的底部導航view,是存在緩存的,如果我們可以取消,會非常不符合ios的設計規範。

從用戶角度而言,通常情況這中tab view navigation是不需要重新加載數據的,這樣的主頁面,幾乎時刻存在,從新加載會浪費過多的時間。

這裏附上一個ionic的緩存方案:

$stateProvider.state('demo', {
            cache:true,
            url: '/demo',
            template: '<div></div>',
        })

上面代碼中,通過路由功能對這個頁面做了一個cache功能,同樣可以禁止掉。

注意:ionic默認是有頁面緩存的哦。

避免過多使用監聽

監聽操作,非常的好用,開發中帶來了很多便利,但是於此同時也帶來了一些弊端,即爲內存的消耗,如果過多的創建監聽,勢必會佔用很多內存。

所以在某些情況下我們可以避免使用監聽操作:

比如:我們需要監聽一個input輸入框數值的變化:

通常來講會是如下方案:

$scope.$watch('', function(new , old){

})

但是這樣就創建了一個監聽操作。

我們也可以這樣做:

<input type="text" ng-blur="action" />

或者

<input type="text" ng-change="action" />

合理使用HTTP

對於部分App, 在程序啓動後,可能不止請求一個服務,可能會兩個或者三個以上,如果需要等所有服務都請求完在進行頁面跳轉,實現起來就比較麻煩了。

如果一個請求完在請求另一個,時間勢必會延長,影響用戶體驗。

我們可以採用如下解決方案,來自angular:

$q.all([async1, async2, async3...], function(res2, res2, res3 ....){
})

http請求即爲異步請求,所以以上方案就是異步併發請求,而且是等所有返回結果結束在進行統一回調。

這大大提升了請求速度,提升用戶體驗。

合理使用Native功能彌補JS的短板

JS是萬能,但又不是萬能的,混合應用的有些功能如果用js實現性能上會存在很多問題,比如:

  1. 拍照壓縮
  2. 文件上傳下載
  3. 網絡狀態監聽

爲了能夠有更好的體驗,我們就需要調用原生api實現此功能,併合理創建多線程處理,從而提高運行速度。

採用輕量級組件化框架技術

這幾年下來,我使用的框架,還是比較多的,比如vue.js, ionic, avalon, SAPUI5, backbone, durandal, angular還有最新的react系列。

目前框架都在主推,高效輕量級的框架,效率高,性能好,加載速度快,易上手。

這些特性對於移動端同樣適用。

所以,我們在進行混合應用開發時候,要選擇輕量級的框架,切穩定,社區活躍的,保證遇到問題,有章可循。

這裏建議的有: angular, ionic, backbone。

http://blog.csdn.net/jiangbo_phd/article/details/51761565

實現本地View的加載渲染

目前混合主要實現方式有兩種:

  1. webview 加載本地html css, js , 圖片文件,也就是說所有文件存在於手機app本地。
  2. 還有一種即爲,html, css, js 圖片文件部署在遠程服務器上,通過url形式被webview加載。

兩種方式各有優缺點,第一種加載速度快,適用於離線app. 第二種加載速度慢,使用與web app。

看你選擇了。

Infinate動態加載數據

對於前端無窮渲染list數據是存在一定問題的,angular最大的問題就在這個地方,angular的ng-repeat,無法渲染大量數據,否則的頁面會出現假死的狀態。

對此,同樣存在解決方案,也就是Infinate無窮加載,當滑動到底部,會動態監測,然後load更多。

附上一個簡單的實現方案,採用ionic技術框架:

<ion-content ng-controller="MyController">
  <ion-list>
  ....
  ....
  </ion-list>

  <ion-infinite-scroll
    on-infinite="loadMore()"
    distance="1%">
  </ion-infinite-scroll>
</ion-content>
function MyController($scope, $http) {
  $scope.items = [];
  $scope.loadMore = function() {
    $http.get('/more-items').success(function(items) {
      useItems(items);
      $scope.$broadcast('scroll.infiniteScrollComplete');
    });
  };

  $scope.$on('$stateChangeSuccess', function() {
    $scope.loadMore();
  });
}

請求數據並渲染DOM優化方案

現在幾乎所有的系統都是前後端分離的,也就說需要通過ajax請求數據後在渲染到頁面上。

目前主流的框架都會支持很多的綁定方式,比如:

  1. 雙向數據綁定:
  2. 單向數據綁定
  3. 一次性數據綁定

如何使用合適的綁定方式決定了我們內存消耗的多少。

上面三種方式消耗狀態: 雙向> 單向> 一次性

那麼什麼時候該使用什麼樣的綁定方式呢?

舉個例子:

  1. 用戶登陸 (雙向,因爲要獲取用戶名和密碼在controller)
  2. 新聞詳細信息顯示(一次性,因爲不需要再更新數據)
  3. 表單驗證結果顯示信息(單向,因爲需要根據controll信息動態更新檢驗結果狀態)

避免創建過多的window全局對象

學過JS的同學都知道window即爲全局對象,是任何一個地方都可以調用到的,也會有老程序員告訴新人說:“你不要污染了全局空間”。

例如Angular這樣的框架,同樣有個全局屬性“$rootScope”, 也是我們在任何地方都可以使用的。

如果項目過多的使用了這樣變量:

一. 是會導致全局變量的混亂不堪,無法維護。
二. 不使用時候,無法回收這個變量,垃圾變量佔用內存。

儘量避免使用JS控制Dom樣式

最近兩年,Angular是非常火熱的框架,所以本人使用angular也會比較多一點,在這個框架中,有一個屬性是可以動態控制dom樣式的,叫ng-class, 可以說非常的好用,剛開始時候變肆無忌憚的使用起來。

後來發現運行起來多個ng-class控制時,或者在ng-class中使用個屬性,並使用===, 三目運算符進行判斷,最終導致頁面閃動的狀態,也就是效果不佳。

附上一個簡單的例子:

<div ng-class="{{'customStyle':flag ==== customFlag}}">
</div>

避免過度使用陰影和梯度和3D渲染

最近嘗試了一下three.js這個框架,主要是想做3D圖像,運行在瀏覽器端沒有任何問題,但是在手機上,發現運行明顯下降,運行一段時間後,手機發熱,所以我們在項目中,儘量不要過多的使用3D這些特效,如果使用,要及時銷燬,以免佔用過多系統內存。

對於簡單的CSS 3D旋轉效果, 沒有大問題, 大部分手機足以應付得了。

比如下面這段代碼:

div
{
transform: rotateX(120deg);
-webkit-transform: rotateX(120deg); /* Safari 和 Chrome */
-moz-transform: rotateX(120deg);    /* Firefox */
}

避免使用setInterval對插件進行頻繁調用

對於cordova插件運行機制,很多新人並不是很清楚,也並沒有很多人去編寫自定義插件, 一個插件的調用流程大概如下圖所示:

這裏寫圖片描述

所以,一次調用過程,會花費較長的時間, 這個過程中,由於JS的單線程異步操作,此時通過JS調取,會阻塞線程的繼續運行。

如果此時,頻繁的調用插件,應用內存會驟然上升,後果會crash掉。

及時升級優化Cordova Platform

我們都知道移動系統的更細速度異常的快,andorid每年一個大版本, IOS也是如此,每年都會推出一個大版本,每一個版本的改變可大可小,但是對我們混合應用還是有一定影響的。

其中webkit的更新最爲明顯,畢竟混合應用使用的是webkit做UI渲染嘛。

最近就遇到一個問題,當IOS 更新到10, android更新到6以後,總會有一些莫名的問題。顯示的多少會有些差異。

解決方案是,對Cordova Platform 做一個升級。

ios-platform —> 4.2.0

android-platform —-> 5.2.0

於此同時也對JS庫做了一個版本升級,以便支持最新的webkit.

儘量不去渲染不需要顯示的DOM

有一部分app在設計過程中,view的部分信息是不需要或者不經常顯示的,也就是這部分信息根據用戶行爲是可有可無的,但是這個部分信息在此頁面可能dom元素又非常之多,層次也會比較深。

如果我們默認就繪製dom元素,肯定會花費一些時間。

這種情況下,我們往往不去渲染,等需要的時候在渲染,所以這種實現方式很大程度上提升了渲染的速度。

下面說一下我再angular工程中的實現方案, 採用了ng-if解決這個問題,簡單容易:

<div>

    this is need to show

    <div ng-if="false">this is not need to show</div>

</div>

使用canvas優化圖片加載速度

通常使用Img在移動端渲染圖片,速度往往不是很理想,會顯示空白,這種情況我們通常會使用canvas進行優化,具體代碼如下:

使用過程要注意,只有圖片加載完成後才能顯示出來,否則會無效,這裏用到了回調onload方法。

<head>  
    <title></title>  
    <script type="text/javascript" src="jquery-1.7.1.min.js"></script>  
    <script type="text/javascript">  
        $(function () {  
            var c = document.getElementById("myCanvas");  
            var cxt = c.getContext("2d");  
            var img = new Image();  
            img.src = "./images/richard.jpg";  
            img.onload = function () {  
                cxt.drawImage(img, 0, 0);  
            }  
        });  
    </script>  
</head>  
<body>  
    <canvas id="myCanvas" width="1024" height="768" style="border: 1px solid #f00;">Your browser does not support the canvas element.  
    </canvas>  
</body>  

清除不需要的歷史View

如果你的app頁面越來越多,運行起來對內存的佔用多少會有一定的影響,對於移動app,就需要我們儘量清除已經不再需要的view,比如註冊流程所有的頁面,比如slide導航,註冊信息頁,一旦註冊完成,就需要刪除所有的view歷史。

再比如登陸,如果登陸成功也需要remove.

還有需要定時顯示的banner等頁面,顯示過了,可能就不再需要了,這時候也需要清除掉。

下面說一下,我用ionic做過工程的相關方案:

if(this.$ionicHistory.backView()){ //獲取上一個是哪個頁面
    this.$ionicHistory.clearHistory();  //clear
}

動態刷新頁面,爲開發增添樂趣

對於多頁面,複雜一些的項目工程,往往我們編譯會花費較多的時間,記得我做過一個android native的項目,由於項目比較大,每次使用android studio編譯都要花2分鐘左右的時間,非熬人,對混合項目也不例外。

開發過程中,我們往往需要快速查看效果,實時瀏覽編譯狀態,所以就需要動態刷新了,說一下我採用的技術。

主要採用gulp + browserSync+ watchify來監聽動態編譯,實時加載,速度還是很快的,主要代碼如下:

'use strict';

var config      = require('../config');
var browserSync = require('browser-sync');
var gulp        = require('gulp');
var url         = require('url');
var proxy       = require('proxy-middleware');

gulp.task('browserSync', function() {

    var proxyOpts = url.parse(config.backendUrl);
    proxyOpts.route = '/api';
    var browserSyncConfig = {
        port: config.serverport,
        server: {
            baseDir: config.dist,
            middleware: [proxy(proxyOpts)]
        },
        logPrefix: 'MyDemo',
        browser: ["google chrome"],
        minify: false,
        notify: false
    };
    browserSync(browserSyncConfig);
});


browserSync.reload({ stream: true, once: true })

gulp.watch('path',   [task]);

前端代碼工程化

前端代碼工程化,今年被唱的很熱,都在提這個概念,目前來看,一人獨大的時代已經過去了,更多的團隊協作開發,所以一個工程化代碼是非常有必要的。

主要包括以下幾個方面:

  1. 有意義的變量命名,遵從駝峯命名法或者團隊內部命名法則,統一規範。
  2. 使用JSLint對代碼進行檢查,所以人統一一個標準。
  3. 採用構建工具對工程進行自動化部署,減少操作步驟,比如使用npm command, gulp ,grunt webpack等等。
  4. 合理編寫註釋代碼,方便閱讀及他人接手,編譯後切記要remove掉,因爲對於產品環境,這些註釋沒有任何意義。
  5. 使用標準的代碼倉庫,比如git, bitbuket等等, 提交代碼進行comment, 合理創建子分之,保證多人開發協作無衝突。
  6. 代碼組件化或者模塊化,爭取做到一人一模塊。一模塊一個文件包或者一個文件,這樣可以保證彼此沒有衝突。
  7. 保證團隊每個人對代碼工程使用,爛熟於心,方便管理及升級。
  8. 任命技術負責人,負責代碼統一管理,版本合併,產品發佈。

採用實時雲更新技術對app升級

對於部分項目需求,用戶不行頻繁升級app, 的確這樣也比較麻煩,這裏我採用CodePush對混合應用代碼進行雲部署,不需要更新app, 既可以部署業務邏輯到用戶的手機上,也可以根據用戶需要定義所需的功能。

更多信息大家可以參考這篇文章:

http://blog.csdn.net/jiangbo_phd/article/details/52692320

使用Android Studio 和 Xcode性能查看工具

如果查看app內存消耗,是很多人關心的問題,這裏我採用Android Studio 和Xcode性能工具進行查看測試,可以隨時查看項目運行狀態,並作出調整。

注意:項目需要的debug的運行狀態。

Android:

這裏寫圖片描述

IOS:

這裏寫圖片描述

WebStorm工具使用優化

前端開發工具這幾年最火爆的,無疑是Sublime, WebStorm, Atom, visual studio code, 其中Atom, Visual Studio code作爲一款開源的IDE, 是當先最爲流行的, 支持很多酷炫的樣式和各種的自定義插件。

但是,說起來,這幾年用的最多的,非常喜歡的一款工具就是WebStorm, 它可以說是一款集成的工具,支持很多H5框架,但確實收費的。

伴隨着項目工程的越來越大, 項目更是用了npm bower這類庫管理工具,到整個工程文件愈發龐大,有時候寫着代碼,工具就死掉了,非常不happy, 探索一番,找到如下解決方案。

打開我們的工程, 找到WebStorm的preference屬性後,搜索Directories, 看到下面這個截圖,右鍵Exclude一些文件包,比如node_modules, bower_components, platforms, www等等。

然後點擊apply ⇒ ok,就好了,主要原理就是不要加載過大的工程文件,否則工具會吃不消的。

這裏寫圖片描述

總結

混合應用的開發之路,並不是很容易,個人認爲它的難點和複雜度要遠遠高於native和web,因爲我們要掌握的東西實在太多了。

個人微信公衆號

這裏寫圖片描述

發佈了208 篇原創文章 · 獲贊 143 · 訪問量 79萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章