您的位置:首页 > 博客中心 > APP开发 >

Android 性能优化探究

时间:2022-03-13 19:57

  • 使用ViewStub动态载入布局。避免一些不常常的视图长期握住引用:

    ViewStub的一些特点:
    1. ViewStub仅仅能Inflate一次,之后ViewStub对象被置空:某个被ViewStub指定的布局被Inflate后,就不会够再通过ViewStub来控制它了。
    2. ViewStub仅仅能用来Inflate一个布局文件,而不是某个详细的View。当然也能够把View写在某个布局文件里。
    基于以上的特点。那么能够考虑使用ViewStub的情况有:
    1. 在程序的运行期间,某个布局在Inflate后,就不会有变化。除非又一次启动。
    由于ViewStub仅仅能Inflate一次,之后会被置空,所以无法指望后面接着使用ViewStub来控制布局。

    所以当须要在运行时不止一次的显示和隐藏某个布局,那么ViewStub是做不到的。这时就仅仅能使用View的可见性来控制了。
    2. 想要控制显示与隐藏的是一个布局文件。而非某个View。
    由于设置给ViewStub的仅仅能是某个布局文件的Id,所以无法让它来控制某个View。

  • 硬件加速:android:hardwareAccelerated

 <application
        android:name="com.tchip.carlauncher.MyApplication"
        android:icon="@drawable/ic_launcher"
        android:label="Car Launcher"
        android:hardwareAccelerated="true"
        android:largeHeap="true"
        android:theme="@android:style/Theme.Holo.Light" >
  • View缓存:setDrawingCache
    hsvMain = (HorizontalScrollView) findViewById(R.id.hsvMain);
    hsvMain.setDrawingCacheEnabled(true);
  • 将Acitivity 中的Window 的背景图设置为空:
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        getWindow().setBackgroundDrawable(null);
        setContentView(R.layout.activity_main);
        }


以下内容转自:

Android性能优化典范(一)

技术分享
2015新年伊始,Google公布了关于。一共16个短视频。每一个3-5分钟,帮助开发人员创建更快更优秀的Android App。

课程专题不仅仅介绍了Android系统中有关性能问题的底层工作原理,同一时候也介绍了怎样通过工具来找出性能问题以及提升性能的建议。主要从三个方面展开,Android的渲染机制。内存与GC,电量优化。以下是对这些问题和建议的总结梳理。

0)Render Performance

大多数用户感知到的卡顿等性能问题的最主要根源都是由于渲染性能。从设计师的角度,他们希望App能够有很多其它的动画。图片等时尚元素来实现流畅的用户体验。可是Android系统非常有可能无法及时完毕那些复杂的界面渲染操作。Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,假设每次渲染都成功,这样就能够达到流畅的画面所须要的60fps。为了能够实现60fps。这意味着程序的大多数操作都必须在16ms内完毕。
技术分享
假设你的某个操作花费时间是24ms。系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一帧画面。
技术分享
用户easy在UI运行动画或者滑动ListView的时候感知到卡顿不流畅。是由于这里的操作相对复杂,easy发生丢帧的现象,从而感觉卡顿。

有非常多原因能够导致丢帧。或许是由于你的layout太过复杂。无法在16ms内完毕渲染,有可能是由于你的UI上有层叠太多的绘制单元,还有可能是由于动画运行的次数过多。

这些都会导致CPU或者GPU负载过重。

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

1)Understanding Overdraw

Overdraw(过度绘制)描写叙述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。

在多层次的UI结构里面,假设不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。

这就浪费大量的CPU以及GPU资源。
技术分享

当设计上追求更华丽的视觉效果的时候,我们就easy陷入採用越来越多的层叠组件来实现这样的视觉效果的怪圈。这非常easy导致大量的性能问题。为了获得最佳的性能。我们必须尽量降低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。
    技术分享

不幸的是,刷新频率和帧率并非总能够保持同样的节奏。假设发生帧率与刷新频率不一致的情况,就会easy出现Tearing的现象(画面上下两部分显示内容发生断裂,来自不同的两帧数据发生重叠)。
技术分享
技术分享
理解图像渲染里面的双重与三重缓存机制。这个概念比較复杂,请移步查看这里:


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


技术分享
在这样的情况下,某些帧显示的画面内容就会与上一帧的画面同样。

