Migration

原wordpress博客所在homezz虚拟主机到期不能续费,临时迁移至github pages (thx 2 hexo)

评论暂无,后续有时间整理

2014年终小结

41d5uf 整理下桌子,收拾下心情,准备写点文字,送别2014。 虽说对于大部分中国人来说,元旦不过算是个“洋节”,只是休一天假而已,真正的一年要在一个半月后才会结束。但是今天下班时,我却明显的有了告别一年,急匆匆要放下一担好好休整下的感觉,同事们也都早早下班,各有各的去处… 回顾过去这一年,惊讶的发现从硬性指标来说,我的生活居然发生了几件大事 No.1 婚了 合法的领了证,合情合理的回老家办了婚礼。完成人生大事一桩,从此和认识十年的老婆幸福的生活在一起… 说心里话感悟并不是很多,领证婚礼更多的是顺从法律和人情世事走的仪式,当然最大的好处让双方父母和爷爷奶奶们开心一下。我和老婆都不算是很浪漫和讲究的人,既然决定在一起了,一切就是水到渠成的事.. 至于婚礼,基本都是父母和姐姐操办,我们俩提前一天回家“出席”了下,也没有休假就匆匆赶回来上班(据说这一上就要幸福快乐任劳任怨的到70岁才能退休Orz..) 有一点点小不顺心,老婆新工作由于学校实验仪器的原因,基本这半年多都在杭州出差(异地科研-_lll),没想到经历了美国的两年后刚凑到一起,又要这样每到周末才能在一起。心里多少有些不爽,两个人也没少为此生气,但是确实是客观因素,也算是对我们的新的考验吧。好在这种生活很快就结束了,希望2015能是一种新的状态。 No.2 奴了。 买了房,未来很多年都是负翁。这事算是我个人的一大突破,对于我这种怕麻烦的人来说,不依靠父母(当然首付还是很大部分来自他们),一直很难想象自己可以处理这么多繁杂的事情,最后算是有些运气的成分,整个过程还算省心,估计比一些人去租个房子还省事,符合我们两人的一切从简从易的生活态度。从此在南京有个小窝,虽然自此每月拮据不少,也算是强制培养了节俭和理财的习惯。从这个角度讲,我认为应该更年轻一点结婚买房甚至生子,个人会成长和有担当的更快一些。 No.3 挪了。 New Career,从工作了两年多的准创业公司来到途牛。关于离开,我自己也说不清楚到底“是心委屈了,还是钱没给到位”(马总语),直接的导火索应该是公司的搬迁和新项目的合伙人给我Yesterday-Once-More的强烈心里暗示。回顾这两年,从一开始戏剧化的0基础面试入职(特别感谢高总的知遇之恩和一贯近于放任的信任),当后来慢慢做到web端负责人,自己的成长有本身的努力,也有很多机缘巧合,还遇到了很多很牛的同事(感谢互联网的开放和自由,我觉得我选对了适合自己的行业)。很遗憾到我离开的时候,很多梦想还没有实现,希望坚守的兄弟们可以做好。关于新工作,感谢另一位高总把我带到途牛并一直给予我本不应得的信任。入职两个多月也还在试用期,说实话由于一些主客观因素还没有完全找到属于自己的节奏,但这些都可以克服,最兴奋也特别憧憬的是看到这份新工作给我在技术和事业上都拓展了很大的可提升空间,有了值得拼一把的方向.. 总结过去这年,有了这三件大事,加上许多附加的小事,很深的一个感觉就是时间被分割的很零碎,没有一个平常心和稳定的状态好好的做事情。现在新的环境和方向有了,未来一年的重心就是工作。 最后,感谢2014!

未婚先胖...

aa 今天把域名续费了,才想起这个博客太久没有更新.. 一年的域名加主机费用,才零散的写了那么几篇,实在大浪费。技术总结还是很有必要,现在有同事遇到问题,一个文章链接发过去就不用多费口舌,而且时间越久越有价值。 没有更新的主要原因有三 (1)懒,obviously (2)工作很忙,而且最近没有做移动前端,值得分享且有冲动分享的东西不多 (3)LP大人回国后,生活的琐事慢慢多起来,个人时间收紧 域名续了一年,未来一年的目标要调整好工作和生活,多总结多记录,就这样 //————————————————————————————————————- 然后就是已婚了,5月22,Mark下… 附婚纱照一张(应题),LP的还未获批准暂时不能放出来 aa

