移动互联时代

PhoneGap应用开发的那些坑爹事儿

子曾经曰过:如果你恨一个人,让他去开发PhoneGap应用;如果你爱一个人,让他去开发PhoneGap应用。

去年这个时候我很烦恼,因为我觉得我OUT了。

起因是我买了一台Android系统的手机。当我用自己的google账号登录上去后,我发现通讯录被同步了,Gtalk实时通知了,Gmail随身能看了。还有成百数千的应用,让我足足玩了一周。

电脑占据了用户每天8小时的时间,网站创造了巨大的价值。但是,手机这东西却能够24小时不间断的陪着用户,消息推送机制更是让用户变得永久在线,随时可响应。我很兴奋,但是却高兴不起来。

我是一个Web程序员。我喜欢写PHP,喜欢处理Mysql,我能用CSS和JavaScript构建不错的页面。但是我不喜欢Java,也不喜欢写Object C,就像那帮做客户端开发的同学不喜欢写网站后台一样。

这样的沮丧持续了挺久,直到我遇到了PhoneGap。其实我有想过通过Mobile Web的方式进入移动互联网,但是在手机上输入网址的体验太折磨了。另外消息推送和本地设备接口(比如摄像头)都是问题。而PhoneGap解决了这一切。简单的说,它就是一个提供js接口调用本地设备接口的浏览器,这个浏览器还可以打包成一个单独的应用,安装到系统,添加到桌面和发布到应用市场。

由于完全基于浏览器,PhoneGap还有一个好处,可以很轻松的跨多个平台。

基于PhoneGap,我用3天的时间,给TeamToy写了一个手机客户端,可以读取工作组的通讯录,可以浏览Feed和发布广播。当然,还能收通知。

虽然算只跳舞的熊,但它的确能工作。这让我不禁想,这东西到底能做什么程度的应用?

PhoneGap只是一个壳,它是不管里边跑的应用长什么样子的,更不会有那些移动设备上常用的控件。

好在有其他的项目提供支持,其中最有名的是两个。一个是sencha touch,一个jquery mobile

于是我都试用了下,然后我明白了苦头在后边⋯⋯

当时这些项目都还属于发展期,连文档都没什么,需要自己去读DEMO和源代码。

首先我尝试着用sencha touch写了一个微盘的第三方客户端。坦白的说,sencha touch表现不错,在菜单导航和列表拖拽上都明显比Jquery Mobile靠谱。但是sencha touch采用JS来描述整个page的结构,这让你的layout看起来就像一堆json数据。

我很讨厌这样,因为这种级别的可读性让项目很难维护。另外,sencha1版本Bug也不少,比如那个List最后一行能拖拽出来,却永远点不到的问题。现在sencha已经在测试2版本了,据说提供了直接打包成本地应用的工具,这样也许就不需要PhoneGap了。

 

由于对Sencha Touch的编码方式不认同,我完全转向了Jquery Mobile。JQuery Mobile的方式更容易接受,它完全使用HTML标签进行页面布局,你只需要通过data-role之类的标签来告诉JQM你想干什么就OK了。我用JQM写了Riki.co的移动版,有兴趣的同学可以去体验下:http://riki.co/m/ (这个东西有很多问题,下边我会提到)

JQM最近已经1.0了,成熟了不少,但是问题依然很多。首先是底导航浮动的问题。JQM的底导航实现很诡异,丫是一个浮动图层,根据当前屏幕的高度,把自己降到屏幕最下方,当你拖动页面内容时,这个浮动条还会隐藏。这个效果有多么的BT,真是谁用谁知道。没用过的同学可以到这里去体验下官方版本: http://jqmdoc.sinaapp.com/docs/toolbars/footer-persist-a.html

这其实还不是最难受的,最难受的是,当你把JQM打包到PhoneGap里边后,由于未知原因10次里边JQM能有2~3次取不到浏览器高度,直接造成导航挂掉。

这个问题其实iScroll4 很好的解决了,它使用了相对距离的CSS来固定顶导航和底导航。所以后来我把它给整合到JQM里边,然后天下太平了。

iScroll4还实现了列表拖拽刷新的功能,可以做出iPhone上常用的下拉刷新。