糟糕的事情是,帧率从超过60fps突然掉到60fps以下,这样就会发生LAG,JANK,HITCHING等卡顿掉帧的不顺滑的情况。这也是用户感受不好的原因所在。

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的位置。

假设布局非常复杂,这就会非常easy导致严重的性能问题。我们须要尽量降低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有自己主动管理内存的机制,可是对内存的不恰当使用仍然easy引起严重的性能问题。在同一帧里面创建过多的对象是件须要特别引起注意的事情。

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的project师在尽量缩短每次GC所花费的时间,可是特别注意GC引起的性能问题还是非常有必要。假设不小心在最小的for循环单元里面运行了创建对象的操作,这将非常easy引起GC并导致性能问题。

通过Memory Monitor我们能够查看到内存的占用情况,每一次瞬间的内存降低都是由于此时发生了GC操作,假设在短时间内发生大量的内存上涨与降低的事件,这说明非常有可能这里有性能问题。

我们还能够通过Heap and Allocation Tracker工具来查看此时内存中分配的究竟有哪些对象。

10)Performance Cost of Memory Leaks

尽管Java有自己主动回收的机制,可是这不意味着Java中不存在内存泄漏的问题,而内存泄漏会非常easy导致严重的性能问题。

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

寻找内存泄漏并修复这个漏洞是件非常棘手的事情,你须要对运行的代码非常熟悉。清晰的知道在特定环境下是怎样运行的,然后细致排查。比如。你想知道程序中的某个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来处理唤醒的问题,能够正确运行唤醒操作并依据设定及时关闭操作进入睡眠状态。


某些非必须立即运行的操作,比如上传歌曲,图片处理等。能够等到设备处于充电状态或者电量充足的时候才进行。
触发网络请求的操作,每次都会保持无线信号持续一段时间,我们能够把零散的网络请求打包进行一次操作,避免过多的无线信号引起的电量消耗。关于网络请求引起无线信号的电量消耗,还能够參考这里:

我们能够通过手机设置选项找到相应App的电量消耗统计数据。我们还能够通过Battery Historian Tool来查看详细的电量消耗。
技术分享

假设发现我们的App有电量消耗过多的问题,我们能够使用JobScheduler API来对一些任务进行定时处理。比如我们能够把那些任务重的操作等到手机处于充电状态,或者是连接到WiFi的时候来处理。 关于JobScheduler的很多其它知识能够參考

14)Understanding Battery Drain on Android

电量消耗的计算与统计是一件麻烦而且矛盾的事情,记录电量消耗本身也是一个费电量的事情。唯一可行的方案是使用第三方监測电量的设备,这样才干够获取到真实的电量消耗。

当设备处于待机状态时消耗的电量是极少的,以N5为例。打开飞行模式,能够待机接近1个月。

可是点亮屏幕,硬件各个模块就须要開始工作。这会须要消耗非常多电量。

使用WakeLock或者JobScheduler唤醒设备处理定时的任务之后,一定要及时让设备回到初始状态。每次唤醒无线信号进行数据传递。都会消耗非常多电量,它比WiFi等操作更加的耗电,详情请关注

技术分享

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

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,它能够查看程序被唤醒的频率。又谁唤醒的。持续了多长的时间,这些信息都能够获取到。

请关注程序的电量消耗,用户能够通过手机的设置选项观察到那些耗电量大户。并可能决定卸载他们。所以尽量降低程序的电量消耗是非常有必要的。

Android性能优化典范(二)

技术分享
Google前几天刚公布了Android性能优化典范第2季的课程,一共20个短视频,包括的内容大致有:电量优化,网络优化。Wear上怎样做优化。使用对象池来提高效率,LRU Cache,Bitmap的缩放,缓存,重用,PNG压缩。自己定义View的性能,提升设置alpha之后View的渲染性能,以及Lint。StictMode等等工具的使用技巧。 以下是对这些课程的总结摘要,认知有限,理解偏差的地方请多多不吝赐教!

1)Battery Drain and Networking

对于手机程序。网络操作相对来说是比較耗电的行为。优化网络操作能够显著节约电量的消耗。

在性能优化第1季里面有提到过。手机硬件的各个模块的耗电量是不一样的。其中移动蜂窝模块对电量消耗是比較大的,另外蜂窝模块在不同工作强度下,对电量的消耗也是有差异的。当程序想要运行某个网络请求之前,须要先唤醒设备。然后发送数据请求。之后等待返回数据,最后才慢慢进入休眠状态。