原wordpress评论

在Android Webview的assets目录下开发Hybrid App的一些坑…

crying_android 好久没写blog了,忙起来真是没法.. 在上一篇文章《基于Android Webview的Hybrid App开发的前端优化》的最后一条“以上都不是”里提到了“其实Hybrid App的最佳实践,还是应该把所有的html css js和主要的图片资源离线存储在Android的asset文件夹下,然后由Android实现从服务器端到手机的这个www主文件夹的更新机制,这样才不用凡事从server端下载..”没想到这么快就应用到新的项目中了。 当然,还只是实现了将webapp放在Android app的assets下,通过json api与server交互这步,理想中的通过app文件操作的更新机制还没有做,但是已经是梦想照进现实的节奏。感谢Android开发的同事包容了我这边不厌其烦反反复复的修改调试,才让这个相对Cutting Edge的方法得以应用到产品中。原本使用Hybrid App的初衷是为了分担Android开发同事的一些工作,但由于实践经验不足,而且Android webview确实还存在一些bug和兼容问题,最后评估下来反而多耗了不少时间精力。因此我们对于Hybrid App方式的使用暂时告一段落,web侧工作重心回归数据和后台开发,但是这轮开发的经验和教训还是很宝贵的,也给了我们更多信心,相信再过一小段时间,HTML5在移动设备的大规模应用就会到来。 好的,梦醒了还是总结下在Android Webview的assets目录下开发Hybrid App的一些坑,和各种跳过或者踩过坑的方案吧。 【坑 #1】 部分Android 4.0设备(HTC、海尔等)LoadUrl不能识别?参数或#hashtag 项目开始的时候,web部分是单独开发的,为了以后加入更多模块的扩展考虑,这个项目采用了AngularJs MVVM前端框架和ui-router做页面路由,其实就是单页应用(SPA)。当然,web程序无论采用什么技术,传参数只有两种方式 (1) ?参数形式,如top.html?id=1 (2) #hashtag形式,如index.html#topic/1 如果把程序部署在server端,或者Android的assets目录下,使用Webview访问的方式为

1
2
3
4
mWebView =(WebView)findViewById(R.id.mb_webview);
// String url\_server = "http://www.awebird.com/demo\_project/index.html#demo/123";
String url\_assets = "file:///android\_asset/www/demo_project/index.html#demo/123";
mWebView.loadUrl(url_assets);

我们之前的项目已经大量使用了第一种url_server的方式进行开发,webview loadUrl远程链接也从没有出过问题,所以就顺理成章的开发着,但是等web端做的差不多了,部署到Android工程的assets目录下测试时,遇到一个非常棘手的问题,有一台HTC Android 4.0.3的测试机总是无法打开网页(Page not found) 1 由于Android开发侧的同事不懂Web,我这边对Android的了解也是汗毛级别,所以遇到这种问题的纠结与困惑就不赘述,直接给出“坑#1_desc” Android的issue 17535 (Issue 17535: WebView - URL mechanism is broken - passing parameters does not work) https://code.google.com/p/android/issues/detail?id=17535 打开这个页面,一阵惊叹,反正我是第一次在google groups或者stackoverflow上看到这么多老外对一个bug如此大面积的愤怒情绪爆发,这个密集程度快赶上国内门户网站中国足球新闻下的评论了Orz.. 具体bug不细描述,其实就是Android 3.0以后的webview在访问assets目录下的本地html资源时,带?参数和#hashtag的URL机制不能被识别,上面链接(#148楼)里2012年6月29号有google的开发人员回帖说这个问题已经在Jelly Bean被修复,当然这个只是Android代码的修复,所以现在市面上还是有很多手机的Android版本依然存在这个问题,就像我们不能强迫用户都改用Chrome,不能强迫国行Android用户root手机安装GMS服务包一样,我们也没办法让“找不到网页”的用户换手机或者升级系统,所以问题还是要被解决,下面提供三种方案,我们使用了最后一种 (1)从Android侧扩展webview类进行处理 相关的方案和jar包已经在下面的链接列出 Step1~6即可解决。由于要对项目java部分进行改动,所以我们这边app侧没有采纳次方案,但应该是可行的。多提一句,我这边测试过,Phonegap的开源版本Apache Cordova就已经修复了这个问题 http://bricolsoftconsulting.com/fixing-the-broken-honeycomb-and-ics-webview/ https://github.com/bricolsoftconsulting/WebViewIssue17535Fix (2)loadUrl(‘index.html’)后,再loadUrl一句js跳转语句的方法 这个方法,是得到V2EX的Archangel_SDY童鞋提醒,就是先只载入不带参数的页面(可以显示空白页或者loading效果),在页面载入后(onPageFinished),在loadUrl一句javascript用于跳转页面,下面的locationTo是在index.html写好的js函数,具体内容可以自己控制,只要接收到参数就ok Android侧 (onKeyDown和java interface是用于处理返回事件问题的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
mWebView = (WebView)findViewById(R.id.mb_webview);
String base\_url = file:///android\_asset/www/demo_project/index.html;
mWebView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
mWebView.loadUrl("javascript:locationTo(#demo/"+"123"+"')");
super.onPageFinished(view, url);
}
}
mWebView.loadUrl(base_url);