然后是页面切换的问题。JQM和Sencha其实都有,就是在android系列机器上,页面切换时的抖动和闪烁。最开始你注意到的时候,是切换的瞬间页面反白一下,这个问题其实可以解决,用一行CSS就可以了:

但是,代价是惨重的。首先是会导致性能降低,然后是,当你切到从页面A切换到页面B时,页面A的内容会突然在滑动效果结束后,再闪一下。亲,这是坑爹啊还是坑爹啊。还没完,如果你用的JQM,然后在android系统上用百度或者QQ输入法,那么恭喜,当你在输入文字时,页面会上下不停的抖动,就像ZF要强拆它家似的。

JQM通过Ajax载入的页面都有这个问题,你可以选择关闭Ajax载入,这样每个页面都独立载入,除了速度慢点外,不能忍受的是列表滚动条进度会丢失。这意味着你在列表中部选中的一条内容,查看完后,返回列表时,列表又回到第一条去了。

最后我选择了采用DIV切换的方式。我会在一个页面把所有要用的Page都载入进来,然后display:none;在需要使用的时候再显示出来。这样做的问题是一次读取了较多的内容,会稍微多占点内存,另外没有动态切换效果;但好处显而易见:页面之间的切换速度已经接近原生应用。

我想说的是,JQM是个好框架,但它并不是完全为在PhoneGap里边跑的HTML设计的。它需要考虑资源载入速度和流量的问题,而PhoneGap这种应用资源都放本地的;它在浏览器上可以不断的刷新页面,所以它不用考虑太多内存管理和DOM回收的事情,而对PhoneGap里边的应用来讲,这直接决定了应用的稳定性。

这些差异决定了一个专门为PhoneGap这种应用设计一个框架是必要的。而目前没有一个好的框架解决掉我提到的这些问题,我们只能小心的绕过这些一个个的雷点。( 如果你愿意写一个这样的框架,我可以提供一个新浪的全职职位 :) )

虽然我吐了一肚子的苦水,但是最终结果是很不错的,我现在已经能以天为单位开发一些社交和资讯类应用了,而这些应用可以直接运行在iPhone,Android和芒果上。

更好的消息是你不用再被折腾一遍,因为我有个东西给你。

那就是我在历经折磨后,做出的一个叫做LazyMobile的应用模板。

它其实就是一个已经写好的应用,你可以直接修改它,添加自己的功能。它看起来是这个样子的:

这东西基于JQM和iScroll。

首先它用iScroll4解决了底导航固定的问题,在android和ios上都很完美,下拉刷新也是可以用的。

然后它通过Tab切换来载入其他页面,效果接近原生应用,但是不要在一个List里边放太多的数据,少放点,动态加载和实时回收item项。我已经做了一个两级导航,应该够用了,微博客户端也就用到二级导航。

它用JQuery的ajax函数从服务器端读取json格式数据,然后用jquery.tmpl进行渲染,目前这个地方会消耗不少CPU,但暂时没有更好的办法。

另外,它还用localStroage存储了上次网络请求回来的json数据,这样在网络断开时,依然可以显示内容。

这个项目完全是一个副产品,所以我不保证会维护它。不过你已经知道了它的大多数细节,我相信Web程序员都能很好的使用它。项目地址:http://code.google.com/p/lazymobile/

 

PS: 我的微博ID @Easy ,这应该是找到我最快的方式。

 …

一个简单的基于PhoneGap的开源微博客户端

因为新浪云的一些变更,下边这种获取Token的方式已经不能用了,建议大家用新方案 https://github.com/qhm123/tinybo

 

有人说PhoneGap做不了复杂的东西,比如微博客户端。这话对,也不对。因为光是用PhoneGap的标准接口是没办法做OAuth认证的,页面转换几次就不知道跳到哪里了;但是别忘了,PhoneGap拥有强大的插件系统。在GitHub上的PhoneGap插件库中,就有Facebook之类的OAuth2插件。

从4月18日起,新浪移动云的打包器和调试器开始支持微博插件,这让微博客户端开发变得异常简单。

于是我做了一个特别简单的微博客户端(只有登录和读取Feed和@我),然后把它放到了GitHub上

先放个视频看看效果

这篇文章主要讲怎样把这个简单的客户端换成你的key,让它工作起来,然后你可以以它为基础,修改成你想要的各种客户端。

在继续之前,请更新你的云窗调试器到最新版本。(目前只支持Android,iOS版本测试中)

创建微博开放平台应用

首先进入微博开放平台,创建一个客户端应用。

填好基本信息后,就可以创建一个移动客户端应用了。

创建成功后,进入应用的【基本信息】页面,可以看到app key和app secret。

因为PhoneGap打出来的包很容易被人反向工程,找到里边的key,所以新浪移动云的微博插件里边不直接使用这两个key。

新开一个窗口,进入云窗key加密页面。(稍后这个页面会整合到SAE去)

输入appname,和微博应用【基本信息】页面的两个key值,可以得到新的两个key。把它们记下来。稍后会用到。

然后回到微博开放平台,进入应用的【高级设置】,在【回调地址】页面填上http://appname.sinaapp.com

这个步骤非常重要,很多同学都是因为忘了填写而导致授权失败。

三个必须的东西:

  1. 加密后的App Key
  2. 加密后的App Secret
  3. 填到应用高级信息里边的回调地址

创建SAE移动应用

进入SAE,创建一个移动应用,代码模板选择【默认模板】。

成功创建以后,代码目录会出现一个client文件夹。

获取代码

你可以通过git或者下载链接获取最新的Demo代码,然后打开js/app.js文件,将129行开始的内容换成前文中得到的对应的值。

修改完成后,将Demo代码传到SAE代码目录的client文件夹,然后你就可以通过【云窗调试器】进行调试了。

插件接口简介

这里简单的说下微博插件用法。微博插件在js中的对象为CDV.WB,它有6个方法,分别是:

  1. CDV.WB.init – 初始化
  2. CDV.WB.login – 登录
  3. CDV.WB.logout – 终止会话
  4. CDV.WB.get – get请求
  5. CDV.WB.post – post请求
  6. CDV.WB.upload – 带文件的post请求

详细的范例可以查看【简微】客户端中js/app.js。SAE的官方说明文档已经放出,点这里

 常见问题

1 如果点击登录按钮,没有出现loading… 提示信息的,请确认使用了本文里边提供的【云窗调试器】。其他地方的可能不是最新的,包括sae页面上的。

2 如果提示  /oauth2/authorize”, error_code: 10001, error: system error , 其实就是appkey和appsecret错误,请再次检查app.js中输入的加密后的appkey和appsecret,以及appname是否正确。

3 如果提示 invalid redirect url,是因为app.js中的回调地址和open.weibo.com的应用高级信息页面中的回调地址不一致。

4 如果提示 sorry  that page not exists ,也是因为回调地址的问题,记得要加 http://。

5 如果授权后进入微博列表页面,load后又跳回登录页面了,那是因为你的微博应用没通过审核只能用应用创建人的微博账号测试,要用别人的账号需要在open.weibo.com的应用高级信息页面添加测试用户。

 …

PhoneGap开发不可或缺的五件装备

PhoneGap是一种介于WebApp和NativeApp之间的解决方案,它为每种移动客户端提供一个Native的壳,这种壳里边包着一个Web应用。借助于壳,Web应用可以被安装,可以被发布到各大市场。同样的,借助于壳和设备之间的通信,壳内的Web应用可以轻松调用设备硬件。虽然目前想用PhoneGap开发商业应用还有很多的坑要填,但其跨平台和低学习成本的特性决定了未来它在移动互联网领域必有一席之地。

1 轻量级的JQuery兼容库

JQuery已经成长的得非常强大了,但在移动设备上有些臃肿。如果你的应用只运行在WebKit内核的机器上,那么我强烈建议你换用移动版本的JQuery兼容库。

ZeptoJQ.mobi都是专门为移动设备优化后的JQ兼容库。

 

Zepto我不是特别熟悉,JQ.mobi我用得比较多,官方提供的数据称,JQ.mobi比JQueryMobile快3倍以上。

