随便搜索一下“Android vs. iOS”,都会出现很多关于哪个平台更好的争论,大多数的争论点都是关于市场占有率、易用性和设备分化等问 题。当然也有一些“以开发者的角度”去比较这两个平台的文章,但是很少有从技术上做深入的比较,通常也只是用一个简单的示例应用介绍一些基本的特性。缺少 这种深入的比较其实是有原因的:一个公司要做一个足够复杂的移动应用,通常需要一个人或团队做Android,另外一个人或团队做iOS。这两个平台使用 不同的编程语言(Java和Objective-C),提供不同的SDK,使用不同的开发工具,所以人力资源分配上各做各的平台也就不奇怪了。
GQueues是一个在线任务管理器,之前只有一个HTML5版本。最近我完成了GQueues for Android 和GQueues for iPhone & iPad 的开发。虽然这两个应用的复杂程度不能和***人称射击游戏相提并论,但也绝不简单 – 为用户存储和管理数以千计的任务信息、支持多账户、提供到WEB端 的后台同步、复杂的过滤、排序和分组功能。通过这次的实践,我希望透过独特的视角,分析和比较为这两个平台开发GQueues应用的过程。
统计概况
Android App | iOS App | |
启动日期 | Sept 21, 2012 | Mar 2, 2013 |
***个可测的Beta版本 | Dec 22, 2012 | June 10, 2013 |
应用发布日期 | Jan 31, 2013 | July 18, 2013 |
项目总耗时 | 4.25 months | 4.5 months |
Ramp Up Time | 1 week | 2 weeks |
开发耗时 | 870 hours (approx) | 960 hours (approx) |
Beta测试&Bugfix | 34 days | 38 days |
Beta测试人员人数 | 92 people | 48 people |
代码行数 | 26,981 lines | 23,872 lines |
应用大小 | 1.1 MB | 3.5 MB |
视频预览 | GQueues for Android Video | GQueues for iOS Video |
下载 |
学习曲线
我已经写了12年的代码,但这是我写的***个Android应用,也是我写的***个偏向数据处理的iOS应用(2010年我做过两个iOS 3上的 游戏,但那两个游戏主要只涉及一些动画和蓝牙连接)。 我***一次用Java是在研究生阶段,而我的Objective-C也仅限于那两个游戏。所以对于 这两个平台,我基本上可以算是从零开始。
简单讲,只需要花一半学习iOS的时间来学习Android,我就能开始Android开发。对于Android,我花了一周时间用来看书、跟着一 些教程做一些测试应用,这些测试应用包含了GQueues将会用到的一些核心功能。做完这些,我基本上算是打好了为GQueues设计架构的基础,同时也 可以开始为这个项目写代码了。在接下来的一周我可以很轻松自如地基于Android做开发,而不再需要依赖某个资源去实现新特性了。
对于iOS,我同样按照上面的流程,但我花了两周时间做各种测试/实验,才让自己觉得可以开始为这个项目写一些基础代码了。其中大部分的时间都花在研究CoreData各种复杂的API上面。搞清楚怎么设置、怎么在线程安全的前提下,为每个用户集中管理PersistentStoreCoordinators和ManagedObjectContexts也花了些功夫,最重要的是要支持多账户(这个话题可能需要另一篇博客来单独讲讲)。为FetchedResultsControllers开发一个可扩展的架构花了更多时间,FetchedResultsControllers用于支持可被用户查看以及操作的任务表单、队列和分类。***又过了两周(总共花了一个月)自己才能比较轻松自如地基于iOS写代码。
总的来说,Android的文档(官方文档、第三方教程、图书、代码示例、StackOverflow)质量都非常高。我从一些著名的开源 Android应用中学到了很多架构上的***实践,如Google开放给开发者的2012 Google I/O app。此外,Android本身就是 开源的,必要时我可以自己查看Android的平台代码,弄清楚一些疑难问题。虽然iOS也有很多文档,但由于iOS5和iOS6相比之前的版本改动非常 大,大部分文档都已经过时,其中包括ARC入门一文(introduction of Automatic Reference Counting)。因此,大部分的示例代码(包括Apple官方示例)和一些问题的解决方法都是不正确的,需要使用新的方法取而代之。搞清楚这些肯定也需要花更多的时间。
从上面的统计表中也可以看出,开发GQueues for Android要比开发 iOS 版的快十分之一的时间,尽管在开发Android版的期间我重新实现了之前用于支持GQueues HTML5版的整个后端服务器同步代码。而开发一个不 采用原始iOS6风格UI的应用也需要多花些时间,单单比较这个数据,Android开发就是比iOS开发快。
用到的资源
上面列出来的书其实用处很有限,因为跟大部分的技术类书籍一样,书的内容都有点过时了,而且大部分书只停留在入门级别的概念介绍。不过,在一开始的前几天看一下这些书,能够比较快地理解平台上的一些核心功能。就目前来讲,对于这两个平台,在线资源仍然是最有价值的。
工具
接下来我只简单说一下这两个平台的开发工具,因为关于这个话题已经有很多的讨论。我不是Eclipse或者XCode的脑残粉,它们有各自的强项和 弱点(其实我最喜欢的还是Vim)。Eclipse的搜索暴慢而且很繁琐。XCode Organizer的文档搜索也卡爆了。Eclipse中使用 log tags(通过Android插件的logcat集成)过滤日志超级实用。两个IDE的代码补全都很不错,XCode的 Interface Builder一点用处都没有(后面细讲)。不过XCode Instruments就非常有用了,可以用它做优化分析、调试等等。 我开始做GQueues for Android的时候,Google还没发布Android Studio,不过在GQueues的后续更新版本中我会 拿它来试试。
如果你一边写代码一边测试,用Android的模拟器简直就是浪费时间(真不敢相信它能慢成这个鸟样)。在开发过程中,我都是直接部署到真机上测试 的,用真机快很多。iOS的模拟器则很不同,跟Android相比简直就是火箭跟蜗牛赛跑,这也让整个开发过程更加高效。每写一小段代码我都会在模拟器上 跑一下,等到整个功能完成了我就会部署到真机上玩玩。
对于Android,我有各个版本的测试机器(除了Gingerbread,即Android 2.3),除此之外,就要倚靠beta测试过程中各种设备的覆盖了。对于iOS来讲就要简单很多了,我只需要拿GQueues需要支持的最旧的和***的机器来测试就够了。
测试设备
|
|
设计
布局
GQueues的其中一个需求就是必须同时支持任意尺寸的手机和平板,并且针对不同的表单元素进行优化布局。由于各种各样的设备都运行着 Android系统,Android也理所当然地有着成熟的UI组件帮助开发者支持各种尺寸。例如从Android***个版本开 始,RelativeLayout提供了View之间相对布局的支持,可用于创建灵活、响应迅速的布局。另外,在Android中所有的布局都由XML定 义,这设计界面的方式非常简洁、简单并且高效,试过iOS中创建布局之后这种体会就更加深刻了。
相对于Android的RelativeLayout,iOS有Auto Layout,这种布局方式比较新(iOS 6新引入的),集成到了 Interface Builder(IB)中,但是太难用了。我花了好多天学习IB中怎么用Auto Layout,跟任何iOS 6开发者一样,仅靠 IB为视图(View)设定各种精确的约束,完全改变了我自己的标准,这是因为IB所谓的“智能”系统时刻维持(纠正)着视图布局相对位置。我学了很多技 巧,想着弥补IB的短板,但是没啥作用。***我只能放弃IB,转而用冗长的代码实现所有布局。如果你放弃IB和富有极客范的 ASCII art style来写布局,使用Auto Layout来实现还是很强大、很直接的。希望苹果在iOS 7中已经改善这些,不过我还木有试 过。
如果一个应用需要同时针对小屏设备和大屏设备进行优化,最关键的就是基于屏幕的真实尺寸进行动态组合视图,这种方式被称作“适配性布局 (Adaptive Layout)”,平板电脑可以在一屏中显示两个或三个视图,而手机上一屏则只显示一个视图。Android通过Fragments 支持这种设计,Fragment是一个独立的、自包含的的模块,能够在需要的时候直接丢到Activity中去用。通过使用Fragments,只需要调 整几行XML代码就可以让GQueues的布局适配不同分辨率的屏幕。对于我来讲,Fragments是一种非常自然的解决方案,因为它是基于面向对象里 面两个众所周知的准则设计的 - 高内聚和低耦合。
通过Custom Container View Controller(你也可以用Master-Detail模板,当然这种方式宽度是固定的, 也不支持个性化定制),iOS支持一屏使用多个ViewController。对于这个不成熟的特性,我觉得Apple的文档显得很复杂和不完整,***的 资源还要数Ray’s iOS5 tutorials和WWDC视频。我花了比预计要多的时间,终于搞好了在iPad上同时显示多个View、在 iPhone上显示单个View的布局架构。
设备翻转
简单说,在Android上支持设备翻转需要做很多工作,这些工作也是最终导致很多bug的源头,而在iOS上,支持屏幕翻转只需要做一点点工作, 剩下就是系统帮我们搞定了。在Android上,屏幕翻转会直接销毁现有整个视图栈(Activity栈),屏幕翻转完成后再重建每个视图。所以在 GQueues中支持屏幕翻转,我需要无时无刻保存好所有当前状态,随时保证翻转后能正常恢复状态。而在iOS上,系统会帮你管理所有屏幕翻转相关的细 节,唯一需要我关心的就是翻转之后,我需要调整那些没有被Auto Layout处理好的视图的位置。
“复杂”布局
网页开发上有一些常见的布局在GQueues上实现起来非常困难,不管是Android还是iOS。其中一个例子是在任务详细界面显示标签。每个标 签都是变长的,在必要时标签需要自动换行。在网页上实现这个只需要设置CSS的float值就可以了。但不管是Android还是iOS对这种“流式布 局”(Flow Layout)都没有原生的支持,这也意味着我需要写很多代码自己去计算和摆放这些标签,以达到“流式布局”的效果。***Android 的代码是基于Romain Guy的演讲内容和Artem Votincev的flow layout实现的。在iOS上也采用了类似的方法,基于容器的总宽度,计算每个标签的宽度,***设置auto layout的参数。对于这个布局的实现在两个平台上都耗了很大的工作量。
旧设备支持
关于Android的生态系统常被人吐槽的就是严重的系统分化。运营商推送更新的步伐总是很慢,所以现在仍有大量运行着旧系统的设备,这也就意味着 如果要保证应用足够大的设备覆盖率,开发者就不能使用新版系统带来的新特性。不过好在现在针对这个问题,Android社区做了很大的努力,提供了一些用 于在旧系统上支持新特性的库。通过使用Android官方的 Support Library和Jake Wharton的ActionBarSherlock Library,我几乎可以在Android 2.2上使用Jelly Bean(4.2)中所有的新特性。
对于iOS来说,支持旧系统一说几乎不存在,或者说根本就不是关键。在准备阶段我花了一些时间考虑从哪个iOS版本开始支持,而当时的统计数据显示使用iOS 6系统的设备已经达到83%, 而当时对于放弃支持iPad一代我也有一些疑虑,因为我老爸老妈老姐用的就是iPad一代,他们将是GQueues的铁杆支持者。***我决定还是只支持 iOS 6+,这样我可以放开手使用Auto Layout,而不需要浪费大量时间实现任何过时的布局技术。当然,我解决了iPad一代的问题(至少对我 家里人说来说已经解决),就是换掉他们的iPad一代,给他们每人买一个iPad四代(作者有钱银)。
架构
数据存储和管理
对于GQueues来说,数据是核心 - 把数据保存到设备上然后同步到WEB端。Android和iOS有着完全不同的数据管理系统。 Android提供了ContentProvider,它是SQLite数据库上层的一个可被继承的应用接口,作为一个结构化框架被用于所有应用的数据处 理。ContentProvider学习起来比较难,搞定一个GQueues可用的实现,前期需要花很多工作。一旦搞定了***步后面的扩展和个性化定制都 变得简单多了。
一些背景信息,GQueues的web service是基于Google App Engine’s Datastore的, 这是一个高扩展性的分布式NoSQL存储系统,而SQLite则是一个标准的关系型数据库,扩展性明显也比较差,但这完全不需要考虑,因为这个应用只存储 一个用户的数据。(顺便说一下,架构上我采用了“一个用户对应一个数据库”的设计,这对于快速简单地实现多用户切换有重要意义,不过实现细节可能得再开一 博来聊了)。不管怎么说,Android的一个很大的优点就是可以创建SQLite Views来支持Smart Queues。为了支持Smart Queues,搞清楚各种复杂的表关联查询和子查询也花了写功夫,但是这也让Smart Queues的加载更加高效和快速,因为过滤不是在代码里面实现的(在SQL里面)。
在iOS上,我用的是Core Data, 它是iOS上的“schema驱动数据图形管理和持久化框架”,基本上它可以被看做是一个NoSQL存储,不过有趣的是,Core Data背后实际上是 SQLite数据库(呃…实际上SQLite也是几个可选项中最合理的选择)。iOS也允许用户直接创建SQLite数据库,但只支持通过纯C代码来操 作,对于其他iOS组件没有原生集成。Core Data的学习起来也比较困难,但***我还是选择Core Data而不用SQLite,因为这样我可以 轻松实现很多功能,包括缓存、数据模型迁移支持,还有通过 NSFetchedResultsController,可以非常简单地为界面中的 table(列表)提供数据。
管理数据集的关键就是使用事务,尤其重要是做数据同步的时候 - ACID,即:atomic(原子性)、consistent(一致性)、 isolated(隔离性)、durable(持久性)。Android上实现事务似很直观,跟大部分关系型数据库管理系统的实现方式是一样的,因此,保 证数据完整性并不困难。另外,用好SQLite中的UNIQUE ON CONFLICT REPLACE语句,在数据同步的过程中建表、对记录进行原子更新的时候几乎不需要做任何额外工作。
严格来讲,Core Data并不完全支持事务。通过使用单独的子ManagedObjectContexts做后台线程处理,再加上@synchronized,能够处理好数据更新和同步,同时避免不正确的写操作覆盖(overwrite)。关于高效更新和创建对象,iOS给的建议帮助很小,总的来说,CoreData给我的赶脚很笨重,并没有它声称的那么好用。另外,在Android上,SQLite可以轻松实现快速加载Smart Queues,而在iOS上,所有的过滤都必须在代码中实现,就算用了大量的缓存,速度仍然很慢。
搜索
在GQueues for Android上增加强大的全文搜索功能很简单,我模仿Google I/O应用里面的搜索实现,使用了SQLite的FTS3特性。首先创建一个虚拟表,然后在一个存储了用户任务的表上设置几个触发器,由这些触发器填充数据到虚拟表。做完了这些,剩下的就是设计一个搜索界面和为搜索历史添加存储。
iOS的Core Data对于全文搜索并没有原生支持,所以我通过在谓词(Predicate)中使用LIKE语句,实现基本的任务描述和日记的搜索功能。这个实现当然没有全文搜索那么强大,但我认为它已经能够覆盖现实生活中大部分的使用场景了。
特性API
用于比较,我只会列举在GQueues中使用到的几个API。
快速添加(Quick Add)
正则表达式在实现GQueues中Quick Add解 析的时候扮演着一个非常重要的角色,幸运的是,Android和iOS对于正则表达式都有着原生的支持。Android中的Pattern和 Matcher从***个版本起就开始支持,同时也包含了很多正则语法,其中包括前向断言(look-ahead assertion)和后向断言 (look-behind assertion)。iOS则从iOS 4开始引入NSRegularExpression类,令人高兴的是,我可以把我在Android上辛辛苦苦写好的正则表达式几乎原封不动地搬到了iOS上。
分页
在设计界面的时候,我希望用户在查看任务详细的时候左右滑动切换。在Android上我用了 ViewPager和新的FragmentStatePagerAdapter类,FragmentStatePagerAdapter 还处于试验阶段,并且只能通过支持库(Support Library)来使用。我花了几天的时间实现了一个绑定好数据的初级版本,同时解决了几个关于重 复菜单项的bug和在数据发生变化后的处理。这些比我预想的要困难很多,要不是因为左右滑动切换任务的用户体验那么好的话,我真不想实现这个功能。iOS 上的UIPageViewController就简单很多了,虽然也有一些奇怪问题要解决,并且需要自己再加上缓存支持使滑动复杂视图的时候达到可用状态。
语音输入
Android提供了了一个先进但很容易使用的speech-to-text API,只用20行代码,我就把RecognizerIntent集成到GQueues,提供了一个自定义的语音输入功能。但很遗憾,iOS并没有提供支撑SIRI背后技术的API,开发者只能使用第三方库,依赖键盘上的麦克风提供语音输入的支持。我找了各种第三方库,包括Nuance - SIRI语音识别的提供商,但发现没有免费版本,收费版本价格不菲。所以***GQueues只能靠用户自己使用键盘上内置的麦克风选项来进行语音输入,其实这也已经足够了,只要用户还记得有这么个功能。
分享/插件(小部件)
通过使用Intent,在Android上可以很容易就可以把我的应用集成到安装在用户手机上的其他应用。同样地,只需要很少的代码,通过支持ACTION_SEND intent,我就能够让用户在其他应用中创建GQueues任务。Android同时也提供了一个小部件平台,于是我也做了几个小部件,以后还会增加一些。iOS对于跨应用集成和桌面小部件的支持度为零,完全不支持这两个功能。
测试和发布
beta测试
在上面的统计概况表中已经指出,beta版面向真实用户测试了一个多月。两组测试人员都非常棒,帮我找到了数十计的bug,提出了增加一些特性的建 议,对一些UI上不合理的地方提出了反馈。我通过私有的Google Group组织beta测试,这样的beta测试保证了***发布的应用对人们是真正 有用的。在每次beta测试的***,通过调查问卷我收集到了很多有建设性的反馈,也帮助进一步判断我的应用是否达到了可发布的状态。
让测试者开始测试只需要发个APK的链接,让他们下载到他们机器上(呃..他们还需要在设置界面中开启“允许安装Google Play以外的应用”的选项)。Google很方便地支持用真实用户来进行alpha和beta测试,可在开发者控制台和阶段推广中进行设置。在未来的版本更新中我想用用这两个功能。
iOS中的beta测试困难得多,就算用了TestFlight服 务,虽然TestFlight很大程度地简化了流程。为了满足Apple的控制欲,每部测试设备的UUID都要加到用于签名beta版应用的证书当中。因 此,每次要添加beta测试者的时候,不论是添加一个人还是一群人,我都需要重新build一遍我的app。除此之外,Apple还限制了你一年最多只能 注册100个测试设备。所以我要小心利用好这100个坑,这也是为什么GQueues的iOS测试者只有Android的一半。
发布
当然,不谈谈发布流程,Android和iOS的比较都不算完,在Google Play上发布GQueues是一件很好玩的事情,只要我认为已经 准备好了,我随时可以发布我的应用。点下按钮之后,30分钟内,我的应用就能在Google Play上被全世界的用户找到并安装到他们的设备上。而在 App Store上发布一款应用,相信每个iOS开发者都有同样的感受,那是一个令人感到郁闷的经历。经过了数月紧张严密的编码,我只能把我的创作提交 给Apple,然后等7天,7天之后审核人员花2分钟看看我的应用,***拒绝了我的提交。我只能按要求做了修改之后再次提交,我又得等8天才在***通过了 审核。当然还有很多关于提交应用到App Store的恐怖故事,跟他们比起来,我就像是公园里逛了一圈。尽管如此,在自己的商业控制上要对这样一个“情 绪化的第三方平台”做出那么多的让步,仍然让我觉得很不爽。
获胜的平台
从上面的分析来看,做GQueues的过程中,并没有出现平台A完胜平台B的情况。Android和iOS在某些领域各有千秋,也都有需要改进的地 方。从这两个平台的历史来看,貌似目前Android势头更猛一些,不止体现在市场占有率上,而是看到了Android近两年在UI上的改进和开发平台的 稳步提升。而Apple则是封闭的王者,我也坚信他们在很努力地做着他们认为是下一代移动计算革命的事情。不管怎么说,当我想想这6年间所兴起的app生 态圈,我为自己在这个移动技术快速更新的时代,能在这两个平台上做开发感到荣幸。