@Override
public boolean onKeyDown(int keyCode, KeyEvent event){
if(keyCode == KeyEvent.KEYCODE_BACK) {
//点击返回键时
if(mWebView.canGoBack()){
mWebView.goBack();// 返回前一个页面
}else{
finish();
}
return true;
}
return super.onKeyDown(keyCode, event);
}

//Javascript Interface
public class demo\_java\_interface{
/\*\*
\* 关闭当前页面
*/
public void closeCurrectActivity(){
finish();
}
}

Web侧index.html中js (sessionStorge和javascript Interface调用代码是处理手机返回键事件用的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
function locationTo(data_path){
if(!sessionStorage.getItem("once")){
sessionStorage.setItem("once", "1");
location.href=data_path;
}else{
//call demo\_java\_interface.goback()..
if(window.demo\_java\_interface){
window.demo\_java\_interface.closeCurrectActivity();
}else{
console.log('call java to closeCurrentActivity');
}
}
}
</script>

当然,由于我手上没有这个问题的测试机(Android开发在不同城市Orzzzz),其实也不能确认这个方法是否真正有效,因为插入了一个中间页面,所以带来了返回和跳转问题,我们很快放弃了这个方案,继续寻找其它方法 (3)重写onReceivedError()方法 #最终采纳并测试有效版本# 参见 http://stackoverflow.com/questions/6542702/basic-internal-links-dont-work-in-honeycomb-app/7297536#7297536 代码(只支持#hashtag)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mWebView = (WebView)findViewById(R.id.mb_webview);

String url = file:///android\_asset/www/demo\_project/index.html#demo/123;

@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
{
if (failingUrl.contains("#")) {
String\[\] temp;
temp = failingUrl.split("#");
mWebView.loadUrl(temp\[0\]);
try {
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
mWebView.loadUrl(failingUrl);
}
}

mWebView.loadUrl(url);

**【坑 #2】所有Android Webview不能捕获#hashtag变化 Hybrid App中Android侧经常需要捕获webview中的url变化来做一些响应(比如改变header title的文字,处理终端的返回键等等) 但是无论是Server端的Url还是assets中的Url,webview都无法捕获到#hashtag的变化,也就是说index.html#step1 –> index.html#step2 –> index.html#step3 … 这些页面跳转是不能被Android监控到的。 这里提供两个解决方案 (1)在webapp里面使用javascript window.onhashchange里面调用Android的Javascript Interface方法,通知Android hash变化 http://stackoverflow.com/questions/15176519/android-webview-is-it-possible-to-detect-url-hash-change (2)自己在页面跳转逻辑里,根据需要调用Android的Javascript Interface方法实现需要的功能** 比如我这边用Angularjs,就在每个url对应的controller里面,调用Android侧的Javascript Interface方法,后来极端一点,由于不同页面功能点差别较大,直接废弃了web侧的ui-router功能,所有的跳转都交给java处理,也就是每个web页面(包括SPA中的一个状态)都交给Android的一个单独Activity,也就是做Activity的条状,Web侧的router白白浪费了T-T 其实上面的(1)和(2)是同一种方法,Android的Java和Webview里的web交互只有两种办法,一个是url捕获,一个是Javascript Interface,这里在#hashtag不能被url捕获到的情况下,就只能使用Javascript Interface了。 这样处理的一个结果就是Hybrid app里的web部分加入的java接口调用,如果脱离了App环境,要单独处理,或者通过检测Javascript Interface对象是否存在,来决定是采用web方法还是app方法跳转

1
2
3
4
5
6
7
8
9
$rootScope.Java_go2step2 = function){
if(window.demo\_java\_interface_obj){
//如果java interface对象存在,调用Java方法跳转Activity
window.demo\_java\_interface_obj.go2step2);
}else{
//否则,使用web跳转
$state.go('step2');
}
}

