用户体验导向的Android应用开发
本文指出“流畅的环境”、“友好的体验”和“节省电量”是保证Android应用拥有良好用户体验的三要素。
Android开发目前是移动开发中的“当红炸子鸡”,大量Java程序员涌向Android,同时会习惯性地将桌面和Web端的开发/设计经验带到移动设备上。这样的好处是充分利用了移动开发和桌面/Web服务的共性,比如广泛使用的列表、本地数据库等常用组件;坏处是移动和桌面/Web的使用场景和载体完全不同,直接移植桌面端开发的经验有害无益。
比如,手机主要在碎片时间使用,用户容易对复杂的界面设计感到疲惫;同时,移动环境中上网慢,网络连接频率和失败重发机制的设计更有讲究;此外,手机电池续航能力差,后台复杂的计算会加速耗电速度。这些开发理念直接影响用户最终体验,下面我们来讨论一下在Android中如何以用户体验为导向进行开发优化。
虽然不用深入了解底层,但需要对系统有基本的了解。Android系统分层清晰,最底层是Linux Kernel 2.6,之上包含了Webkit、SQLite、OpenGL ES等基础C/C++库,同时Dalvik虚拟机运行于Kernel之上,帮助应用进行底层内存管理(这样使Android应用无法直接进行内存释放)。这些库一方面被系统大量使用,另一方面也通过Framework层提供接口给开发者。此外,Framework层还提供其他系统级的服务,如消息通知服务、位置获取服务、设备信息读取服务等。
由此可见Android对于开发者非常开放和灵活,尽管如此,开发时仍然要注意不要过于随意,以免产品过于复杂而让用户不知所措。当然,除了少数系统级应用开发需要深入了解Framework层实现机制之外,一般第三方应用开发者并不需要深入了解每一层原理,应把重点放在如何理解和灵活运用庞大的Android SDK API。
本文主要围绕用户的三种感觉来说明如何进行开发。
流畅的环境
让用户感觉使用非常流畅。迟缓会潜移默化地留下不好的印象。用户看见App的图标,便会在心中和“迟缓”、“卡”、“不稳定”画上等号,产生“打开畏惧症”。
用户滑动Listview、Gallery、Coverflow时觉得卡,多半是因为相应Adapter对getView的处理不够好。每个Item都会和数据源绑定,而数据源的获取方式有多种:网络、本地文件、SQLite数据库、SharedPreference以及内存,它们的传输时间分别是7秒、2秒、1秒、100毫秒、5毫秒。
对于最耗时的网络请求,很多人会采用异步操作,不会让用户耗费精力在网络等待过程中。但在I/O以及SQLite查询时,用户的等待时间容易被忽略,从而降低滑动的流畅感。Android用户常常遇到的ANR(Application Not Responding),便是这个问题的升级版。要知道,Activity Manager和Window Manager监视着应用程序的响应,当发现按键或触摸发生后5秒还没执行完处理逻辑,或是BroadcastReceiver处理时间超过了10秒,系统便会抛出ANR错误,并提醒用户强制终止应用。
我的建议如下:
对于无法在短时间完成的操作,在独立线程中处理,Android有多种异步处理模型可供使用,包括Thread-Handler、AsyncTask以及Loader and CursorLoader。
尽可能减少复杂计算和降低I/O,充分估计对象的使用频率,选择合适的数据源。个人认为大部分应用中不会存在太多太大的对象,可以考虑将数据缓存在内存中。如果应用中有太多图片不能一直缓存,可采用LRU(Least Recently Used ,最近最少使用)算法将不常用的缓存清理出内存,这样缓存大小可控,从而不会出现Out of Memory(内存溢出)的Bug。
但要注意,算法是把双刃剑,如果你享受到类似LRU带来的提速后的爽快,就可能会挖空心思探索更高效的算法。这时要慎重,后面会讲到看上去很牛的算法带来的问题。
另外,网络等待虽然是最耗时,但却容易被忽略。因为粗看上去网络是不可控的,与开发无关。一般会设置几秒钟的超时,超时则重发。事实上,在国内,中国移动的GRPS网络占主导,所以手机上网普遍很慢,HTTP连接上下行10秒是很正常的,超时设置20秒都不为过。同时,根据友盟对Android应用使用的统计,用户在每个App上的一次启动花费时间是1分钟左右,理论上有3次重发机会,但一次超时(假设是20秒)后,用户就已经失去信心,不会再等待一次了。所以在开发时,要结合具体使用场景,设计数据预取机制,尽量降低网络请求次数,同时考虑gzip、protobuf等数据压缩和编码机制,保证一次取到的数据不至于太大而造成额外延时。
Android架构图(图片来源:http://developer.Android.com/guide/basics/what-is-Android.html)
友好的体验
不友好的体验来自三个方面。
其一是Android的碎片化带来了UI适配问题。Android机型众多,和iPhone相比,界面适配饱受诟病。要保证应用能运行在不同分辨率的手机上,需要理解Android提供的自动适配方案。事实上,Android系统为UI适配做了充分的考虑,只要理解系统对此的精心设计,就能在开发时少走很多弯路,给不同分辨率的用户提供友好的呈现界面。
简单来说可做如下解释:
开发时避免绝对布局(AbsoluteLayout),因为这会让你的应用只在测试机上“看上去很美”,放到别处就横七竖八。
界面控件大小单位多用DIP(Device Independent Pixels),理由同上。
图片尽量使用系统提供的NinePatch技术,能使同一张小图片在不同分辨率的屏幕上保持精度自由缩放。
其二是滥用通知服务,导致用户很容易被打断。典型场景是在通知栏上的各种通知消息,有关无关的都推送,让用户感觉不适。建议是通知适可而止,除非是对用户真正有用的信息,否则最好让用户进入程序后再提示。
其三是主要来自设计师的问题,就是照搬iPhone应用的设计。Android的系统特点不同于iPhone,如程序的栈式管理机制、菜单按钮、返回按钮,从而用户的预期也不尽相同。比如我发现很多设计师喜欢抛弃返回键,模仿iPhone给每一页设计一个软返回按钮。生硬做作的移植会导致与用户预期不一致,是彻底的设计败笔。
节省电量
随时都得插在墙上充电的设备,不叫移动设备。如果你的App让用户一直守着墙角,用户也会很快把你丢到墙角。你会问:“他怎么知道我的应用耗电?”很抱歉,目前来看,Android用户中有大量发烧友和技术高手,同时系统很不客气地记录了每个应用的耗电量,于是用户偶尔会去系统后台查查耗电大户,之后会毫不客气地打开卸载工具。
所以需注意以下几点:
第一,不要绞尽脑汁设计复杂算法,不要在后台跑服务,不要网断了还不停重试。在开发一个模块前先想想会不会费电,如果会,就不要去做。代码是为了服务用户,而不是折腾用户。
高手喜欢挑战,尤其在手机上实现精巧的算法,这样能带来更强的征服感。有人曾在手机上实现了布隆过滤器(一个庞大精巧的类哈希表,多用于在服务器端如垃圾邮件查找),其内存消耗和计算复杂度都远远高于普通的HashMap,且实现并不容易。结果App发布之后,出现用户抱怨耗电量大,并且经常出现Bug,最后还是老老实实换成了HashMap。任何算法的目的都是为了服务用户,如果简单自然的方法能更好地做到这点,何乐而不为?如果真的在客户端找不到简单的算法,则需要反思——为什么在手机上需要复杂的计算?是否该将这些计算放在服务器端?
第二,不要在后台滥用Service。Android非常开放,开发者可在后台触发任何处理逻辑,肆意占用CPU和内存。一般来说,Service的目的是为了监控变化,包括系统和网络变化。系统变化可通过注册BroadcastReceiver监听控制,比如应用安装和卸载等事件,这样耗电量非常小,完全可替代在Service中轮播。网络请求无法用BroadcastReceiver监听,但是有两个建议。
无严苛的实时性要求,可延长轮播间隔,如6小时自动请求一次,同时时间隔可通过服务器在线更新。这样既省电,偶尔急需实时推送时也可在线调整时间间隔。
对实时性有要求,考虑使用成熟的推送服务,如Google的C2DM(http://code.google.com/Android/c2dm/),和亚马逊的AWS SDK (http://aws.amazon.com/sdkforAndroid/)。
第三,网络请求不要太频繁。系统组件中最耗电的是屏幕,其次就是网络。前文已经提到过,网络出错重发会降低用户体验,还会耗费电力。可通过数据预取结合数据压缩算法减少网络请求次数。
总之,在开发时我们要替用户思考是否做到了“流畅、友好、省电”,以保证App拥有不错的用户体验。
作者陈彧堃,曾任职于微软研究院,从事数据挖掘及机器学习的相关研究,对时空数据挖掘,普适计算等领域有深入了解。2010年4月加入友盟,开始打造友盟移动开发平台。