Android性能优化典范第一季

 

  2015年伊始,Google发布了关于Android性能优化典范的专题,一共16个短视频,每一个3-5分钟,帮助开发者建立更快更优秀的Android App。课程专题不只仅介绍了Android系统中有关性能问题的底层工做原理,同时也介绍了如何经过工具来找出性能问题以及提高性能的建议。html

  主要从三个方面展开,Android的渲染机制,内存与GC,电量优化。下面是对这些问题和建议的总结梳理。

android

  0)Render Performance

  大多数用户感知到的卡顿等性能问题的最主要根源都是由于渲染性能。从设计师的角度,他们但愿App可以有更多的动画,图片等时尚元素来实现流畅的用户体验。可是Android系统颇有可能没法及时完成那些复杂的界面渲染操做。Android系统每隔16ms发出VSYNC信号,算法

  触发对UI进行渲染,若是每次渲染都成功,这样就可以达到流畅的画面所须要的60fps,为了可以实现60fps,这意味着程序的大多数操做都必须在16ms内完成。canvas

  

  若是你的某个操做花费时间是24ms,系统在获得VSYNC信号的时候就没法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一帧画面。缓存

  

  

  用户容易在UI执行动画或者滑动ListView的时候感知到卡顿不流畅,是由于这里的操做相对复杂,容易发生丢帧的现象,从而感受卡顿。有不少缘由能够致使丢帧,也许是由于你的layout太过复杂,没法在16ms内完成渲染,有多是由于你的UI上有层叠太多的绘制单元,性能优化

  还有多是由于动画执行的次数过多。这些都会致使CPU或者GPU负载太重。

网络

  咱们能够经过一些工具来定位问题,好比可使用HierarchyViewer来查找Activity中的布局是否过于复杂,也可使用手机设置里面的开发者选项,打开Show GPU Overdraw等选项进行观察。你还可使用TraceView来观察CPU的执行状况,更加快捷的找到性能瓶颈。

