Google Developing for Android 系列1

前言

前几天在G+上看到Google Developers站点,有一个Android系列的文章,分享到个人微博,周末闲来没事就学写了下,把它们简单的翻译了下,没想到一发不可收拾,六篇文章全部都翻译完了,有些地方省略了部分示例的描述或者换了另一种表述,如果有理解的不准确的地方,还望指正


context或者这些建议为何如此重要

对于理解这些最佳实践的相关上下文是非常重要的。特别是明白移动端存在一些严重的限制,和台式机以及服务器端的计算是完全不同的。因此,在开发应用的时候,如果没有将这些限制考虑进去,将会导致很大的性能问题和内存消耗,这不是针对于某一个应用而言,而是针对整个设备,因为很多拥有相同性能问题的app共同导致了一个性能很差的设备。
以下使一些重要的约束,限制和现实,在过去以及将来的移动设备中都很重要。

内存

移动设备的内存是很有限的。当然不是指所有的设备,不过对于大多数的移动系统是这样的。对于开发者来说,我们使用的手机可能是比大多数用户现在或者将来所使用的设备更快更好,也更新。比如,拥有一个带有 1GB-2GB内存Nexus 5对于我们来说是比较合理的,512M的内存配置的手机在美国以及一些新兴的低端市场是很常见的。因此,以2GB或者更大内存的标准去衡量一个App是不现实的。
明白Android会运行多个Activity和多个并行的Service也是很重要的。在最近App列表中切换而不是重新启动一个App的方式对于创造出一种很好的体验是非常重要的。但是这样意味着如果这些app消耗了比它们本应该消耗的更多内存,那么留给其它的应用的系统内存就很少了。如果这种低内存情况发生,应用就不能保留在后台,系统会干掉app的进程,用户就被迫以重新启动的方式去开启一个App,显然这样的体验就比较差。

CPU

即使最高级别的移动设备的CPU也要远远比台式设备的CPU慢。当然即使你意识到了这一点,你也应该考虑那些大多数使用了比你更慢的CPU的用户。适用于内存的建议也同样适用于CPU:世界范围的低端设备一直在售卖,因此不要以你自己相对较好的设备的性能为基准,因为跑在那些大多数拥有较慢处理器和更低内存配置的设备上的差别是非常大的。
另一个问题是,CPU的处理器是不会一直保持在最大的速度的。当系统认为电池的发热程度达到了临界点的时候,就会导致CPU降频。一般来说,这些情况发生的时候用户可能不会注意到,比如屏幕关闭了,或者输入事件不响应了,或者动画不再运行了。降频对App来说有两层含意:(1)你的app可能在很多情况下降低运转速度,因此,即使是一个不错的CPU,你也只能够获取一个有限的速度 (2)你的app可能做了一些操作导致CPU无法降频,使得CPU始终维持在处理速度最大化的状态(比如,频繁,长时间或者无休止的动画应该被避免,因为在动画期间,为了让动画更加流畅,系统将试图以最大的功率来运行)。显然,你不想去权衡较高的CPU速率和使用对降低电池的使用寿命的影响,因此当你不需要的时候,尽量避免它们。

GPU

GPU性能的建议类似于CPU,当然还有些额外需要注意的地方:

  • 上传是消耗资源的
    在任何系统上上传大大纹理(bitmap)都是比较消耗资源的,bitmap越大,操作时间越长。因此,这意味着频繁大进行bitmap相关大图形化操作会导致性能问题,比如bitmap,paths(会被光栅化到Bitmap中),和大量到新的或者不同的text(有种问题是大量的非英文的字符集合的导致的问题)

  • 我们通常面临的问题并不是几何的绘制,甚至一些纹理的绘制,而是大量的像素填充到高分辨率的设备上。这些高分辨率的屏幕会导致一个性能问题,因为在动画某一帧中硬件不能够填充如此多的像素。比较常见的是过渡绘制,由于重叠的内容,比如window背景,内容背景和一些半透明的View,就会导致应用会多次重绘同一块区域。

内存 == 性能