相比于JQueryMobile悲催的定导航底导航固定功能,JQ.mobi也提供了自己的UI。虽然和JQM差不多丑,但不会出现万恶的导航栏闪动问题。如果你自己不想构建UI框架,那么还是推荐用JQ.mobi的UI库,换换配色和背景,也能看起来蛮专业的。

另外JQ.mobi还提供了一系列的插件,但是都没有提供文档 。里边的scroll插件不如iScroll4给力,模板插件还可以用。这里讲下用法。

要使用JQMobi的插件,只需要简单的用script载入对应的文件就行。

JQ.Mobi的模板来自这里(这个链接要从源代码中才能翻出来,藏那么好干嘛啊…)是一个使用%号进行标记的系统。

模板部分采用script标签包含:

从上边的例子中可以看到,这套模板可以轻松支持 if/for/else等语法;数组和对象也没有什么问题,可以满足绝大部分需求。

它采用$.tmpl函数将数据传入模板,得到渲染后的html。需要注意的地方是传数据时要以对象的方式来传,同时为传入的数据指定变量名。

2 滚动效果专家:iScroll4

虽然现在JQ.mobi已经解决了导航栏固定和滚动的问题,但是iScroll4依然是非常值得使用的。

在使用iScroll时有几个需要注意的地方。

首先,iScroll采用css的top和bottom值固定了顶导航条和底导航条的高度,可以修改css来调整。

其次,iScroll是静态的,就是说,它并没有随时查看scroll里边的内容是否增加,这就要求我们在动态加入内容后,显示调用scroll对象的refresh方法。

最后,iScroll4能很好的实现iOS用得很多的下拉刷新效果,这个页面上有完整的代码。

3 现代浏览器:Safari/Chrome

在手机浏览器上调试之前,先在电脑浏览器上开发会快很多,建议用safari或者Chrome。这东西虽然大家都有,但有些细节还是潜藏很深的。

比如用Chrome调试的时候你可能会遇到跨域的问题,只要在启动Chrome.exe时加上一个参数就可以了。

chrome.exe –disable-web-security

另外有些同学可能不知道,PhoneGap API页面上的LocalStorage和Indexed DB 其实是Html5的标准接口,所以Chrome和Safari中的自带的调试工具就能很好的管理。

打开Developer tools,选择 Resources Tab页就能看到数据库,本地存储等选项 。( 当初我找了好久Chrome的Sqlite管理插件…)

4 PhoneGap实时调试工具:云窗调试器

虽然有点夸张,但不少网友是用PhoneGap神器来形容它的。

本质上来讲,云窗调试器就是一个支持PhoneGap Javascript接口的移动浏览器。

它和新浪SAE的云平台紧密结合,需要用SAE账号登录,这样当你在SAE上创建一个移动应用后,就能直接在云窗调试器的应用列表页面看见。

点击应用名称后,就可以开始调试应用了。由于代码放置在SAE平台上,我一般用浏览器上的在线编辑器修改代码,Ctrl+S后直接在手机上看效果。

一次变更两秒看到最终结果,比起本地编译,差不多一次能节省半分钟的时间。

云窗调试器iOS版本支持iPhone和iPad,不过还没发布到App Store,如果你的设备已经越狱,可以直接在设备的safari里 点这个链接进行安装

5 在线打包工具:PhoneGap:Build

作为一个跨平台方案,PhoneGap也提供了一个云打包器,利用它我们可以不用安装任何开发环境,直接将HTML打包成iOS,Android,Windows Phone,WebOS,塞班和黑莓6个手机系统的安装包。

PhoneGap:Build 的使用很简单,注册一个PhoneGap账号然后上传代码的Zip包就可以。当然,你也可以直接从git/svn取代码。

需要注意的是PhoneGap:Build的打包是收费服务,对private app有个数限制(我暂时还没遇到,不过看官方的说明是有限制的)。另外PhoneGap:Build上iOS的打包需要上传证书,否则不能打包。

如果你只是需要打Apk包的话,也可以使用SAE提供的在线打包器,这个打包器是完全免费的。另据小道消息,iOS无证书打包器已经在调试中了,预计月底上线。

最后提供一个懒人福利:文章中提到的东东打包下载

新浪移动云上线,发布PhoneGap在线调试工具

今天新浪移动云平台终于和大家见面了。广告我就不多做了,稍后会有相应的推广和教程出来。发表点纯个人的感叹,做这东西挺不容易的。

感谢这几个月来天天被我折磨的@围__城 和 @草丁  同学,硬是被我逼着去学了HTML5 🙂

除了和build.phonegap类似的打包器(目前只支持android版本,ios正在测试中),我觉得最有用的就是云窗调试器了。

我在用PhoneGap写应用的时候,一般都把调用本地的功能放到最后写,因为没有办法在浏览器中调试。每回都得打包调试。

现在特简单了。去SAE上创建一个移动应用,然后安装一个云窗调试器在手机上。一边写代码一边调试,各种爽啊各种爽。

流水账就记到这里,虽然大幕刚揭开,但也算对自己这半年的努力有一个交代。睡觉去鸟。如果你睡不着,这里是移动云的文档。

5分钟搞定你的Rest Server

在写了快10个Rest Server后,我实在无法忍受了。我是真心不想写Rest Server了。这并不是单纯的偷懒,而是因为Rest Server的开发工作实在很无趣。数据表的增删改查,输入和输出过滤。然后重复另一个表,再重复另一个表。

这都2012了啊,居然把宝贵的时间花在这么无聊的事情上。为了省下时间挣船票钱,我自己动手实现了一个Rest Server。它就是LazyRest。

先来看看它的特性:

  1. 对于常见的增删改查接口,不用写一行代码。只需要在Web界面上点点鼠标就好了。
  2. 对于基于token的接口认证,也不用写一行代码,只需要选中一个checkbox。
  3. 每个接口提供I/O过滤功能,你有机会直接过滤输入和修整输出。
  4. 对于不常用的特殊业务逻辑接口,你可以在浏览器上直接编写该接口的代码。
LazyRest的核心理念是
数据库+元数据 = Rest接口
LazyRest的工作方式是
读取数据库信息,让开发者指定对应的元信息;
通过元信息直接实现Rest接口,当元信息发生变动时,无需重新编码,接口直接可用

对关心效率的同学说一句,目前是实时拼接代码并执行,以后的版本会提供代码生成功能。

下边我们就以一个实例来看看,用它做Rest接口有多么快。

需求

我们先伪造一个需求:领导要求你花一天时间做一个手机版本的部门通讯录出来,你用LazyMobile的空白模板做好了前端显示,但是时间已经过去了半天,为了晚上不用加班(这样你才能陪妹子去逛街),你决定使用LazyRest来构造Rest Server。

步骤零:安装

为简化安装和方便调试,目前只放出LR的SAE版本,可以在SAE应用商店直接安装。

步骤一:建立数据表

首先,我们规划下MySQL的数据表,表名就用contact,字段包括姓名-name,email,手机号 – mobile, 家庭住址 – address, 邮编 – zipcode, 工号 – company_id。

用phpmyadmin建好这个表。

步骤二:配置接口

然后登录进入LazyRest首页。

很帅的,contact表已经被列出来了,点击【设置】进入接口设置界面。

这就是LazyRest最常用也是唯一的设置界面了。表格横向列出了所有数据库的字段;纵向列出了四个标准接口。

Insert 接口

首先我们开启Insert接口,点击Insert 下边的【设置】链接。

划上【开启】和【无需认证】两个复选框。

接着我们来指定输入字段。点每个字段表格中的【设置】进行指定。除了id以外,其他的字段都是输入字段,其中name,email和mobile是必填字段。配置完成后应该是这样的:

然后我们来指定返回字段。LazyRest默认采用twitter和weibo接口类似的规则,insert后会返回插入的那条记录信息。包括id字段,都是我们需要的。于是继续配置:

到这里,insert接口已经可用了。是的,我们没有写一行代码。点击粉红色的Insert链接,会进入这个接口的访问地址。

在LazyRest中,所有的接口都是按这个规则进行访问的:

LazyRest的URL/api/表名/接口名/字段1=值1&字段2&值2