总之,不完美… 【坑 #3】Android 2.X Webview不能Scroll滑动 发现有HTML5的页面在Android 4.x下一切正常,在Android 2.X下不能滑动(Scroll)的问题 这个问题无疑是Android的 bug 目前有两个解决方案 (1)Html中去掉meta viewport tag (未经验证)

1
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0" />

http://stackoverflow.com/questions/10552702/cant-scroll-webview-in-android 由于viewport对于移动端的页面适配很重要,前端的同学说不能去掉,试过去掉后样式乱掉了,所以没有采用此方案 (2)Android中的layout布局,将webview布局在ScrollView中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<ScrollView
android:id="@+id/scrollView1"
android:layout\_width="fill\_parent"
android:layout\_height="wrap\_content"
android:layout_above="@+id/tab"
android:layout_alignParentTop="true"
android:background="#00000000"
android:scrollbars="none" >
<LinearLayout
android:layout\_width="fill\_parent"
android:layout\_height="fill\_parent"
android:background="#00000000"
android:orientation="vertical" >
<WebView
android:id="@+id/mb_webview"
android:layout\_width="fill\_parent"
android:layout\_height="wrap\_content" />
</LinearLayout>
</ScrollView>

这是Android侧的方案,上面的布局xml只是一个简单demo,web童鞋遇到了可以跟app开发的童鞋沟通解决。我们采用的是此方案 【—–Scroll问题后续——】 后续开发过程中,Android的同事说不能使用上面的ScrollView布局,因为了整体的布局架构有冲突,所以这个问题再次无解,下面是后ScrollView时代的血泪史 (1)采用iScroll angulrjs可以使用ng-iscroll iscroll可以让webapp乍一看很有原生的感觉,但是我个人认为在webview存在不少问题,而且体验也不够顺畅,除非下拉刷新获取更多数据这种场景,应该尽量不用 使用iscroll后测试确实可以在Android 2.3的手机上Scroll,但是有个问题,是因为我们的页面是先载入,在动态$http.post获取数据,更新页面内容的,所以Scroll的范围特别是高度总是会出现可以滑动到最底部,但是又弹回上面的问题.. 后来分析是isroll的区域判断了初始页面的位置,所以在$http.post的数据获取后,$timeout延时refresh myscroll 上面的方案貌似解决了,但是在webview下又出现了页面闪烁的现象,而且scroll的体验很不好 (2)最后痛定思痛,还是删除了所有的iscroll部分,重新寻找原因,重要发现元凶

1
overflow:hidden

是他,是他,就是他! [原因] Android 2.X不支持这句css [解决方案] 去掉,加上 height:auto [参考] Scrollable DIV on mobile phones 【还在坑里没爬出来的总结…】 通过这个Hybrid App项目的实践,我们遇到了不少麻烦,HTML5在移动端的应用已经基本成熟,但是还有一些问题,让我们再耐心一点,做好准备,等Android特别是webview再成熟一点,等手机终端再快一点,等大家发了压岁钱、年终奖都换了新手机… 很多问题就自愈了(同时一大波新问题随之到来)LOL 本文地址:http://awebird.com/blog/art/190/

