您的位置:首页 > 产品设计 > UI/UE

Android UI性能优化(三)

2016-05-28 13:57 441 查看

Overdraw

为了实现UI设计师的设计效果,我们往往会让我们的布局里三层外三层以达到跟效果图一致的效果。但是上层的UI会覆盖下层,下层不可见得UI也会被绘制,也就是屏幕上同一像素点被反复绘制多次,也就是过度绘制了。过度绘制会对我们的UI性能造成非常不利的影响,我们在布局的时候要注意避免过度绘制。介绍在开发者工具中的Show GPU overdraw选项,这个工具可以帮助我们找出UI的过度绘制,优化我们的UI性能。每个手机的开发者选项和Show GPU overdraw选项都不尽相同,不清楚的可以谷歌找到自己机器或者模拟器的选项并打开它。根据颜色就可以判断一个像素,被绘制了几次。



我们先看一个布局文件。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical"
tools:context="com.example.caoxiao.view3.MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="#ffffff"
android:text="Hello World!" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="Hello World!" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ffffff"
android:layout_margin="16dp"
android:text="Button1" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="Button2" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ffffff"
android:orientation="horizontal">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="#ffffff"
android:text="Hello World!" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="Hello World!" />

</LinearLayout>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="#ffffff"
android:text="Hello World!" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="Hello World!" />

</LinearLayout>
</LinearLayout>


效果如图:



我们打开Show GPU overdraw选项再看这个布局。



最浅的颜色是蓝色,最深的已经到深红了,问题很严重。我们回头看布局xml文件找找过度绘制的原因。我们可以看到有backgroud比没有的要多绘制一层,如果布局中有嵌套(LinearLayout中再嵌套LinearLayout而且都有背景色)那么就会出现浅红甚至深红色,严重影响性能。

优化的思路

谨慎的对待背景色,经常会有需求是背景色灰色的,上面的组件是深色的,往往开发者习惯于被activity总体设一个灰色的背景,上面的组件去覆盖这个灰色,这就造成了过度绘制。简单举个例子。



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#b1b1b1"
android:orientation="vertical"
tools:context="com.example.caoxiao.view3.MainActivity">

<View
android:layout_width="match_parent"
android:background="#ffffff"
android:layout_height="100dp"></View>

<View
android:layout_width="match_parent"
android:layout_height="10dp"></View>

<View
android:layout_width="match_parent"
android:background="#ffffff"
android:layout_height="100dp"></View>

</LinearLayout>


优化后,养成好习惯真的很重要。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.caoxiao.view3.MainActivity">

<View
android:layout_width="match_parent"
android:background="#ffffff"
android:layout_height="100dp"></View>

<View
android:layout_width="match_parent"
android:background="#b1b1b1"
android:layout_height="10dp"></View>

<View
android:layout_width="match_parent"
android:background="#ffffff"
android:layout_height="100dp"></View>

<View
android:layout_width="match_parent"
android:background="#b1b1b1"
android:layout_height="10dp"></View>
</LinearLayout>


类似listview这种控件经常会出现没有必要的重复背景,要尽力避免例如上层容器背景是白色,listview的item背景也给了白色。

布局的时候如果能线性排列,就尽可能避免嵌套。

Walkthrough

要通过Walkthrough来优化我们的UI性能首先要了解Android渲染机制。我们使用App的时候有时候会觉得卡顿不流畅,原因是Android每隔16ms触发一次UI渲染,如果这个时候我们的UI还在做一些操作,无法执行渲染,那么就只能等下一个16ms再做尝试,以此类推。如果我们的UI性能差,一个16ms没有触发渲染,用户就能感觉到不流畅,如果很多个16ms都无法渲染,那么用户就会觉得很抓狂,因为在他看来卡顿实在太严重了,用户体验就会很差。Walkthrough能够帮助我们分析出我们在哪里发生了卡顿,我们就可以更有针对性的提升性能。我们同样在开发者选项里面打开它,效果如图(来源官方文档)。



绿色的线很好理解,代表16ms。如果柱状图超过绿线,说明出现了掉帧。

蓝色柱状图代表创建和升级视图display lists的时间,如果这部分很高说明有很多自定义的View要绘制,或者onDraw方法执行了太多事

在安卓4.0或者更高以上,有紫色的柱状图,代表资源传给render thread的时间

红色部分代表Android’s 2D 渲染器 通知OpenGL去绘制或者重绘display lists花费的时间。这一部分很高,很可能是由于View过于复杂造成的。

橙色代表CPU等待GPU完成绘制花费的时间。这部分很高说明GPU做了太多的操作了。

我们根据柱状图,可以分析我们的的UI除了那些问题,原因更可能在哪里,然后进行优化。

硬件加速

从Android 3.0 (API level 11)开始,安卓开始支持硬件加速。硬件加速使用GPU进行View上的绘制操作,所以可以让View绘制更加流畅,大大提升动画的性能。但是缺点也有,我们接下来整理下硬件加速的知识。

如果你的App Target API level is >=14那么硬件加速默认是打开的,你也可以手动关闭它。如果你的应用里面只有标准的views和 Drawables,那么全局打开硬件加速不会有任何问题。但是硬件加速并不支持所有的绘制方法,而且自定义的View或者绘图操作会受到一些影响。问题通常是错误或者异常的显示一些像素,所以Android可以允许你关闭硬件加速。