在上边的图中,由于name字段被指定为必填,所以接口输出了对应的错误信息。我们来构造一个合法的输入

http://lazyrest.sinaapp.com/api/contact/insert/name=Easy&email=easychen@gmail.com&mobile=119

欧也,成功了。

步骤三:输入过滤

但是明显伪造的手机号码被输入了数据库,这显然是领导不愿意看到的。这个时候,我们用【I/O过滤】功能来处理。在【Insert】接口链接下方,【设置】链接旁边,就是我们的【I/O过滤】。

这里我写了几行简单的PHP脚本,检查mobile字段的长度是否为11位(懒得写正则了,那不是这里的重点)。

if( strlen($_REQUEST[‘mobile’]) != 11 )
return $this->send_error( 1002 , ‘BAD MOBILE NUMBER’ );

代码很简单,在输入过滤部分,我们可以通过$_REQUEST变量访问和修改输入。输入格式检查和转换都可以在这里做。在所有可以编写代码的地方,都建议使用LR的两个标准函数来处理返回值。

错误的情况使用:

$this->send_error( 错误码, 错误详细信息 );

正确的情况使用:

$this->send_result( 返回数据数组 );

保存后,我们再访问刚才的接口:

OK,接口已经能识别出位数不对的手机号了。

非常简单,而且快速。最重要的是你不用写那么多重复的代码了。

内置标准接口详解

下边我介绍下剩下的几个重点接口。

List接口

这个接口用于列表,它除了接受配置的字段外,还接受三个额外参数:max_id,since_id,count。熟悉微博接口的同学都很清楚他们的作用:

  • max_id: 从max_id开始(不含)返回比max_id小的记录,共count条。多用于获取更早数据的场景。
  • since_id:从since_id开始(不含)返回比since_id大的记录,共count条。多用于获取更新数据的场景。
  • count:返回记录数,默认为10,最大为100。

List接口的返回值默认为一个多维数组,其中items是命中的数据项,max_id和min_id项标示了返回的数据中最大和最小的id,方便追加读取。

如果不喜欢,可以通过输入过滤进行调整:

代码就一行,调整了下$data数组的结构:

if( isset( $data[‘items’] ) )  $data = $data[‘items’];

这就是I/O过滤模块的方便之处,因为能编写自定义代码,所以能处理所有情况。

Update和Remove接口

这两个接口比较正常,没有太多可说的,就是注意下记得配置好【%】和【=】,不要误删了数据⋯⋯

从SQL角度说明下:选上了【%】和【=】的字段只会出现在WHERE子句

这里给一个配置完成的典型设置:

自定义接口

点击标准接口表格下方的【添加自定义接口】就可以。注意接口代码不需要带上<?php 标签。I/O过滤部分也是。自定义接口可以处理复杂的业务逻辑,比如联表查询之类。

用户认证

开启用户认证

划上【接口设置】中的【用户认证】复选框即可。

获取token

LR默认【user】表为权限验证表,采用【用户名字段】+【密码字段】MD5进行匹配,成功则返回token。

其中【权限表】,【用户名字段】和【密码字段】在配置文件: app/config/app.config 中可修改。

获取token的接口地址如下:

LazyRest的URL/api/权限表/get_token/account=账号&password=密码

建议通过https访问,SAE上默认可用。开启了【用户认证】的接口,在请求中需要带上token,如:

http://lazyrest.sinaapp.com/api/contact/list/token=2314fdef32

其他注意点

LR是一个通过约定来简化开发的系统,所以有一些需要注意的约定:

  1. 每个数据表都应该包含id字段,LR的List接口对这个字段有依赖
  2. 不要用以下字符作为字段名: m,a,_action,_interface,count,max_id,since_id。
  3. 数据库中有__meta_code,__meta_user两张元数据表,不要删除,其中__meta_user中定义了登录LazyRest管理界面的用户。

最后说几句,LR目前刚写完,还需要一段时间的测试,不建议直接用于生产环境的大项目。等稳定后,我会加上代码生成功能,以处理高压力的情况。由于开发时间有限,所以很多交互细节没有深究(比如设置页面的checkbox之类),待以后逐渐完善吧。…