使用fiddler调试Native和Hybrid App

14 前面一篇文章《使用fiddler将网站上的css js重定向至本地文件,进行在线调试》介绍过在使用Fiddler调试PC上浏览器运行的Web网站的方法。最近一段时间工作重心转移到移动端,遇到一些和Native App交互,和Hybrid App(包括微信公众号上运行的HTML5网站)的问题调试和解决,这种场景下,平时最常用的chrome控制台完全失效,Fiddler的优越性更体现出来了.. 具体的项目不方便透露和截图,Fiddler的用法前篇文章页介绍过,这边就简而化之,记录下手机连接Fiddler代理的方法,和分别举一个Native和Hybrid的例子说明下 【一】手机连接Fiddler (1)电脑上打开Fiddler Tools-Options-Connections 1 (2)获取本机的局域网IP地址(WIFI) 开始-cmd-ipconfig 2 (3)手机连接同一wifi,长按wifi名,点“修改网络”-弹出高级选项,输入代理ip和端口 3 4 【二】检测Native App 这里以<下厨房>为例 app里的酸辣土豆丝 看到的是这样的 5 Fiddler里看到的数据是这样的 6 当然,并不是所有的app都是这么容易获取到接口和数据的,接口和数据保密做的比较好的app的接口数据还是会加密的(这个貌似webapp先天不足啊) 比如印象笔记EverNote,从接口只能得到这样的数据 7 【三】检测Hybrid App 一个高帅富童鞋在微信里分享了个上海的酒店信息,连七天都住不起的程序猿表示很内伤 11 在Fiddler里是这样的 12 既然是web的,直接在电脑上也能打开url查看了 http://mp.weixin.qq.com/mp/appmsg/show?__biz=MjM5NzIxMzU4MA%3D%3D&appmsgid=10000336&itemidx=1&sign=a2b82a70c4a2873f43bf42f2a81b1feb&scene=1&from=message&isappinstalled=0&uin=ODE1Nzg3MjU%3D&key=6a68cf733852b46cfc334349e3ce065b1fa40aea1e36d2c02d4cff481d0b354c3b5f156714ef47e399d09c5d225f0f74&devicetype=android-16&version=25000027&lang=zh_CN 【结语】 这篇主要是介绍了基本的过程,也留着自己时间长了忘记了(经常发生)回来查阅 ,真正遇到问题时往往要换用不同的设备,查看http头(比如最近遇到一个mobile safari上返回键,form表单post data丢失的问题,就只能使用iphone/itouch设备连接Fiddler,边改程序边分析http的request和response) 好吧,就到这里,总之Fiddler确实是个很不错的工具。好的工具可以协助我们做出更好的产品,个人认为web开发测试都应该掌握并经常使用 本文地址:http://awebird.com/blog/art/161/

基于Android Webview的Hybrid App开发的前端优化

webview 最近做一个项目,是将一个相对复杂(内容后台模块化配置)的mobile web页面嵌入到Android的webview展示,把遇到的问题和一些经验总结下 (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已经有了版本更新,语法也发生了变化,这样用即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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之后加一条

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

就可以了。另外lazyload占位图虽然小,但是最好能提前加载到缓存,这样页面显示的时候高度不会突变,把不同宽高比的占位图放在不显示即可

1
2
<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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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开启和设置

    1
    2
    3
    4
    5
    //下面3个是跟浏览器缓存Cache相关的,一个页面的 图片\\js\\css 载入过之后 //在服务器设置的文件有效期内,每次请求,会去服务器检查文件最后修改时间,如果一致,不会重新下载,而是使用缓存

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //下面是跟浏览的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);
        }
    }
  • 浏览器自带缩放按钮取消显示

    1
    2
    3
    //这个是跟浏览器的页面缩放相关,不用显示浏览器的放大缩小按钮,这个一般在最下面出现,体验不好

    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相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
<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/

原wordpress评论

使用fiddler将网站上的css js重定向至本地文件,进行在线调试