控制硬件加速

Application

Activity

Window

View

你可以在四个级别控制硬件加速,例如:

<application android:hardwareAccelerated="true" ...>


<application android:hardwareAccelerated="true">
<activity ... />
<activity android:hardwareAccelerated="false" />
</application>


getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);


myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);


注意:View层不能开启硬件加速,因为这一层有其他开启硬件加速的功能依赖。

判断是否开启了硬件加速 View.isHardwareAccelerated() returns true if the

View is attached to a hardware accelerated window.

Canvas.isHardwareAccelerated() returns true if the Canvas is hardware accelerated

Android的绘制模型

Software-based drawing model

1.Invalidate the hierarchy

2.Draw the hierarchy

当应用需要更新部分UI的时候,触发invalidate()。invalidation这个消息就会传递遍所有的View层,计算需要重绘的区域(the dirty region). 所有与需要重绘区域有交集的区域都需要重绘。

首先这个模式每次draw都会执行大量的代码,比如需要重绘的是一个button但是这个button在一个view的上面,那么即使这个view本身没有改变也会被重绘。

隐藏某些bug,因为上面的第一个问题。比如button下面的View在一些条件下需要重绘但是因为一些bug而没有重绘,但是这个时候因为button触发了View的重绘所以掩盖了这个bug,知道button失效的时候又出现了。这会导致每次修改UI的时候都会变化,所以你的数据和状态每次发生变化,为了刷新而去调用invalidate() 。


Hardware accelerated drawing model

依旧使用invalidate() 和 draw() 来绘制和刷新View,但是处理机制不同。安卓系统不是直接执行绘制命令,而是记录下命令到display lists。只针对dirty region执行invalidate()。


1.让hierarchy失效

2.记录和更新display lists

3.绘制display lists

你不能再指望利用dirty region来刷新View,每个需要刷新的View都需要invalidate()来刷新,否则即使View发生了变化也不会刷新。
使用display lists对动画性能会有好处,因为某些明确的属性,例如alpha或者rotation,不会触发invalidate()会自动执行。
优化也可以应用于所有display lists的视图(当View被应用硬件加速的时候)。假设有一个LinearLayout,包含上述Button的ListView 。对于LinearLayout的display list 看起来像这样:


DrawDisplayList(ListView)

DrawDisplayList(Button)

假如你现在要改变 ListView的透明度,在执行setAlpha(0.5f) 之后,这个display list 现在就包括:

SaveLayerAlpha(0.5)

DrawDisplayList(ListView)

Restore

DrawDisplayList(Button)

ListView的绘制方法没有再次执行,如果没有开启硬件加速,listview和父类的绘制方法会再次执行。

不支持的绘制操作

并不是所有的方法都支持硬件加速。不支持的API可以在官方文档上面查到。https://developer.android.com/guide/topics/graphics/hardware-accel.html#unsupported

View Layers

安卓的所有版本,都支持off-screen buffers,可以通过Canvas.saveLayer()来设置。Off-screen buffers, 或者 layers, 有很多用。当使用复杂的动画效果时,可以提升性能。

LAYER_TYPE_NONE:View对象用普通的方式来呈现,并且不是由屏幕外缓存来返回的。这种类型是默认的行为;

LAYER_TYPE_HARDWARE:如果应用程序是硬件加速的,那么该View对象被呈现在硬件的一个硬件纹理中。如果没有被硬件加速,那么这种层类型的行为与LAYER_TYPE_SOFTWARE相同。

LAYER_TYPE_SOFTWARE:View对象会被呈现在软件的一个位图中。

使用哪种层的类型,依赖以下目标:

性能:使用硬件层类型,把View呈现到一个硬件纹理中。一旦该View对象被呈现到一个层中,那么它的绘图代码直到调用该View对象的invalidate()方法时才会被执行。对于某些动画,如alpha动画,就能够直接使用该层,这么做对于GPU来说是非常高效的。

视觉效果:使用硬件或软件层类型和一个Paint对象,能够把一些特殊的视觉处理应用给一个View对象。例如,使用ColorMatrixColorFilter对象绘制一个黑白相间的View对象。

兼容性:使用软件层类型会强制把一个View对象呈现在软件中。如果View对象被硬件加速(例如,如果整个应用程序都被硬件加速)发生呈现问题,那么使用软件层类型来解决硬件呈现管道的限制是一个容易的方法。

Tips and Tricks

使用硬件加速可以提升性能,但是为了更有效的使用GPU,在设计你的应用的时候应该遵循下面的建议。

减少View的数量,View越多绘制越慢

避免过度绘制,上面讲过这个问题了

在draw方法不要创建渲染对象,就是在draw方法不要去创建Paint等对象,这会引起频繁GC引起性能下降

不要频繁改变形状,复杂的形状,Path,Circle等实例都是通过texture masks渲染的。每次你创建或者修改path,都要新建一个mask,代价太高了。

不要频繁改动bitmap,每次改变Bitmap的内容,都会再次上传一个GPU texture以便下次重新绘制。

小心使用透明度alpha,当你通过 setAlpha(), AlphaAnimation, or ObjectAnimator改变View的透明度,都需要一个双倍的填充率来做off-screen buffer。当你把一个很大的View设置透明度的时候,建议考虑设置layer type成LAYER_TYPE_HARDWARE模式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: