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


最近做一個項目,是將一個相對復雜(內容后台模塊化配置)的mobile web頁面嵌入到Android的webview展示,把遇到的問題和一些經驗總結下

https://github.com/Dianjoy/gamepop

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

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

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

目前總結大致的圖片組成

  • 橫鋪圖片,大概占全屏的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代替jquery

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

那么為什么強烈建議用zepto.js代替jquery呢,這可絕不僅僅因為gzip后差別20K的文件體積,而是因為Android Webview奇葩的js解析效率和更奇葩的onPageFinished事件,總之一旦用了jquery,頁面的白屏loading肯定會多滾很多圈,寶貴的加載時間浪費在一個個用不到的函數對象的建立和兼容判斷語句里了。

而用zepto.js可以有明顯的改善,而且基本的選擇器、DOM操作、ajax,寫起來和jquery是完全一樣的,無痛遷移,個別插件不兼容,往往也只需要把最后閉包外的(jQuery)改成($,window,document)就可以了。常用的插件一般也可以在github上找到zepto.js compatible version

(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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM