基於Android Webview的Hybrid App開發的前端優化

最近作一個項目,是將一個相對複雜(內容後臺模塊化配置)的mobile web頁面嵌入到Android的webview展現,把遇到的問題和一些經驗總結下javascript

https://github.com/Dianjoy/gamepopphp

(1)圖片!圖片!圖片!css

我以爲不論是原生App仍是Web App,加載優化的第一條就是合理的設置圖片,這點每每容易被忽視。一切只在WIFI環境下的測試都是耍流氓!html

這個項目的主頁面,一開始前端負責切圖的同事給出的靜態頁竟然有1M多,其中最大的一張banner圖接近300K! 直接從PSD切出來的高保真原汁原味的展現效果確實震撼,百分比佈局下,在chrome放到全屏顯示仍是清晰無比。理想很豐滿,現實卻骨感,惋惜咱們不是生活在Provo,沒有google fiber的狀況下只能忍痛犧牲這種「網絡不能承受之美」。wap頁面就是手機上看的,通常4~5寸屏幕能清晰顯示,6寸‘巨屏’犧牲點效果不影響使用就足夠了。前端

目前總結大體的圖片組成java

  • 橫鋪圖片,大概佔全屏的1/5~1/4左右的圖片,建議30K左右
  • 櫥窗圖,寬度1/4~1/2方圖,8~15K
  • 加載佔位圖、loading動畫 單色,質量調低,1.5K
  • 多個小圖片,最好合成一張用css sprite佈局,webview裏的http請求很慢,能省則省
  • 什麼時代了,通常的漸變 圓角樣式能用css3就必定不能老土再用圖片了!
  • 一些小圖,能夠base64成字符串,用css data:image保存(這個持保留意見,不直觀,並且增長了css文件的體積,這種字串通常gzip壓縮也不會變小多少)

(2)使用zepto.js代替jqueryjquery

或許你是javascript大牛建議一切用原生,可是簡單的選擇器和DOM操做確定沒有問題,況且手機上不用能夠把大量IE兼容的代碼直接忽略(暗爽)。可是真正作webapp,稍微複雜點仍是須要使用一些插件,每一個功能都用野生js重寫,難度和穩健性先不說,代碼也會愈來愈臃腫難以維護。(野生Javascript怎麼也稱不上優雅)css3

那麼爲何強烈建議用zepto.js代替jquery呢,這可毫不僅僅由於gzip後差異20K的文件體積,而是由於Android Webview奇葩的js解析效率和更奇葩的onPageFinished事件,總之一旦用了jquery,頁面的白屏loading確定會多滾不少圈,寶貴的加載時間浪費在一個個用不到的函數對象的創建和兼容判斷語句裏了。git

而用zepto.js能夠有明顯的改善,並且基本的選擇器、DOM操做、ajax,寫起來和jquery是徹底同樣的,無痛遷移,個別插件不兼容,每每也只須要把最後閉包外的(jQuery)改爲($,window,document)就能夠了。經常使用的插件通常也能夠在github上找到zepto.js compatible versiongithub

(3)先載入DOM,延時加載和執行js

奇怪,這不就是$(document).ready和window.onload的卻別麼?糊弄誰呢

但確實不是這麼簡單,主要緣由就在於Android Webview的onPageFinished事件,Android端通常是用這個事件來標識頁面加載完成並顯示的,也就是說在此以前,會一直loading,可是

