基于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/

相关文章
相关标签/搜索