8 这是一篇写给公司负责切图和调样式的前端的文章。主要适用于一个项目临时需要调整下css或者js,为了一点修改让前端人员在本机搭建完整的LAMP环境,导入DB数据,确实很不方便,特别是像我们这样前后端在地理上就分离在两个城市的团队。这里是使用强大的Fiddler2,以修改本博客网站的云标签插件的样式为例。 cloudtag1 1. 下载安装fiddler2,点击运行 http://fiddler2.com/get-fiddler 一般选择fiddler2就可以了 2. 在浏览器打开需要调试的页面,比如,如果有浏览器缓存,可以ctrl+F5强制刷新获取最新版本 任一浏览器打开http://www.awebird.com/blog/,这也是fiddler相对firebug和chrome开发工具的一个优势,它是与浏览器无关,IE甚至opera mobile emulator这种手机模拟器都可以。注意浏览器如果有代理插件的话,fiddler可能检测不到,比如chrome的SwitchSharp,需要选择“使用系统代理设置” 3. 进行上一步的时候,发现fiddler2中已经有一串串优美的http请求和回应呈现出来了,请选中准备调试的文件 这里很容易辨别出是下图这个cirrusCloud.css,(如果你不洁身自好的装了360之类的“安全软件”,还能时不时发现自己的隐私数据是怎么被悄悄上传的) cloudtag2 有两个地方要注意下

  • 调试过程中不断修改js css上传到服务器,重新进入或者刷新页面很有可能使用的是浏览器的旧的缓存版本,这个时候应该先清下缓存,或者多按几次ctrl+F5强制刷新。看到上面的http result是200的话说明是从服务器获取的最新版本
  • 一般来说服务器端会对js css进行gzip压缩传输,这种情况下,可以点击右侧的黄色提示条解压后就可以看到文本的css内容了

3 4. 在右侧的的AutoResponder选项卡选中 下图所示的两项,点击 Add Rule 4 5. 在右下侧的Rule Editor选择“find a file”, 弹出文件选择器,定位到svn本地目录下的相应css文件 (这里是指开发方式,可以直接定位到svn或者git的client文件夹的对应css文件),这里其实就是一个重定向,当网页渲染请求服务器上的http://awebird.com/blog/wp-content/themes/twentytwelve/style.css?ver=3.6文件时根据这里的设置被重定向到本地的文件(如果本机没有,也可以根据上面的链接直接从服务器下载再重定向,也可以在fiddler邮件save and open as local file),比如C:\AppServ\www\myblog\cirrusCloud.css 4 在右上窗口看到新增了一条记录就是服务器到本机的重定向映射规则,以后可以随时添加和删除这些规则 5 6. 经过上面的操作就可以实现修改本机的css文件,调试浏览器里远程环境的效果了 比如在css中加入如下两条

1
2
3
4
5
6
7
8
#cirrusCloudWidget a{
color:green;
}

#cirrusCloudWidget a:hover{
border: 1px solid red;
color:red;
}

刷新页面,可以看到云标签的字体变成绿色,被选中的标签变成红色 6 7. 就是这么简单 注意,你的修改并没有真的上传到服务器,别人访问网站看到的还是灰色的标签,如果本地映射的就是版本库的话,这时就可以svn commit或者git push,然后等运维更新到服务器环境就可以了。 本文地址:http://awebird.com/blog/art/108/

原wordpress评论

php curl返回400 bad request的问题定位与解决

oops 今晚花了整整一晚的时间帮同事定位一个php curl返回400 bad request的问题了,@七夕Orz.. 是一个网上流传的模拟登陆163邮箱、获取通信录的代码段google,在同事和我的开发机上都运行正常,但是部署到服务器环境上就400了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//跳转
$url = 'http://entry.mail.163.com/coremail/fcg/ntesdoor2?lightweight=1&verifycookie=1&language=-1&style=-1&username=loki_wuxi';
$headers = array('User-Agent' =>
'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/2008052906 Firefox/3.0');