这个流程例如以下图所看到的:

技术分享

在上面那个流程中,蜂窝模块的电量消耗差异例如以下图所看到的:

技术分享

从图示中能够看到,激活瞬间,发送数据的瞬间,接收数据的瞬间都有非常大的电量消耗,所以。我们应该从怎样传递网络数据以及何时发起网络请求这两个方面来着手优化。

1.1)何时发起网络请求

首先我们须要区分哪些网络请求是须要及时返回结果的,哪些是能够延迟运行的。

比如。用户主动下拉刷新列表,这样的行为须要立即触发网络请求,并等待数据返回。可是对于上传用户操作的数据,同步程序设置等等行为则属于能够延迟的行为。我们能够通过Battery Historian这个工具来查看关于移动蜂窝模块的电量消耗(关于这部分的细节,请点击Android性能优化之电量篇)。在Mobile Radio那一行会显示蜂窝模块的电量消耗情况,红色的部分代表模块正在工作,中间的间隔部分代表模块正在休眠状态,假设看到有一段区间,红色与间隔频繁的出现。那就说明这里有能够优化的行为。例如以下图所看到的:

技术分享

对于上面能够优化的部分。我们能够有针对性的把请求行为捆绑起来。延迟到某个时刻统一发起请求。例如以下图所看到的:

技术分享

经过上面的优化之后。我们再回头使用Battery Historian导出电量消耗图,能够看到唤醒状态与休眠状态是连续大块间隔的。这样的话,总体电量的消耗就会变得更少。

技术分享

当然,我们甚至能够把请求的任务延迟到手机网络切换到WiFi。手机处于充电状态下再运行。

在前面的描写叙述过程中,我们会遇到的一个难题是怎样把网络请求延迟。并批量进行运行。还好。Android提供了JobScheduler来帮助我们达成这个目标。

1.2)怎样传递网络数据

关于这部分主要会涉及到Prefetch(预取)与Compressed(压缩)这两个技术。对于Prefetch的使用。我们须要预先推断用户在此次操作之后,兴许零散的请求是否非常有可能会立即被触发,能够把后面5分钟有可能会使用到的零散请求都一次集中运行完毕。对于Compressed的使用,在上传与下载数据之前。使用CPU对数据进行压缩与解压,能够非常大程度上降低网络传输的时间。

想要知道我们的应用程序中网络请求发生的时间,每次请求的数据量等等信息。能够通过Android Studio中的Networking Traffic Tool来查看详细的数据,例如以下图所看到的:

技术分享

2)Wear & Sensors

在Android Wear上会大量的使用Sensors来实现某些特殊功能,怎样在尽量节约电量的前提下利用好Sensor会是我们须要特别注意的问题。以下会介绍一些在Android Wear上的最佳实践典范。

尽量降低刷新请求,比如我们能够在不须要某些数据的时候尽快注销监听,减小刷新频率,对Sensor的数据做批量处理等等。

那么怎样做到这些优化呢?

首先我们须要尽量使用Android平台提供的既有运动数据,而不是自己去实现监听採集数据。由于大多数Android Watch自身记录Sensor数据的行为是有经过做电量优化的。
其次在Activity不须要监听某些Sensor数据的时候须要尽快释放监听注冊。
还有我们须要尽量控制更新的频率,仅仅在须要刷新显示数据的时候才触发获取最新数据的操作。


另外我们能够针对Sensor的数据做批量处理,待数据累积一定次数或者某个程度的时候才更新到UI上。
最后当Watch与Phone连接起来的时候,能够把某些复杂操作的事情交给Phone来运行,Watch仅仅须要等待返回的结果。


更对关于Sensors的知识,能够点击这里

3)Smooth Android Wear Animation

Android Material Design风格的应用採用了大量的动画来进行UI切换,优化动画的性能不仅能够提升用户体验还能够降低电量的消耗,以下会介绍一些简单易行的方法。

在Android里面一个相对操作比較繁重的事情是对Bitmap进行旋转,缩放,裁剪等等。比如在一个圆形的钟表图上。我们把时钟的指针抠出来当做单独的图片进行旋转会比旋转一张完整的圆形图的所形成的帧率要高56%。

技术分享

