Android性能优化典范(B3)

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里面执行内存分配的操作,会容易导致内存抖动,GC频繁被触发,虽然GC后来被改进为执行在另外一个后台线程(GC操作在2.3以前是同步的,之后是并发),可是频繁的GC的操作还是会影响到CPU,影响到电量的消耗。

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


11) Tool: Strict Mode

UI线程被阻塞超过5秒,就会出现ANR,这太糟糕了。防止程序出现ANR是很重要的事情,那么如何找出程序里面潜在的坑,预防ANR呢?很多大部分情况下执行很快的方法,但是他们有可能存在巨大的隐患,这些隐患的爆发就很容易导致ANR。

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

[js]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public void onCreate() {  
  2.      if (DEVELOPER_MODE) {  
  3.          StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()  
  4.                  .detectDiskReads()  
  5.                  .detectDiskWrites()  
  6.                  .detectNetwork()   // or .detectAll() for all detectable problems  
  7.                  .penaltyLog()  
  8.                  .build());  
  9.          StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()  
  10.                  .detectLeakedSqlLiteObjects()  
  11.                  .detectLeakedClosableObjects()  
  12.                  .penaltyLog()  
  13.                  .penaltyDeath()  
  14.                  .build());  
  15.      }  
  16.      super.onCreate();  
  17. }  

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季以及上一期的性能优化之电量篇里面,我们有提到过移动蜂窝模块的电量消耗模型。为了避免我们的应用程序过多的频繁消耗电量,我们需要学习如何把后台任务打包批量,并选择一个合适的时机进行触发执行。下图是每个应用程序各自执行后台任务导致的电量消耗示意图:


因为像上面那样做会导致浪费很多电量,我们需要做的是把部分应用的任务延迟处理,等到一定时机,这些任务一并进行处理。结果如下面的示意图:


执行延迟任务,通常有下面三种方式:

  • AlarmManager:使用AlarmManager设置定时任务,可以选择精确的间隔时间,也可以选择非精确时间作为参数。除非程序有很强烈的需要使用精确的定时唤醒,否者一定要避免使用他,我们应该尽量使用非精确的方式。

  • SyncAdapter:我们可以使用SyncAdapter为应用添加设置账户,这样在手机设置的账户列表里面可以找到我们的应用。这种方式功能更多,但是实现起来比较复杂(Google官方培训课程

  • JobSchedulor:这是最简单高效的方法,我们可以设置任务延迟的间隔,执行条件,还可以增加重试机制。

14) Smaller Pixel Formats

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

Android的Heap空间是不会自动做兼容压缩的,意思就是如果Heap空间中的图片被收回之后,这块区域并不会和其他已经回收过的区域做重新排序合并处理,那么当一个更大的图片需要放到heap之前,很可能找不到那么大的连续空闲区域,那么就会触发GC,使得heap腾出一块足以放下这张图片的空闲区域,如果无法腾出,就会发生OOM。如下图所示:


所以为了避免加载一张超大的图片,需要尽量减少这张图片所占用的内存大小,Android为图片提供了4种解码格式,他们分别占用的内存大小如下图所示:


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值