$ch = curl_init($url);
curl\_setopt($ch, CURLOPT\_RETURNTRANSFER, true);
curl\_setopt($ch, CURLOPT\_HEADER, true);
curl\_setopt($ch, CURLOPT\_CONNECTTIMEOUT, 120);
curl\_setopt($ch, CURLOPT\_POST, true);
curl\_setopt($ch, CURLOPT\_HTTPHEADER, $headers);
curl\_setopt($ch, CURLOPT\_COOKIEFILE, COOKIEJAR);
curl\_setopt($ch, CURLOPT\_COOKIEJAR, COOKIEJAR);
$result = curl_exec($ch);
curl_close($ch);

由于两个开发环境都是windows,服务器环境是linux,所以至少花了一个小时走弯路,在COOKIEJAR文件的路径和权限上。未果 病急乱投医的试遍了网上的各种方案,最后还是沉下心来分析http header,php curl输出完整的request header,需要如下设置

1
2
3
4
5
6
7
$ch = curl_init($url);
curl\_setopt($ch, CURLOPT\_HEADER, true);
curl\_setopt($ch, CURLINFO\_HEADER_OUT, true);
curl\_setopt($ch, CURLOPT\_NOBODY, true);
$result = curl_exec($ch);
print\_r(curl\_getinfo($ch));
curl_close($ch);

对比本机和生产环境的request_header,发现主要区别在于

1
2
本机的  Content-Length: 0 Content-Type: application/x-www-form-urlencoded
服务器 Content-Length: -1 Content-Type: application/x-www-form-urlencoded Expect: 100-continue

最终根据这个线索在StackoverFlow上找到这篇Testing PHP CURL file uploads on laptop (blocked by Norton)

It looks like I had curl_setopt($CURL, CURLOPT_POST, TRUE), and that wasn’t a good idea. I removed it, and Norton doesn’t complain and the script works.

把下面这行注释了就ok了,确实在没有CURLOPT_POSTFIELDS的情况下post内容为空,开启CURLOPT_POST没有意义

1
//curl\_setopt($ch, CURLOPT\_POST, true);

但是,还是要纠结一下,为什么在本机测试ok了,是不是windows和linux环境的却别?刨根问底的继续查了下,终于找到问题的根源,原来是curl的版本不一样,我本机是libcurl/7.16.0,服务器上是libcurl/7.27.0 详细解释参见 http://sevalapsha.wordpress.com/2011/08/03/curl-http11-empty-post-bug/

In the end we discovered that newer version (since 7.20) of cURL interprets missing body as a negotiation request – sends Expect: 100-continue header and Content-Length: -1.

curl官方的log http://curl.haxx.se/changes.html#7_20_0

Fixed in 7.20.0 - February 9 2010
Changes:
send Expect: 100-continue for POSTs with unknown sizes

总结一下,就是这些.. (声明下,这篇文章的除了最初同事的代码片之外,所有的引用都是英文,其中一篇还需要自备梯子才能看,这里不是得瑟什么,就是这么个情况,解决问题的有效渠道就是google groups和stackoverflow) 洗洗睡了,某娜快回来了,希望明年七夕不要再解bug.. T-T 本文地址:http://awebird.com/blog/art/99

原wordpress评论

通过修改apache配置文件和HOSTS在本机测试QQ互联登录的方法

manle_qq 在qq互联登录的开发中会遇到一个很麻烦的问题,就是遇到连不通的情况怎么进行调试?因为qq互联是需要提供网站的信息注册后分配appid等才能使用的,callback参数中需要指定返回到注册网站,简单的说,只有在域名所在网站的“生产环境”或者“现网环境”才能进行调试。如果生产环境不允许开发人员接入或者“流程很繁琐”的话怎么办?本文介绍了通过修改apache配置文件和修改HOSTS进行本机测试qq互联的方法。 假设申请qq互联登录的生产环境的域名是 www.awebird.com 本地的网站根目录(apache)的路径是 C:\AppServ\www\awebird (1)修改apache的配置文件 C:\AppServ\Apache2.2\conf\httpd.conf (修改前先备份)

1
<Directory "C:/AppServ/www">


1
DocumentRoot "C:/AppServ/www"

这两行分别修改为

1
<Directory "C:/AppServ/www/awebird">