另外尽量降低每次重绘的元素能够极大的提升性能,假如某个钟表界面上有非常多须要显示的复杂组件,我们能够把这些组件做拆分处理,比如把背景图片单独拎出来设置为一个独立的View。通过setLayerType()方法使得这个View强制用Hardware来进行渲染。至于界面上哪些元素须要做拆分。他们各自的更新频率是多少,须要有针对性的单独讨论。

怎样使用Systrace等工具来查看某些View的渲染性能,在前面的章节里面有提到过,感兴趣的能够点击

对于大多数应用中的动画,我们会使用PropertyAnimation或者ViewAnimation来操作实现,Android系统会自己主动对这些Animation做一定的优化处理,在Android上面学习到的大多数性能优化的知识同样也适用于Android Wear。

想要获取很多其它关于Android Wear中动画效果的优化。请点击WatchFace这个范例。

4)Android Wear Data Batching

在Android Training里面有关于Wear上面怎样利用Wearable API与Phone进行沟通协作的课程(详情请点击这里)。由于Phone的CPU与电量都比Wear要强大,另外Phone还能够直接接入网络,而Wear要接入网络则相对更加困难,所以我们在开发Wear应用的时候须要尽量做到把复杂的操作交给Phone来运行。

比如我们能够让Phone来获取天气信息,然后把数据返回Wear进行显示。更进一步,在之前的性能优化课程里面我们有学习过怎样使用JobScheduler来延迟批量处理任务,假设Phone收到来自Wear的其中一个任务是每隔5分钟检查一次天气情况。那么Phone使用JobScheduler运行检查天气任务之后。先推断这次返回的结果和之前是否有差异,仅仅当天气发生变化的时候,才有必要把结果通知到Wear,或者仅仅把变化的某一项数据通知给Wear。这样能够更大程度上降低Wear的电量消耗。

以下我们总结一下怎样优化Wear的性能与电量:

  • 仅仅在真正须要刷新界面的时候才发出请求
  • 尽量把计算复杂操作的任务交给Phone来处理
  • Phone仅仅在数据发生变化的时候才通知到Wear
  • 把零碎的数据请求捆绑一起再进行操作

5)Object Pools

在程序里面常常会遇到的一个问题是短时间内创建大量的对象,导致内存紧张,从而触发GC导致性能问题。对于这个问题。我们能够使用对象池技术来解决它。通常对象池中的对象可能是bitmaps,views,paints等等。关于对象池的操作原理,不展开述说了。请看以下的图示:

技术分享

使用对象池技术有非常多优点,它能够避免内存抖动。提升性能。可是在使用的时候有一些内容是须要特别注意的。通常情况下,初始化的对象池里面都是空白的,当使用某个对象的时候先去对象池查询是否存在,假设不存在则创建这个对象然后添加对象池,可是我们也能够在程序刚启动的时候就事先为对象池填充一些即将要使用到的数据,这样能够在须要使用到这些对象的时候提供更快的首次载入速度,这样的行为就叫做预分配。使用对象池也有不好的一面,程序猿须要手动管理这些对象的分配与释放,所以我们须要谨慎地使用这项技术。避免发生对象的内存泄漏。

为了确保全部的对象能够正确被释放,我们须要保证添加对象池的对象和其它外部对象没有互相引用的关系。

6)To Index or Iterate?

遍历容器是编程里面一个常常遇到的场景。在Java语言中。使用Iterate是一个比較常见的方法。

可是在Android开发团队中,大家却尽量避免使用Iterator来运行遍历操作。

以下我们看下在Android上可能用到的三种不同的遍历方法:
技术分享
技术分享
技术分享

使用上面三种方式在同一台手机上,使用同样的数据集做測试,他们的表现性能例如以下所看到的:
技术分享

从上面能够看到for index的方式有更好的效率。可是由于不同平台编译器优化各有差异,我们不妨针对实际的方法做一下简单的測量比較好,拿到数据之后,再选择效率最高的那个方式。

7)The Magic of LRU Cache

这小节我们要讨论的是缓存算法。在Android上面最常常使用的一个缓存算法是LRU(Least Recently Use),关于LRU算法,不展开述说。用以下一张图演示下含义:

技术分享

LRU Cache的基础构建使用方法例如以下:

技术分享

为了给LRU Cache设置一个比較合理的缓存大小值,我们一般是用以下的方法来做界定的:

技术分享

使用LRU Cache时为了能够让Cache知道每一个添加的Item的详细大小,我们须要Override以下的方法:

技术分享

使用LRU Cache能够显著提升应用的性能,可是也须要注意LRU Cache中被淘汰对象的回收。否者会引起严重的内存泄露。

8)Using LINT for Performance Tips

Lint是Android提供的一个静态扫描应用源代码并找出其中的潜在问题的一个强大的工具。

技术分享

比如。假设我们在onDraw方法里面运行了new对象的操作。Lint就会提示我们这里有性能问题,并提出相应的建议方案。Lint已经集成到Android Studio中了。我们能够手动去触发这个工具,点击工具栏的Analysis -> Inspect Code,触发之后。Lint会開始工作,并把结果输出究竟部的工具栏。我们能够逐个查看原因并依据指示做相应的优化改动。

Lint的功能非常强大,他能够扫描各种问题。

当然我们能够通过Android Studio设置找到Lint,对Lint做一些定制化扫描的设置,能够选择忽略掉那些不想Lint去扫描的选项,我们还能够针对部分扫描内容改动它的提示优先级。

建议把与内存有关的选项中的严重程度标记为红色的Error,对于Layout的性能问题标记为黄色Warning。

9)Hidden Cost of Transparency

这小节会介绍怎样降低透明区域对性能的影响。通常来说。对于不透明的View,显示它仅仅须要渲染一次就可以。可是假设这个View设置了alpha值,会至少须要渲染两次。

原因是包括alpha的view须要事先知道混合View的下一层元素是什么,然后再结合上层的View进行Blend混色处理。

在某些情况下。一个包括alpha的View有可能会触发改View在HierarchyView上的父View都被额外重绘一次。以下我们看一个样例。下图演示的ListView中的图片与二级标题都有设置透明度。

技术分享

大多数情况下,屏幕上的元素都是由后向前进行渲染的。在上面的图示中,会先渲染背景图(蓝,绿。红),然后渲染人物头像图。

假设后渲染的元素有设置alpha值,那么这个元素就会和屏幕上已经渲染好的元素做blend处理。非常多时候。我们会给整个View设置alpha的来达到fading的动画效果。假设我们图示中的ListView做alpha逐渐减小的处理,我们能够看到ListView上的TextView等等组件会逐渐融合到背景色上。可是在这个过程中,我们无法观察到它事实上已经触发了额外的绘制任务。我们的目标是让整个View逐渐透明,可是期间ListView在不停的做Blending的操作,这样会导致不少性能问题。

怎样渲染才干够得到我们想要的效果呢?我们能够先依照通常的方式把View上的元素依照从后到前的方式绘制出来,可是不直接显示到屏幕上,而是使用GPU预处理之后,再又GPU渲染到屏幕上,GPU能够对界面上的原始数据直接做旋转,设置透明度等等操作。使用GPU进行渲染,尽管第一次操作相比起直接绘制到屏幕上更加耗时。可是一旦原始纹理数据生成之后,接下去的操作就比較省时省力。

技术分享

怎样才干够让GPU来渲染某个View呢?我们能够通过setLayerType的方法来指定View应该怎样进行渲染,从SDK 16開始,我们还能够使用ViewPropertyAnimator.alpha().withLayer()来指定。例如以下图所看到的:

技术分享

另外一个样例是包括阴影区域的View,这样的类型的View并不会出现我们前面提到的问题,由于他们并不存在层叠的关系。

技术分享

为了能够让渲染器知道这样的情况。避免为这样的View占用额外的GPU内存空间,我们能够做以下的设置。

技术分享

通过上面的设置以后,性能能够得到显著的提升,例如以下图所看到的:

技术分享

10)Avoiding Allocations in onDraw()

我们都知道应该避免在onDraw()方法里面运行导致内存分配的操作,以下解说下为何须要这样做。

首先onDraw()方法是运行在UI线程的,在UI线程尽量避免做不论什么可能影响到性能的操作。尽管分配内存的操作并不须要花费太多系统资源。可是这并不意味着是免费无代价的。设备有一定的刷新频率,导致View的onDraw方法会被频繁的调用,假设onDraw方法效率低下,在频繁刷新累积的效应下,效率低的问题会被扩大,然后会对性能有严重的影响。

技术分享

假设在onDraw里面运行内存分配的操作,会easy导致内存抖动,GC频繁被触发。尽管GC后来被改进为运行在另外一个后台线程(GC操作在2.3曾经是同步的。之后是并发)。可是频繁的GC的操作还是会影响到CPU,影响到电量的消耗。