Android的OnPageFinished事件會在Javascript腳本執行完成以後纔會觸發。若是在頁面中使用JQuery,會在處理完DOM對象,執行完$(document).ready(function() {});事件自會後纔會渲染並顯示頁面。(參見 http://hi.baidu.com/goldchocobo/item/9f7b0639f3cd2efe96f88dfb)這篇文章。文中使用的lazyload.js已經有了版本更新,語法也發生了變化,這樣用便可

?
<script src= "js/lazyload.min.js" ></script>
<script>
function loadComplete(){
     //do something
}
 
//針對Android webview渲染js慢的問題,延時加載
function loadscript(){
         LazyLoad.js([
          'js/zepto.min.js' ,
          'js/jquery.lazyload.min.js'
          'js/mustache.js' ,
          'js/flowtype.js'
         ], loadComplete);
}
setTimeout(loadscript,10);
</script>

這裏的關鍵就是setTimeout(loadscript,10),這個語句就是Webview裏頁面加載顯示和載入和執行其它js和頁面渲染事件的分水嶺。把原來放在$(document).ready裏面的主體程序放在loadComplete裏面就好了。

通過測試,這個對包含複雜js的頁面在webview中加載的提高最明顯,若是你的頁面一直在傻乎乎的loading loading loading.. 最好試一下這個辦法。

不過咱們的主體頁面初始什麼內容都沒有,全部DOM都須要mustache根據api的配置,從模板中render,因此Android交了兵權以後還要在頁面上空白或者顯示自定義的loading圖一小會,不過絕對比以前那種體驗要明顯快的多(大概15秒=>5秒的樣子)。

(4)圖片懶加載

緣由仍是由於不在Provo,注意此lazyload非彼lazyload,這裏是jquery.lazyload,小改動就能夠支持zepto.js

這個插件很常見,最好仍是去github主頁https://github.com/tuupola/jquery_lazyload/看用法,手機上調用的時候最好加上 threshold:300,不然滾動,由佔位圖加載的等待時間仍是有點明顯。

若是滾動加載失效(找不到緣由),能夠試試在lazyload以後加一條

?
$(window).trigger( "scroll" );

就能夠了。另外lazyload佔位圖雖然小,可是最好能提早加載到緩存,這樣頁面顯示的時候高度不會突變,把不一樣寬高比的佔位圖放在<body>不顯示便可

?
< img src = "upload/images/other/load_full.jpg" style = "display:none;" />
< img src = "upload/images/other/load_half.jpg" style = "display:none;" />

(5)使用LocalStorage緩存DOM

若是你的頁面主體和咱們此次同樣,初始的DOM只有一個loading甚至空白,全部的內容都須要api獲取接口數據,而後根據模板(好比mustache.js)render以後在append到DOM裏的話,那麼無論怎麼優化,每次還都是須要等待那麼一下子,api請求接收和js模板引擎的處理在webview上都明顯的慢。

而有些頁面雖然須要後臺配置,但並非那麼動態,像一個商城的首頁這種,即便前端顯示更新不那麼即時,也不是很大的問題,刷新或者下次進入再顯示最新版本也能夠接受甚至是更好的用戶體驗。

咱們這裏把第一次mustache render好的html塊,存入LocalStorage,而後下次進入頁面的時候,先直接從LocalStorage中讀取並顯示,api讀取和模板渲染後的新DOM再更新到LocaStorage中(若是有必要,能夠在這個時候,比較下新舊是否相同,不一樣再更新一次DOM)

?
function jq_lazyload(){
     $( "div#page_all img.lazy" ).lazyload({threshold:300, load : function (e){$( this ).next( 'b' ).hide();$( this ).removeClass( 'lazy' );}});
     $(window).trigger( "scroll" );
}
 
function loadComplete(){
     //omit ...
 
     //若是用localstorage則先lazyload img
     if (window.localStorage){
         if (localStorage.getItem( 'dom_all' )){
            jq_lazyload();
         }
     }
 
     $.ajax({
         url:server_url,
         dataType: "json" ,
         type: "GET" ,
         success: function (json){
             var dom_all= "" ;
             for ( var i=0; i<json.floors.length; i++){
                 var style_this = json.floors[i].style;
                 dom_all+=Mustache.render($( '#floor_tpl_' +style_this).html(), json.floors[i]);
             }
             if (!window.localStorage || !localStorage.getItem( 'dom_all' )){
                document.getElementById( "page_all" ).innerHTML = dom_all;
                jq_lazyload();
             }
             localStorage.setItem( 'dom_all' ,encodeURIComponent(dom_all));
             dom_all= null ; //釋放內存
         }
    });
}
 
function loadscript(){
     if (window.localStorage){
         if (localStorage.getItem( 'dom_all' )){
             document.getElementById( "page_all" ).innerHTML = decodeURIComponent(localStorage.getItem( 'dom_all' ));
         }
     }
     LazyLoad.js([
         'js/zepto.min.js' ,
         'js/jquery.lazyload.min.js'
         'js/mustache.js' ,
         'js/flowtype.js'
     ], loadComplete);
}
setTimeout(loadscript,10);
 
//處理Webview未lazyload完,進入其它頁面,js停止,返回不執行
window.ontouchstart = function (e){
     jq_lazyload();
}

(6)Webview的設置

webview自己的設置也很重要,特別是cache和localStorage是否開始,是否app退出再進入就不存在了,各自空間有多大,這些須要和Android開發的同事溝通好,說不定就是一行參數設置,體驗就大不一樣

  • Cache開啓和設置

//下面3個是跟瀏覽器緩存Cache相關的,一個頁面的 圖片\js\css 載入過以後
//在服務器設置的文件有效期內,每次請求,會去服務器檢查文件最後修改時間,若是一致,不會從新下載,而是使用緩存

?
browser.getSettings().setAppCacheEnabled( true );
browser.getSettings().setAppCachePath( "/data/data/[com.packagename]/cache" );
browser.getSettings().setAppCacheMaxSize( 5 * 1024 * 1024 ); // 5MB
  • LocalStorage相關設置

//下面是跟瀏覽的LocalStorage有關的,像首頁的DOM,第一次載入,須要從服務器ajax請求接口json配置數據,而後用js從模板中渲染拼接成DOM,顯示在頁面中
//因爲Android webview的JS處理很慢,這裏把第一次渲染後的DOM存入LocalStorage中,之後打開頁面不用請求API和JS渲染,優先加載頁面,和Cache配置,速度會快不少
//可是Android webview的LocalStorage有個問題,關閉APP或者重啓後,就清楚了,因此須要下面browser.getSettings().setDatabase相關的操做,把LocalStoarge存到DB中

?
browser.getSettings().setDatabaseEnabled( true );
browser.getSettings().setDomStorageEnabled( true );
String databasePath = browser.getContext().getDir( "databases" , Context.MODE_PRIVATE).getPath();
browser.getSettings().setDatabasePath(databasePath);
 
myWebView.setWebChromeClient( new WebChromeClient(){
    @Override
    public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
    {
        quotaUpdater.updateQuota(estimatedSize * 2 );
    }
}
  • 瀏覽器自帶縮放按鈕取消顯示

//這個是跟瀏覽器的頁面縮放相關,不用顯示瀏覽器的放大縮小按鈕,這個通常在最下面出現,體驗很差

?
browser.getSettings().setBuiltInZoomControls( false );

(7)服務器端設置 gzip etag Cache-Control

gzip就不說了,總之必定要開啓html css js json的gzip壓縮!!!

爲了弄明白這個,非科班出身的我連着fiddler邊調測邊翻了小半本<計算機網絡>的書,其實也還沒徹底弄明白。並且測試發現如今的瀏覽器特別是桌面的360(#Anti-360#)和一些國產手機瀏覽器,爲了製造「極速」的假象,緩存處理不少地方都沒有按照規範來,動不動就會過分緩存,致使頁面不能及時更新。Android Webview的LOAD_CACHE_ELSE_NETWORK設置更是徹底無視etag、expire time這些,強制使用緩存。

總之,這塊還沒徹底弄明白,等後面完全明白了再結合fiddler和apache總結下吧。給出我這邊apache .htaccess相關配置

?
<IfModule mod_deflate.c>
AddOutputFilter DEFLATE html xml php js css json
</IfModule>
 
<IfModule mod_headers.c>
     <FilesMatch "\\.(ico|jpe?g|bmp|png|gif|swf|css|js|json)$">
         Header set Cache-Control "max-age=2692000, public"
     </FilesMatch>
     <FilesMatch "\\.(php|html)$">
         Header set Cache-Control "max-age=60, private, must-revalidate"
     </FilesMatch>
     Header unset ETag
</IfModule>

(8)以上都不是

其實Hybrid App的最佳實踐,仍是應該把全部的html css js和主要的圖片資源離線存儲在Android的asset文件夾下,而後由Android實現從服務器端到手機的這個www主文件夾的更新機制,這樣纔不用凡事從server端下載(不少人討論webapp時只大談特談性能,其實一切須要加載的實現方式纔是最大的「阻塞」)。這樣也能夠爲所欲爲的使用一些Sencha Touch或AngularJS+UI這樣的中型和重型框架,惋惜上面提到的文件更新機制沒有創建,暫時尚未機會實踐這種模式。這種想法的文章很少,參考http://developer.appcelerator.com/question/146564/update-apps-local-html-webviewed-files的reply部分

就到這裏吧…

本文地址:http://awebird.com/blog/art/122/

相關文章
相關標籤/搜索