前言
對於混合應用而言,性能問題一直被吐槽,雖然設備的內存的不斷增大,很大程度上緩解了這個一問題,但是和原生應用來講還是有很大區別,本人從Phonegap2.x開始,一直的探索和使用混合應用技術。
當時的2.x性能真是不怎麼樣,首次加載時間也比較長,後來phonegap被apache納入旗下以後,更名爲Cordova,可以說從此以後,性能問題得到了很大的改善,佔用內存也越來越小,到如今使用的版本已經變爲6.3.0,看得出cordova也一直在嘗試縮小與native的差距。
不管一個平臺如何,是好,是壞?很大程度上都取決開發人員的技術水平,所以如何開發一個高性能的混合應用成爲了至關重要的問題。
對於大部分項目而言,都不是一錘子買賣,很多都是長期項目,長期項目就免不了版本的一次一次的更新迭代,可以業務邏輯變得越來越複雜,頁面變的越來越多,動畫越來越炫酷,數據量越來越大,面對種種問題,我們都需要解決,所以一個好的優化方案勢在必得。
下面,就談一下,這幾年我再混合應用開發過程中嘗試的優化方案。
部分內存數據移動至離線存儲
影響速度的根本因素,幾乎都在內存上,如果內存消耗過高,應用就會卡頓,甚至閃退現象。
所以建議內存方面應注意, 不要存大量的應用數據在內存中,我們可以選擇離線數據庫,比如pouchdb,當然部分必要數據還是要存的,看實際需求。
目前離線數據庫框架都是基於websql,運行速度非常快, 所以必要情況下,可以選擇數據離線存儲,避免佔用內存,因爲混合應用的webview渲染本身已經消耗部分內存了。
加載速度優化
由於項目不斷變大,勢必會導致js bundle文件的不斷擴充,文件大了,加載速度也就會變慢, 首要解決方案大概如下:
- JS壓縮整合 (壓縮所以js,整合一個文件)
- 圖片壓縮整合 (壓縮圖片,保證清晰度即可)
- CSS壓縮整合 (壓縮整合一個文件,減小大小)
- 巧用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實現性能上會存在很多問題,比如:
- 拍照壓縮
- 文件上傳下載
- 網絡狀態監聽
爲了能夠有更好的體驗,我們就需要調用原生api實現此功能,併合理創建多線程處理,從而提高運行速度。
採用輕量級組件化框架技術
這幾年下來,我使用的框架,還是比較多的,比如vue.js, ionic, avalon, SAPUI5, backbone, durandal, angular還有最新的react系列。
目前框架都在主推,高效輕量級的框架,效率高,性能好,加載速度快,易上手。
這些特性對於移動端同樣適用。
所以,我們在進行混合應用開發時候,要選擇輕量級的框架,切穩定,社區活躍的,保證遇到問題,有章可循。
這裏建議的有: angular, ionic, backbone。
http://blog.csdn.net/jiangbo_phd/article/details/51761565
實現本地View的加載渲染
目前混合主要實現方式有兩種:
- webview 加載本地html css, js , 圖片文件,也就是說所有文件存在於手機app本地。
- 還有一種即爲,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請求數據後在渲染到頁面上。
目前主流的框架都會支持很多的綁定方式,比如:
- 雙向數據綁定:
- 單向數據綁定
- 一次性數據綁定
如何使用合適的綁定方式決定了我們內存消耗的多少。
上面三種方式消耗狀態: 雙向> 單向> 一次性
那麼什麼時候該使用什麼樣的綁定方式呢?
舉個例子:
- 用戶登陸 (雙向,因爲要獲取用戶名和密碼在controller)
- 新聞詳細信息顯示(一次性,因爲不需要再更新數據)
- 表單驗證結果顯示信息(單向,因爲需要根據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]);
前端代碼工程化
前端代碼工程化,今年被唱的很熱,都在提這個概念,目前來看,一人獨大的時代已經過去了,更多的團隊協作開發,所以一個工程化代碼是非常有必要的。
主要包括以下幾個方面:
- 有意義的變量命名,遵從駝峯命名法或者團隊內部命名法則,統一規範。
- 使用JSLint對代碼進行檢查,所以人統一一個標準。
- 採用構建工具對工程進行自動化部署,減少操作步驟,比如使用npm command, gulp ,grunt webpack等等。
- 合理編寫註釋代碼,方便閱讀及他人接手,編譯後切記要remove掉,因爲對於產品環境,這些註釋沒有任何意義。
- 使用標準的代碼倉庫,比如git, bitbuket等等, 提交代碼進行comment, 合理創建子分之,保證多人開發協作無衝突。
- 代碼組件化或者模塊化,爭取做到一人一模塊。一模塊一個文件包或者一個文件,這樣可以保證彼此沒有衝突。
- 保證團隊每個人對代碼工程使用,爛熟於心,方便管理及升級。
- 任命技術負責人,負責代碼統一管理,版本合併,產品發佈。
採用實時雲更新技術對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,因爲我們要掌握的東西實在太多了。