那么简单解决频繁分配内存的方法就是把分配操作移动到onDraw()方法外面,通常情况下。我们会把onDraw()里面new Paint的操作移动到外面。如以下所看到的:

技术分享

11)Tool: Strict Mode

UI线程被堵塞超过5秒。就会出现ANR,这太糟糕了。

防止程序出现ANR是非常重要的事情,那么怎样找出程序里面潜在的坑。预防ANR呢?非常多大部分情况下运行非常快的方法,可是他们有可能存在巨大的隐患,这些隐患的爆发就非常easy导致ANR。

Android提供了一个叫做Strict Mode的工具,我们能够通过手机设置里面的开发人员选项,打开Strict Mode选项,假设程序存在潜在的隐患,屏幕就会闪现红色。我们也能够通过StrictMode API在代码层面做细化的跟踪,能够设置StrictMode监听那些潜在问题。出现故障时怎样提醒开发人员,能够对屏幕闪红色,也能够输出错误日志。以下是官方的代码演示样例:

public void onCreate() {
     if (DEVELOPER_MODE) {
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                 .detectDiskReads()
                 .detectDiskWrites()
                 .detectNetwork()   // or .detectAll() for all detectable problems
                 .penaltyLog()
                 .build());
         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                 .detectLeakedSqlLiteObjects()
                 .detectLeakedClosableObjects()
                 .penaltyLog()
                 .penaltyDeath()
                 .build());
     }
     super.onCreate();
}

12)Custom Views and Performance

Android系统有提供超过70多种标准的View,比如TextView。ImageView,Button等等。在某些时候,这些标准的View无法满足我们的须要。那么就须要我们自己来实现一个View,这节会介绍怎样优化自己定义View的性能。

通常来说,针对自己定义View,我们可能犯以下三个错误:

  • Useless calls to onDraw():我们知道调用View.invalidate()会触发View的重绘,有两个原则须要遵守,第1个是仅仅在View的内容发生改变的时候才去触发invalidate方法,第2个是尽量使用ClipRect等方法来提高绘制的性能。
  • Useless pixels:降低绘制时不必要的绘制元素。对于那些不可见的元素。我们须要尽量避免重绘。

  • Wasted CPU cycles:对于不在屏幕上的元素,能够使用Canvas.quickReject把他们给剔除,避免浪费CPU资源。另外尽量使用GPU来进行UI的渲染,这样能够极大的提高程序的总体表现性能。
    最后请时刻牢记。尽量提高View的绘制性能,这样才干保证界面的刷新帧率尽量的高。很多其它关于这部分的内容,能够看这里

13)Batching Background Work Until Later

优化性能时大多数时候讨论的都是怎样降低不必要的操作,可是选择何时去运行某些操作同样也非常重要。

在第1季以及上一期的性能优化之电量篇里面,我们有提到过移动蜂窝模块的电量消耗模型。

为了避免我们的应用程序过多的频繁消耗电量。我们须要学习怎样把后台任务打包批量,并选择一个合适的时机进行触发运行。

下图是每一个应用程序各自运行后台任务导致的电量消耗示意图:

技术分享

由于像上面那样做会导致浪费非常多电量。我们须要做的是把部分应用的任务延迟处理,等到一定时机,这些任务一并进行处理。

结果如以下的示意图:

技术分享

运行延迟任务,通常有以下三种方式:

1)AlarmManager

使用AlarmManager设置定时任务,能够选择精确的间隔时间,也能够选择非精确时间作为參数。

除非程序有非常强烈的须要使用精确的定时唤醒,否者一定要避免使用他,我们应该尽量使用非精确的方式。

2)SyncAdapter

我们能够使用SyncAdapter为应用添加设置账户,这样在手机设置的账户列表里面能够找到我们的应用。

这样的方式功能很多其它,可是实现起来比較复杂。

我们能够从这里看到官方的培训课程:

3)JobSchedulor

这是最简单高效的方法,我们能够设置任务延迟的间隔,运行条件,还能够添加重试机制。

14)Smaller Pixel Formats

常见的png,jpeg,webp等格式的图片在设置到UI上之前须要经过解码的过程,而解压时能够选择不同的解码率。不同的解码率对内存的占用是有非常大区别的。在不影响到画质的前提下尽量降低内存的占用,这能够显著提升应用程序的性能。