在这个系列中,很多的指导会围绕着内存的使用来讨论。但有一点很重要,就是内存是和运行期间的性能以及电池的寿命是紧紧关联的。因为你分配的内存越多,设备就要为你的应用做更多的工作。内存分配和回收增加了运行期间的活动会。大的heap内存意味着你的你的app占用了更多的内存,但是设备总体的内存是减少了,会导致其它的活动被迫减少自己的内存开销,或者直接被kill掉,这样将导致设备的总体性能,因为用户会在不同的活动中切换,而这些Activity需要重新启动。更的的Heaps也会导致更长时间的GC中断,因为一个较大的heap内存会导致内存的分配和回收消耗更长的时间。这些所有的行为都会消耗更多的电量,因为设备所做的工作越多,电池就会消耗越多也更有限。

因此这个系列的文章很多涉及到了性能问题,事实就是这些技术对于写出一个好的App来讲,是非常重要的。

因此这篇文档的最佳实践所涉及到的点不是内存就是性能,这些技术也是能够写出一个好的app所必须的。

低端设备

上面很多部分也提到,就是开发者所使用的设备可能比大多数未来使用者的设备都要好。虽然在2015年2GB已经是主流,但是很多在世界范围的很多新兴市场仍然卖着512M的设备。

更平滑的帧率

对于Android的最佳体验是低于16毫秒的帧率。也就是说应用必须在16毫秒内完成所有的输入,布局,绘制和其它所有的事情。这个速度允许系统在动画和输入事件的过程中以60帧美妙的速度渲染。动画必须能在60帧率的时间内完成必要部分的重绘已完成平滑的动效。问题更多的是,很多应用通常可以在16ms内完成渲染,但它丢弃了当前帧,并且之后不能够延续之前的帧率,这种不连续的间隔会容易会引起用户的注意。

平滑的完成一帧意味着任何特殊的帧需要执行所有的渲染代码(包括framework发送给GPU和CPU绘制到缓冲区的命令)都要在16ms内完成。这也就是为什么即使在垃圾回收事件中丢失了5ms也会有很大的影响。因为它严重限制了一帧时间内剩余的绘制时间。越接近16ms,在垃圾回收事件触发的时候,就越容易导致卡顿。

缓冲区只能够在

运行期

有两个运行环境需要主要:Dalvik和ART。在5.0版本之前,Android使用Dalvik。开发者可以在4.4版本中选择ART进行测试。但是它只会在5.0的系统中运行。

Dalvik是采用的时 JIT编译器,能够执行一些细微的优化,但是和其它的很多JIT编译器是不同的。ART是一个Ahead-of-Time 编译器,比Dalvik会做更多的优化。然而,不论是Dalvik还是ART提供的优化水平与服务器和台式机运行平台都是不一样的。比如方法的内联和escape analysis。有一些内联,ART会在叶子方法上执行,但是更进一步的优化只可能在将来的版本中出现,因为未来会有新的优化编译器。由于app开发者可能需要支持老得版本,他们需要继续关心现在和之前编译器的限制。

通常,ART性能要比Dalvik提升30 - 200+%。ART的编译器会执行更多的优化,比如,实质性的提升接口的分发。ART优化的范围要更大些,内存分配也更快。最终,应用的线程会更少的受限于垃圾回收,短时间的中断次数也会减少。

垃圾回收器

垃圾回收器是一个通过运行期间释放那些不再引用的内存的过程。GC也是导致严重性能问题的原因之一,如果GC所做的工作超过了那些必须的工作,那么留给应用平滑的帧率的时间就越少。

在Dalvik和ART中的垃圾回收器实质上是不同的。一个主要的区别就是Dalvik不是一个移动的回收器。就是说所有的分配对象都将呆在Heap的同一个地方,这对于Dalvik来说,为新的对象找到可分配的内存就会更加困难和耗时,特别是当Heap颗粒化和碎片化并且又有大量对象急需内存的时候。Heap碎片化也会导致GC更频繁的中断,因为Dalvik会尝试清理从那些无用的heap。这些GC中断是非常消耗资源的,并且在正常的情况下一个比较快的手机也会很容易耗费掉10-20ms的时间。很重要的是,垃圾回收的时间与在堆中对象的数量成正比,这是尽可能避免分配对象的另一个原因。