1
DocumentRoot "C:/AppServ/www/awebird"

修改完后需要重启apache,如果是appserv集成包的话可以到 开始菜单-所有程序-AppServ-Control Server by Service-Apache Restart 进行重启 (2)修改HOSTS文件 C:\Windows\System32\drivers\etc\HOSTS 最后增加下面两行并保存

1
2
127.0.0.1 awebird.com
127.0.0.1 www.awebird.com

(3)修改程序的配置文件 经过如上两步操作之后,可以实验下在浏览器输入www.awebird.com, 就是本机的127.0.0.1/awebird/ 的网站内容,当然对于大部分的系统来说,还需要改一下配置文件,比如C:\AppServ\www\awebird\config.php中的

1
$site_url='http://www.awebird.com';

修改为

1
$site_url='http://127.0.0.1/awebird';

上面的修改和文件夹路径只是举例,请根据实际情况修改 (4)最后 经过以上操作,就可以在本机测试qq互联这种需要依赖实际域名的功能了,是不是很方便。 当然,测试完成后不要忘记把上面的都修改回来,否则在本机就无法“真正”的访问到实际域名的数据了 本文地址:http://awebird.com/blog/art/86

使用svnsync在windows下同步svn版本库来“极速”showlog

Little_Turtle_Fly_Away 虽然相比如日中天的git来说早已是明日黄花,但由于历史或团队的原因,svn还是目前应用最广泛的版本控制管理工具。随着日积月累的修修改改,我们会越来越倚重于svn的showlog功能来查看某一段代码的修改记录,但是svn的所有历史版本和这些修改记录都存在服务器上,client端只有一个最新update的版本,这样如果处于离线状态或server在内网,还有像我们现在这样开发团队在不同的城市的话,那么showlog的缓慢体验一定很糟糕。这里介绍一个使用subversion的svnsync工具在windows本机同步svn版本库的方法,从此你就可以‘脱机’顺畅的showlog了..

  • 安装Subversion

使用svn的话,你本机一定安装了类似TortoiseSVN这样的客户端工具,但是使用本文的方法,还需要安装subversion,这是windows的SVN服务端工具。可以到下面下载安装,具体过程不再详述 http://subversion.apache.org/packages.html#windows

  • 新建一个文件夹用于建立本机的版本库

比如 E:\svn_server\demoProject

  • svnadmin create命令新建版本库

开始菜单,输入cmd,进入命令行工具

1
svnadmin create e:\\svn_server\\demoProject

这时打开demoProject文件夹发现SVN server的目录结构和一些配置文件已经生成

  • demoProject/hooks下新建pre-revprop-change.bat

新建一个空文件,然后重命名为pre-revprop-change.bat即可

  • 初始化,与远程svn库关联

使用的命令如下,注意 e:/svn_server/demoProject ——————————是本地svn版本库目录,http://211.111.222.333/svn/project/demoProject/ —–是需要同步的项目的远程版本库目录

1
svnsync init file:///e:/svn_server/demoProject http://211.111.222.333/svn/project/demoProject/

  • 使用svnsync sync同步全量版本到本地

使用下面的命令,就可以把远程的版本库从0版本到最近版本事无巨细的全部同步下来,甚至包含用户的鉴权信息和所有历史版本。以后使用同样的命令也可以随时用来同步更新至最新版本。如果版本库庞大或者网络不畅,这个过程将持续较长时间,请耐心等待

1
svnsync sync file:///e:/svn_server/demoProject

  • 客户端Checkout

这个没什么好说的,就是使用你用来导出或者连接远程svn的客户端,比如TortoiseSVN,建立新文件夹(比如e:/svn_client/demoProject),checkout本地的svn版本库,注意版本库路径使用 file:///e:/svn_server/demoProject 即可,这样以后就可以“极速”showlog所有的历史版本和修改日志了,用来定位问题“狠”方便哦!!! 【注意】提醒一下,这个svn_client仅仅用来update和showlog,千万不要commit,否则会破坏同步的本地版本库,如果误操作了的话就需要删掉再把上面的所有步骤重新来过一遍了T-T 原文地址: http://awebird.com/blog/art/60