Android的Heap空间是不会自己主动做兼容压缩的,意思就是假设Heap空间中的图片被收回之后。这块区域并不会和其它已经回收过的区域做又一次排序合并处理。那么当一个更大的图片须要放到heap之前。非常可能找不到那么大的连续空暇区域。那么就会触发GC,使得heap腾出一块足以放下这张图片的空暇区域。假设无法腾出。就会发生OOM。

例如以下图所看到的:

技术分享

所以为了避免载入一张超大的图片。须要尽量降低这张图片所占用的内存大小,Android为图片提供了4种解码格式。他们分别占用的内存大小例如以下图所看到的:

技术分享

随着解码占用内存大小的降低,清晰度也会有损失。我们须要针对不同的应用场景做不同的处理,大图和小图能够採用不同的解码率。在Android里面能够通过以下的代码来设置解码率:

技术分享

15)Smaller PNG Files

尽量降低PNG图片的大小是Android里面非常重要的一条规范。相比起JPEG,PNG能够提供更加清晰无损的图片,可是PNG格式的图片会更大,占用很多其它的磁盘空间。究竟是使用PNG还是JPEG。须要设计师细致衡量。对于那些使用JPEG就能够达到视觉效果的。能够考虑採用JPEG就可以。我们能够通过Google搜索到非常多关于PNG压缩的工具,例如以下图所看到的:

技术分享

这里要介绍一种新的图片格式:Webp,它是由Google推出的一种既保留png格式的优点,又能够降低图片大小的一种新型图片格式。关于Webp的很多其它细节,请点击

16)Pre-scaling Bitmaps

对bitmap做缩放,这也是Android里面最遇到的问题。对bitmap做缩放的意义非常明显。提示显示性能,避免分配不必要的内存。

Android提供了现成的bitmap缩放的API,叫做createScaledBitmap(),使用这种方法能够获取到一张经过缩放的图片。

技术分享

上面的方法能够高速的得到一张经过缩放的图片。可是这种方法能够运行的前提是,原图片须要事先载入到内存中,假设原图片过大,非常可能导致OOM。以下介绍其它几种缩放图片的方式。

inSampleSize能够等比的缩放显示图片。同一时候还避免了须要先把原图载入进内存的缺点。我们会使用相似像以下一样的方法来缩放bitmap:

技术分享
技术分享

另外,我们还能够使用inScaled,inDensity。inTargetDensity的属性来对解码图片做处理,源代码例如以下图所看到的:

技术分享

另一个常常使用到的技巧是inJustDecodeBounds,使用这个属性去尝试解码图片,能够事先获取到图片的大小而不至于占用什么内存。例如以下图所看到的:

技术分享

17)Re-using Bitmaps

我们知道bitmap会占用大量的内存空间,这节会解说什么是inBitmap属性,怎样利用这个属性来提升bitmap的循环效率。前面我们介绍过使用对象池的技术来解决对象频繁创建再回收的效率问题,使用这样的方法,bitmap占用的内存空间会差点儿相同是恒定的数值,每次新创建出来的bitmap都会须要占用一块单独的内存区域。例如以下图所看到的:

技术分享

为了解决上图所看到的的效率问题,Android在解码图片的时候引进了inBitmap属性,使用这个属性能够得到下图所看到的的效果:

技术分享

使用inBitmap属性能够告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的bitmap会尝试去使用之前那张bitmap在heap中所占领的pixel data内存区域,而不是去问内存又一次申请一块区域来存放bitmap。利用这样的特性。即使是上千张的图片,也仅仅会仅仅仅仅须要占用屏幕所能够显示的图片数量的内存大小。以下是怎样使用inBitmap的代码演示样例:

技术分享

使用inBitmap须要注意几个限制条件:

在SDK 11 -> 18之间,重用的bitmap大小必须是一致的。比如给inBitmap赋值的图片大小为100-100,那么新申请的bitmap必须也为100-100才干够被重用。从SDK 19開始,新申请的bitmap大小必须小于或者等于已经赋值过的bitmap大小。
新申请的bitmap与旧的bitmap必须有同样的解码格式,比如大家都是8888的,假设前面的bitmap是8888。那么就不能支持4444与565格式

相关推荐

电脑软件

本类排行

今日推荐

热门手游