app

 1)Understanding Overdraw

  Overdraw(过分绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了屡次。在多层次的UI结构里面,若是不可见的UI也在作绘制的操做,这就会致使某些像素区域被绘制了屡次。这就浪费大量的CPU以及GPU资源。工具

  

 

  当设计上追求更华丽的视觉效果的时候,咱们就容易陷入采用愈来愈多的层叠组件来实现这种视觉效果的怪圈。这很容易致使大量的性能问题,为了得到最佳的性能,咱们必须尽可能减小Overdraw的状况发生。布局

  幸运的是,咱们能够经过手机设置里面的开发者选项,打开Show GPU Overdraw的选项,能够观察UI上的Overdraw状况。

  

  

  蓝色,淡绿,淡红,深红表明了4种不一样程度的Overdraw状况,咱们的目标就是尽可能减小红色Overdraw,看到更多的蓝色区域。

  Overdraw有时候是由于你的UI布局存在大量重叠的部分,还有的时候是由于非必须的重叠背景。例如某个Activity有一个背景,而后里面的Layout又有本身的背景,同时子View又分别有本身的背景。仅仅是经过移除非必须的背景图片,这就可以减小大量的红色Overdraw区域,

  增长蓝色区域的占比。这一措施可以显著提高程序性能。

 2)Understanding VSYNC

  为了理解App是如何进行渲染的,咱们必须了解手机硬件是如何工做,那么就必须理解什么是VSYNC

  在讲解VSYNC以前,咱们须要了解两个相关的概念:

  • Refresh Rate:表明了屏幕在一秒内刷新屏幕的次数,这取决于硬件的固定参数,例如60Hz。
  • Frame Rate:表明了GPU在一秒内绘制操做的帧数,例如30fps,60fps。

  GPU会获取图形数据进行渲染,而后硬件负责把渲染后的内容呈现到屏幕上,他们二者不停的进行协做。

  

  不幸的是,刷新频率和帧率并非总可以保持相同的节奏。若是发生帧率与刷新频率不一致的状况,就会容易出现Tearing的现象(画面上下两部分显示内容发生断裂,来自不一样的两帧数据发生重叠)。

  

  

  理解图像渲染里面的双重与三重缓存机制,这个概念比较复杂,请移步查看这里:http://source.android.com/devices/graphics/index.html,还有这里http://article.yeeyan.org/view/37503/304664

  一般来讲,帧率超过刷新频率只是一种理想的情况,在超过60fps的状况下,GPU所产生的帧数据会由于等待VSYNC的刷新信息而被Hold住,这样可以保持每次刷新都有实际的新的数据能够显示。可是咱们遇到更多的状况是帧率小于刷新频率。

  

  在这种状况下,某些帧显示的画面内容就会与上一帧的画面相同。糟糕的事情是,帧率从超过60fps忽然掉到60fps如下,这样就会发生LAGJANKHITCHING等卡顿掉帧的不顺滑的状况。这也是用户感觉很差的缘由所在。

  3)Tool:Profile GPU Rendering

  性能问题如此的麻烦,幸亏咱们能够有工具来进行调试。打开手机里面的开发者选项,选择Profile GPU Rendering,选中On screen as bars的选项。

  

  选择了这样之后,咱们能够在手机画面上看到丰富的GPU绘制图形信息,分别关于StatusBar,NavBar,激活的程序Activity区域的GPU Rending信息。

    

  随着界面的刷新,界面上会滚动显示垂直的柱状图来表示每帧画面所须要渲染的时间,柱状图越高表示花费的渲染时间越长。

    

  中间有一根绿色的横线,表明16ms,咱们须要确保每一帧花费的总时间都低于这条横线,这样才可以避免出现卡顿的问题。

   

  每一条柱状线都包含三部分,蓝色表明测量绘制Display List的时间,红色表明OpenGL渲染Display List所须要的时间,黄色表明CPU等待GPU处理的时间。

  4)Why 60fps?

  咱们一般都会提到60fps与16ms,但是知道为什么会是以程序是否达到60fps来做为App性能的衡量标准吗?这是由于人眼与大脑之间的协做没法感知超过60fps的画面更新。

  12fps大概相似手动快速翻动书籍的帧率,这明显是能够感知到不够顺滑的。24fps使得人眼感知的是连续线性的运动,这实际上是归功于运动模糊的效果。24fps是电影胶圈一般使用的帧率,由于这个帧率已经足够支撑大部分电影画面须要表达的内容,同时可以最大的减小费用支出。

  可是低于30fps是没法顺畅表现绚丽的画面内容的,此时就须要用到60fps来达到想要的效果,固然超过60fps是没有必要的。

  开发app的性能目标就是保持60fps,这意味着每一帧你只有16ms=1000/60的时间来处理全部的任务。

  5)Android, UI and the GPU

  了解Android是如何利用GPU进行画面渲染有助于咱们更好的理解性能问题。那么一个最实际的问题是:activity的画面是如何绘制到屏幕上的?那些复杂的XML布局文件又是如何可以被识别并绘制出来的?

  

  

  Resterization栅格化是绘制那些Button,Shape,Path,String,Bitmap等组件最基础的操做。它把那些组件拆分到不一样的像素上进行显示。这是一个很费时的操做,GPU的引入就是为了加快栅格化的操做。

  CPU负责把UI组件计算成Polygons,Texture纹理,而后交给GPU进行栅格化渲染。

  

  然而每次从CPU转移到GPU是一件很麻烦的事情,所幸的是OpenGL ES能够把那些须要渲染的纹理Hold在GPU Memory里面,在下次须要渲染的时候直接进行操做。因此若是你更新了GPU所hold住的纹理内容,那么以前保存的状态就丢失了。

  在Android里面那些由主题所提供的资源,例如Bitmaps,Drawables都是一块儿打包到统一的Texture纹理当中,而后再传递到GPU里面,这意味着每次你须要使用这些资源的时候,都是直接从纹理里面进行获取渲染的。固然随着UI组件的愈来愈丰富,有了更多演变的形态。

  例如显示图片的时候,须要先通过CPU的计算加载到内存中,而后传递给GPU进行渲染。文字的显示更加复杂,须要先通过CPU换算成纹理,而后再交给GPU进行渲染,回到CPU绘制单个字符的时候,再从新引用通过GPU渲染的内容。动画则是一个更加复杂的操做流程。

  为了可以使得App流畅,咱们须要在每一帧16ms之内处理完全部的CPU与GPU计算,绘制,渲染等等操做。

  6)Invalidations, Layouts, and Performance

  顺滑精妙的动画是app设计里面最重要的元素之一,这些动画可以显著提高用户体验。下面会讲解Android系统是如何处理UI组件的更新操做的。

  一般来讲,Android须要把XML布局文件转换成GPU可以识别并绘制的对象。这个操做是在DisplayList的帮助下完成的。DisplayList持有全部将要交给GPU绘制到屏幕上的数据信息。

  在某个View第一次须要被渲染时,DisplayList会所以而被建立,当这个View要显示到屏幕上时,咱们会执行GPU的绘制指令来进行渲染。若是你在后续有执行相似移动这个View的位置等操做而须要再次渲染这个View时,咱们就仅仅须要额外操做一次渲染指令就够了。

  然而若是你修改了View中的某些可见组件,那么以前的DisplayList就没法继续使用了,咱们须要回头从新建立一个DisplayList而且从新执行渲染指令并更新到屏幕上。

  须要注意的是:任什么时候候View中的绘制内容发生变化时,都会从新执行建立DisplayList,渲染DisplayList,更新到屏幕上等一系列操做。这个流程的表现性能取决于你的View的复杂程度,View的状态变化以及渲染管道的执行性能。举个例子,假设某个Button的大小须要增大到目前的两倍,在增大Button大小以前,须要经过父View从新计算并摆放其余子View的位置。修改View的大小会触发整个HierarcyView的从新计算大小的操做。若是是修改View的位置则会触发HierarchView从新计算其余View的位置。若是布局很复杂,这就会很容易致使严重的性能问题。

  咱们须要尽可能减小Overdraw。

  

  咱们能够经过前面介绍的Monitor GPU Rendering来查看渲染的表现性能如何,另外也能够经过开发者选项里面的Show GPU view updates来查看视图更新的操做,最后咱们还能够经过HierarchyViewer这个工具来查看布局,使得布局尽可能扁平化,移除非必需的UI组件,

  这些操做可以减小Measure,Layout的计算时间。

  7)Overdraw, Cliprect, QuickReject

  引发性能问题的一个很重要的方面是由于过多复杂的绘制操做。咱们能够经过工具来检测并修复标准UI组件的Overdraw问题,可是针对高度自定义的UI组件则显得有些力不从心。

  有一个窍门是咱们能够经过执行几个APIs方法来显著提高绘制操做的性能。前面有提到过,非可见的UI组件进行绘制更新会致使Overdraw。例如Nav Drawer从前置可见的Activity滑出以后,若是还继续绘制那些在Nav Drawer里面不可见的UI组件,这就致使了Overdraw。

  为了解决这个问题,Android系统会经过避免绘制那些彻底不可见的组件来尽可能减小Overdraw。那些Nav Drawer里面不可见的View就不会被执行浪费资源。

  

  可是不幸的是,对于那些过于复杂的自定义的View(重写了onDraw方法),Android系统没法检测具体在onDraw里面会执行什么操做,系统没法监控并自动优化,也就没法避免Overdraw了。可是咱们能够经过canvas.clipRect()来帮助系统识别那些可见的区域。

  这个方法能够指定一块矩形区域,只有在这个区域内才会被绘制,其余的区域会被忽视。这个API能够很好的帮助那些有多组重叠组件的自定义View来控制显示的区域。同时clipRect方法还能够帮助节约CPU与GPU资源,在clipRect区域以外的绘制指令都不会被执行,

  那些部份内容在矩形区域内的组件,仍然会获得绘制。

  

  除了clipRect方法以外,咱们还可使用canvas.quickreject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操做。作了那些优化以后,咱们能够经过上面介绍的Show GPU Overdraw来查看效果。

  8)Memory Churn and performance

  虽然Android有自动管理内存的机制,可是对内存的不恰当使用仍然容易引发严重的性能问题。在同一帧里面建立过多的对象是件须要特别引发注意的事情。

  Android系统里面有一个Generational Heap Memory的模型,系统会根据内存中不一样的内存数据类型分别执行不一样的GC操做。例如,最近刚分配的对象会放在Young Generation区域,这个区域的对象一般都是会快速被建立而且很快被销毁回收的,

  同时这个区域的GC操做速度也是比Old Generation区域的GC操做速度更快的。

  

  除了速度差别以外,执行GC操做的时候,任何线程的任何操做都会须要暂停,等待GC操做完成以后,其余操做才可以继续运行。

  

  一般来讲,单个的GC并不会占用太多时间,可是大量不停的GC操做则会显著占用帧间隔时间(16ms)。若是在帧间隔时间里面作了过多的GC操做,那么天然其余相似计算,渲染等操做的可用时间就变得少了。

  致使GC频繁执行有两个缘由:

  • Memory Churn内存抖动,内存抖动是由于大量的对象被建立又在短期内立刻被释放。

  • 瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。即便每次分配的对象占用了不多的内存,可是他们叠加在一块儿会增长Heap的压力,从而触发更多其余类型的GC。这个操做有可能会影响到帧率,并使得用户感知到性能问题。

   

  解决上面的问题有简洁直观方法,若是你在Memory Monitor里面查看到短期发生了屡次内存的涨跌,这意味着颇有可能发生了内存抖动。

  

  

  同时咱们还能够经过Allocation Tracker来查看在短期内,同一个栈中不断进出的相同对象。这是内存抖动的典型信号之一。

  当你大体定位问题以后,接下去的问题修复也就显得相对直接简单了。例如,你须要避免在for循环里面分配对象占用内存,须要尝试把对象的建立移到循环体以外,自定义View中的onDraw方法也须要引发注意,每次屏幕发生绘制以及动画执行过程当中,onDraw方法都会被调用到,

  避免在onDraw方法里面执行复杂的操做,避免建立对象。对于那些没法避免须要建立对象的状况,咱们能够考虑对象池模型,经过对象池来解决频繁建立与销毁的问题,可是这里须要注意结束使用以后,须要手动释放对象池中的对象。

  9)Garbage Collection in Android

  JVM的回收机制给开发人员带来很大的好处,不用时刻处理对象的分配与回收,能够更加专一于更加高级的代码实现。相比起Java,C与C++等语言具有更高的执行效率,他们须要开发人员本身关注对象的分配与回收,可是在一个庞大的系统当中,

  仍是免不了常常发生部分对象忘记回收的状况,这就是内存泄漏。

  原始JVM中的GC机制在Android中获得了很大程度上的优化。Android里面是一个三级Generation的内存模型,最近分配的对象会存放在Young Generation区域,当这个对象在这个区域停留的时间达到必定程度,它会被移动到Old Generation,最后到Permanent Generation区域。

  

  每个级别的内存区域都有固定的大小,此后不断有新的对象被分配到此区域,当这些对象总的大小快达到这一级别内存区域的阀值时,会触发GC的操做,以便腾出空间来存放其余新的对象。

  

  前面提到过每次GC发生的时候,全部的线程都是暂停状态的。GC所占用的时间和它是哪个Generation也有关系,Young Generation的每次GC操做时间是最短的,Old Generation其次,Permanent Generation最长。执行时间的长短也和当前Generation中的对象数量有关,

  遍历查找20000个对象比起遍历50个对象天然是要慢不少的。

  虽然Google的工程师在尽可能缩短每次GC所花费的时间,可是特别注意GC引发的性能问题仍是颇有必要。若是不当心在最小的for循环单元里面执行了建立对象的操做,这将很容易引发GC并致使性能问题。经过Memory Monitor咱们能够查看到内存的占用状况,

  每一次瞬间的内存下降都是由于此时发生了GC操做,若是在短期内发生大量的内存上涨与下降的事件,这说明颇有可能这里有性能问题。咱们还能够经过Heap and Allocation Tracker工具来查看此时内存中分配的到底有哪些对象。

  10)Performance Cost of Memory Leaks

  虽然Java有自动回收的机制,但是这不意味着Java中不存在内存泄漏的问题,而内存泄漏会很容易致使严重的性能问题。

  内存泄漏指的是那些程序再也不使用的对象没法被GC识别,这样就致使这个对象一直留在内存当中,占用了宝贵的内存空间。显然,这还使得每级Generation的内存区域可用空间变小,GC就会更容易被触发,从而引发性能问题。

  寻找内存泄漏并修复这个漏洞是件很棘手的事情,你须要对执行的代码很熟悉,清楚的知道在特定环境下是如何运行的,而后仔细排查。例如,你想知道程序中的某个activity退出的时候,它以前所占用的内存是否有完整的释放干净了?

  首先你须要在activity处于前台的时候使用Heap Tool获取一份当前状态的内存快照,而后你须要建立一个几乎不这么占用内存的空白activity用来给前一个Activity进行跳转,其次在跳转到这个空白的activity的时候主动调用System.gc()方法来确保触发一个GC操做。

  最后,若是前面这个activity的内存都有所有正确释放,那么在空白activity被启动以后的内存快照中应该不会有前面那个activity中的任何对象了。

  

  若是你发如今空白activity的内存快照中有一些可疑的没有被释放的对象存在,那么接下去就应该使用Alocation Track Tool来仔细查找具体的可疑对象。咱们能够从空白activity开始监听,启动到观察activity,而后再回到空白activity结束监听。

  这样操做之后,咱们能够仔细观察那些对象,找出内存泄漏的真凶。

  

  

  11)Memory Performance

  一般来讲,Android对GC作了大量的优化操做,虽然执行GC操做的时候会暂停其余任务,但是大多数状况下,GC操做仍是相对很安静而且高效的。可是若是咱们对内存的使用不恰当,致使GC频繁执行,这样就会引发不小的性能问题。

  为了寻找内存的性能问题,Android Studio提供了工具来帮助开发者。

  • Memory Monitor:查看整个app所占用的内存,以及发生GC的时刻,短期内发生大量的GC操做是一个危险的信号。
  • Allocation Tracker:使用此工具来追踪内存的分配,前面有提到过。
  • Heap Tool:查看当前内存快照,便于对比分析哪些对象有多是泄漏了的,请参考前面的Case。

  12)Tool - Memory Monitor

  Android Studio中的Memory Monitor能够很好的帮组咱们查看程序的内存使用状况。

  

  

  

  13)Battery Performance

  电量实际上是目前手持设备最宝贵的资源之一,大多数设备都须要不断的充电来维持继续使用。不幸的是,对于开发者来讲,电量优化是他们最后才会考虑的的事情。可是能够肯定的是,千万不能让你的应用成为消耗电量的大户。

  Purdue University研究了最受欢迎的一些应用的电量消耗,平均只有30%左右的电量是被程序最核心的方法例如绘制图片,摆放布局等等所使用掉的,剩下的70%左右的电量是被上报数据,检查位置信息,定时检索后台广告信息所使用掉的。

  如何平衡这二者的电量消耗,就显得很是重要了。

  有下面一些措施可以显著减小电量的消耗:

  • 咱们应该尽可能减小唤醒屏幕的次数与持续的时间,使用WakeLock来处理唤醒的问题,可以正确执行唤醒操做并根据设定及时关闭操做进入睡眠状态。
  • 某些非必须立刻执行的操做,例如上传歌曲,图片处理等,能够等到设备处于充电状态或者电量充足的时候才进行。
  • 触发网络请求的操做,每次都会保持无线信号持续一段时间,咱们能够把零散的网络请求打包进行一次操做,避免过多的无线信号引发的电量消耗。关于网络请求引发无线信号的电量消耗,还能够参考这里http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html

  咱们能够经过手机设置选项找到对应App的电量消耗统计数据。咱们还能够经过Battery Historian Tool来查看详细的电量消耗。

  

  若是发现咱们的App有电量消耗过多的问题,咱们可使用JobScheduler API来对一些任务进行定时处理,例如咱们能够把那些任务重的操做等到手机处于充电状态,或者是链接到WiFi的时候来处理。

  关于JobScheduler的更多知识能够参考http://hukai.me/android-training-course-in-chinese/background-jobs/scheduling/index.html

  14)Understanding Battery Drain on Android

  电量消耗的计算与统计是一件麻烦并且矛盾的事情,记录电量消耗自己也是一个费电量的事情。惟一可行的方案是使用第三方监测电量的设备,这样才可以获取到真实的电量消耗。

  当设备处于待机状态时消耗的电量是极少的,以N5为例,打开飞行模式,能够待机接近1个月。但是点亮屏幕,硬件各个模块就须要开始工做,这会须要消耗不少电量。

  使用WakeLock或者JobScheduler唤醒设备处理定时的任务以后,必定要及时让设备回到初始状态。每次唤醒无线信号进行数据传递,都会消耗不少电量,它比WiFi等操做更加的耗电,

  详情请关注http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html

  

  

  修复电量的消耗是另一个很大的课题,这里就不展开继续了。

  15)Battery Drain and WakeLocks

  高效的保留更多的电量与不断促使用户使用你的App来消耗电量,这是矛盾的选择题。不过咱们可使用一些更好的办法来平衡二者。

  假设你的手机里面装了大量的社交类应用,即便手机处于待机状态,也会常常被这些应用唤醒用来检查同步新的数据信息。Android会不断关闭各类硬件来延长手机的待机时间,首先屏幕会逐渐变暗直相当闭,而后CPU进入睡眠,这一切操做都是为了节约宝贵的电量资源。

  可是即便在这种睡眠状态下,大多数应用仍是会尝试进行工做,他们将不断的唤醒手机。一个最简单的唤醒手机的方法是使用PowerManager.WakeLock的API来保持CPU工做并防止屏幕变暗关闭。这使得手机能够被唤醒,执行工做,而后回到睡眠状态。

  知道如何获取WakeLock是简单的,但是及时释放WakeLock也是很是重要的,不恰当的使用WakeLock会致使严重错误。例如网络请求的数据返回时间不肯定,致使原本只须要10s的事情一直等待了1个小时,这样会使得电量白白浪费了。

  这也是为什么使用带超时参数的wakelock.acquice()方法是很关键的。可是仅仅设置超时并不足够解决问题,例如设置多长的超时比较合适?何时进行重试等等?

  解决上面的问题,正确的方式多是使用非精准定时器。一般状况下,咱们会设定一个时间进行某个操做,可是动态修改这个时间也许会更好。例如,若是有另一个程序须要比你设定的时间晚5分钟唤醒,最好可以等到那个时候,两个任务捆绑一块儿同时进行,

  这就是非精肯定时器的核心工做原理。咱们能够定制计划的任务,但是系统若是检测到一个更好的时间,它能够推迟你的任务,以节省电量消耗。

  

  

  这正是JobScheduler API所作的事情。它会根据当前的状况与任务,组合出理想的唤醒时间,例如等到正在充电或者链接到WiFi的时候,或者集中任务一块儿执行。咱们能够经过这个API实现不少免费的调度算法。

  从Android 5.0开始发布了Battery History Tool,它能够查看程序被唤醒的频率,又谁唤醒的,持续了多长的时间,这些信息均可以获取到。

  请关注程序的电量消耗,用户能够经过手机的设置选项观察到那些耗电量大户,并可能决定卸载他们。因此尽可能减小程序的电量消耗是很是有必要的。

  

  Youtube上视频地址:https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE

  文章来源:http://hukai.me/android-performance-patterns/

  感谢原创做者给咱们带来的精彩的翻译。