ART会动态的提升垃圾回收的效率。比如,ART是一个移动的垃圾回收器,在应用长时间单中止时,它会让heap变得紧凑而不会影响用户的体验。而且,这里有一些独立的专门用于大对象的heap,比如像bitmap这样的对象,ART就会更快的找到内存,而不是有序的遍历碎片化的heap。在ART中的中断,通常在2-3ms间。

尽管ART相对于Dalvik来说的垃圾回收来说有一个很大的性能提升。但是在写Android app的时候,仍然需要注意的是即使是2-3ms的时间对于超过16ms帧率的界限也是足够的。因此,尽管垃圾回收在5.0之后不再是耗资源的行为,但也是始终需要尽可能避免的,特别是在执行动画的情况下,可能会导致一些让用户明显感觉的丢帧。

UI Thread

很多的性能和卡顿问题是由于我们在UI线程中做了大量的工作。Android是一个独立线程的UI系统,在这里所有发生在View上的操作,包括View的绘制,都发生在Activity的UI线程上。任何发生在相同UI线程的操作,即使是View的绘制也可能导致卡顿,因为它没有时间在16ms的时间内达到一个平滑的帧率。

在5.0版本里,framework引入了“Render Thread”,用于向GPU发送实际渲染的操作。这个线程减轻了一些UI Thread减少的操作。但是输入,滚动和动画仍然在UI thread,因为thread必须能够响应操作。

存储

存储的指标在不同的Android设备上是不同的,但是可能是比较慢而且是受限制的。移动设备只有8G(2015年,在低于中等水平的设备中很常见)甚至4G搭配一个SDcard,去存储整个系统,所有的应用以及所有的音频,很容易就满了。在这种情况下,一个app可能导致用户为它删除其它内容以便获取更多空间,或者因为每有足够的空间就直接卸载它。

存储性能也是需要关心的点。移动设备和桌面设备的硬盘相比是完全不同的。

同时,也要注意到外部存储SDcard的内存在I/O性能上可能有很大的不确定性,其依赖于供应商,芯片和速度指标。但是App不应该阻止用户使用SDcard,因为很多设备确实内存比较小,需要它作为扩展。

网络

位于在城市中的大多数软件开发者来说拥有一些现代的基础设施和移动网络是很简单的。但很多其它地区并不具备这样的设施。更别说LTE或4G了。很多国家的地区还是2G的网络而且可能还要承受大量数据的传输。这样就会导致两个通用的问题:

  • 依赖于快速的网络速度
    App严重依赖大媒体数据(Video,audio,images)在网络基础较差的地方可能没有选择。但是避免下载,直到条件允许的情况下再去下载也是App体验的一部分

  • 多度同步
    也许你的App希望更新一些信息,但是用户并不需要它,更重要的问题,设备并不应该承受所有应用程序与网络不断交互的情况。这种动态会很容易地使设备持续工作而不能够进行休眠,最终影响电池的续航。

每一个设备都是一个村落

用户的设备上会安装很多的应用,包括系统的UI和Launcher App。如果你的App使用了越多的资源(内存,CPU,GPU,电池),那么其它应用就会越少,那么呈现给的用户的设备就会越差。如果你的处理越多,当系统需要更多内存的时候,就越有可能试图从内存中干掉它,那就意味着你的App就要花费更长时间去开启,从而导致一种很不好的体验。

因此,在Android设备上成为一个良好的公民吧。也是每个App应该去做的,因为如果每个应用都贪婪,那么最终痛苦是设备和用户。

平民的灾难

移动设备最大的问题是一些app在自己感兴趣的情况下活动导致了设备的整体性能和体验。分析特定app可能不是应用需要修复的标志性的问题。但是对设备整体的影响,所有的app都遭受资源的限制,最终用户的体验很差。

一个好的例子是观察某个应用是否太频繁的同步。那意味着特定的应用在以特定的频率访问服务器。但是如果用户有超过100个应用都在这样做,相比较那些延迟,批处理的同步系统来说,结果就是设备将永远不能够休眠并且很快耗完电。

译文链接:http://www.lightskystreet.com/2015/06/07/google-for-android-1-mobile-context/
译文作者:lightSky
原文链接:Developing for Android, I:Understanding the Mobile Context