Android绘制优化(一)绘制性能分析

Android绘制优化(一)绘制性能分析

http://liuwangshu.cn/application/performance/draw-1-performance.html

Android绘制优化(二)绘制性能分析

http://liuwangshu.cn/application/performance/draw-2-layout.html

 2017-03-13

 ANDROID应用层

 ANDROID性能优化ANDROID绘制优化

阅读 2457

1417629-3fa7a007a8729dc3_副本_副本.png1417629-3fa7a007a8729dc3_副本_副本.png
相关文章
Android性能优化系列

前言

一个优秀的应用不仅仅是要有吸引人的功能和交互,同时在性能上也有很高的要求。运行Android系统的手机,虽然配置在不断的提升,但仍旧无法和PC相比,无法做到PC那样拥有超大的内存以及高性能的CPU,因此在开发Android应用程序时也不可能无限制的使用CPU和内存,如果对CPU和内存使用不当也会造成应用的卡顿和内存溢出等问题。因此,应用的性能优化对于开发人员有着更高的要求。Android性能优化分为很多种,比较常用的有绘制优化、内存优化、耗电优化和稳定性优化等,这个系列我们就来学习性能优化中的绘制优化。

1.绘制原理

Android绘制View有三个主要的步骤,分别是measure、layout和draw。关于它们的原理请查看我的文章Android View体系(七)从源码解析View的measure流程Android View体系(八)从源码解析View的layout和draw流程,这里就不在赘述。measure、layout和draw方法主要是运行在系统的应用框架层,而真正将数据渲染到屏幕上的则是系统Nativie层的SurfaceFlinger服务来完成的。

绘制过程主要是由CPU 来进行Measure、Layout、Record、Execute的数据计算工作,GPU负责栅格化、渲染。CPU和GPU是通过图形驱动层来进行连接的。图形驱动层维护了一个队列,CPU将display list添加到该队列中,这样GPU就可以从这个队列中取出数据进行绘制。

1.1 渲染时间线

FPS(Frames Per Second)这个名词我想很多同学都知道,它是指画面每秒传输帧数,通俗来讲就是指动画或视频的画面数,最简单的举例就是我们玩游戏时,如果画面在60fps则不会感觉到卡顿,如果低于60fps,比如50fps则会感觉到卡顿,你就可以考虑要换显卡或者采取其他一些措施了。
要想画面保持在60fps,则需要每个绘制时长在16ms以内,如下图所示。

04080416_dgEb.png04080416_dgEb.png

Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染, 如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,那什么是VSYNC呢?VSYNC是Vertical Synchronization(垂直同步)的缩写,是一种定时中断,一旦收到VSYNC信号,CPU就开始处理各帧数据。
如果某个操作要花费24ms,这样系统在得到VSYNC信号时无法进行正常的渲染,会发生丢帧。用户会在32ms中看到同一帧的画面,如下图所示。

04080416_cWwX.png04080416_cWwX.png

产生卡顿原因有很多,主要有以下几点:

  • 布局Layout过于复杂,无法在16ms内完成渲染。
  • 同一时间动画执行的次数过多,导致CPU或GPU负载过重。
  • View过度绘制,导致某些像素在同一帧时间内被绘制多次。
  • UI线程中做了稍微耗时的操作。

为了解决上述的问题,除了我们要在写代码时要注意外,也可以借助一些工具来分析和解决卡顿问题。

2.Profile GPU Rendering

Profile GPU Rendering是Android 4.1系统提供的开发辅助功能,我们可以在开发者选项中打开这一功能,如下图所示。

打开Profile GPU Rendering_副本_副本.png打开Profile GPU Rendering_副本_副本.png
我们点击Profile GPU Rendering选项并选择On screen as bars即开启Profile GPU Rendering功能。接着屏幕会显示出彩色的柱状图,如下所示。

Screenshot_20170308-152841_副本.pngScreenshot_20170308-152841_副本.png
上面的彩色的图的横轴代表时间,纵轴表示某一帧的耗时。绿色的横线为警戒线,超过这条线则意味着时长超过了16m,尽量要保证垂直的彩色柱状图保持在绿线下面。这些垂直的彩色柱状图代表着一帧,不同颜色的彩色柱状图代表不同的含义:

  • 橙色代表处理的时间,是CPU告诉GPU渲染一帧的地方,这是一个阻塞调用,因为CPU会一直等待GPU发出接到命令的回复,如果橙色柱状图很高,则表明GPU很繁忙。
  • 红色代表执行的时间,这部分是Android进行2D渲染 Display List的时间。如果红色柱状图很高,可能是由重新提交了视图而导致的。还有复杂的自定义View也会导致红的柱状图变高。
  • 蓝色代表测量绘制的时间,也就是需要多长时间去创建和更新DisplayList。如果蓝色柱状图很高,可能是需要重新绘制,或者View的onDraw方法处理事情太多。

在Android 6.0中,有更多的颜色被加了进来,如下图所示:
QQ截图20170320133358.pngQQ截图20170320133358.png

下面来分别介绍它们的含义:

  • Swap Buffers:表示处理的时间,和上面讲到的橙色一样。
  • Command Issue:表示执行的时间,和上面讲到的红色一样。
  • Sync & Upload:表示的是准备当前界面上有待绘制的图片所耗费的时间,为了减少该段区域的执行时间,我们可以减少屏幕上的图片数量或者是缩小图片的大小。
  • Draw:表示测量和绘制视图列表所需要的时间,和上面讲到的蓝色一样。
  • Measure/Layout:表示布局的onMeasure与onLayout所花费的时间,一旦时间过长,就需要仔细检查自己的布局是不是存在严重的性能问题。
  • Animation:表示计算执行动画所需要花费的时间,包含的动画有ObjectAnimator,ViewPropertyAnimator,Transition等。一旦这里的执行时间过长,就需要检查是不是使用了非官方的动画工具或者是检查动画执行的过程中是不是触发了读写操作等等。
  • Input Handling:表示系统处理输入事件所耗费的时间,粗略等于对事件处理方法所执行的时间。一旦执行时间过长,意味着在处理用户的输入事件的地方执行了复杂的操作。
  • Misc Time/Vsync Delay:表示在主线程执行了太多的任务,导致UI渲染跟不上VSYNC的信号而出现掉帧的情况。

Profile GPU Rendering可以找到渲染有问题的界面,但是想要修复的话,只依赖Profile GPU Rendering是不够的,可以用另一个工具Hierarchy Viewer来查看布局层次和每个View所花的时间,这个工具会在下一篇文章进行介绍。

3.Systrace

Systrace是Android4.1中新增的性能数据采样和分析工具。它可帮助开发者收集Android关键子系统(SurfaceFlinger、WindowManagerService等Framework部分关键模块、服务,View体系系统等)的运行信息。Systrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等。对于UI显示性能,比如动画播放不流畅、渲染卡顿等问题提供了分析数据。

3.1 使用Systrace

Systrace跟踪的设备要在Android4.1版本以上,对于Android4.3版本之前和4.3版本之后使用上有点区别,现在也很少有人用Android4.3之前的版本,因此这里只讲Android4.3版本的使用方法。Systrace可以在DDMS上使用,可以使用命令行来使用,也可以在代码中进行跟踪。接下来分别来介绍这三种方式。
在DDMS中使用Systrace
1.首先我们要打开Android Studio的Tool中的Android Device Monitor,并连接手机。
2.点击Systrace按钮进入抓取设置界面,如下图所示。


抓取设置界面可以设置跟踪的时间,以及trace文件输出的地址等内容。如下图所示。
QQ截图20170311224620_副本.pngQQ截图20170311224620_副本.png

3.设置完成后,我们就来操作的跟踪的过程。跟踪时间结束后,生成trace.html文件。
4.用Chrome打开trace.html文件进行分析。分析的方法,后文会讲到。

用命令行使用Systrace
Android 提供一个python脚本文件 systrace.py,它位于Android SDK 目录 /tools/systrace 中,我们可以执行以下命令来使用Systrace:

$ cd android-sdk/platform-tools/systrace
$ python systrace.py --time=10 -o newtrace.html sched gfx view wm

在代码中使用Systrace
Systrace并不会追踪应用的所有工作,在Android4.3及以上版本的代码中,可以使用Trace类对应用中的具体活动进行追踪。
Android源码中也引用了Trace类,比如RecyclerView:

...
 private final Runnable mUpdateChildViewsRunnable = new Runnable() {
        public void run() {
            if (!mFirstLayoutComplete) {
                return;
            }
            if (mDataSetHasChangedAfterLayout) {
                TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
                dispatchLayout();
                TraceCompat.endSection();
            } else if (mAdapterHelper.hasPendingUpdates()) {
                TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG);
                eatRequestLayout();
                mAdapterHelper.preProcess();
                if (!mLayoutRequestEaten) {
                    rebindUpdatedViewHolders();
                }
                resumeRequestLayout(true);
                TraceCompat.endSection();
            }
        }
    };
    ...

TraceCompat类对Trace类进行了封装,只会在Android4.3及以上版本才会使用Trace类,其中beginSection方法和endSection方法之间的代码会被追踪,endSection方法会只会结束最近的beginSection方法,因此要保证beginSection方法和endSection方法的调用次数要相同。

3.2 用Chrome分析Systrace

通过前面的方法生成的trace.html需要用Chrome打开,打开后效果如下图所示。

我们可以使用W键和S键进行放大和缩小,A键和D键进行左右移动。
Alert区域
首先来看Alert区域,这一区域会标记处性能有问题的点,单击叹号图标就可以查看某一个Alert的问题描述,如下所示。

这个Alert指出了View在Measure/Layout时耗费了大量的时间,导致出现jank(同一帧画了多次)。给出的建议是避免在动画播放期间控制布局。

CPU区域
接下来我们来查看CPU区域,每一行代表一个CPU核心和它执行任务的时间片,放大后会看到每个色块代表一个执行的进程,色块的长度代表其执行时间,如下图所示。

图中CPU 0主要执行adbb线程和InputReader线程,CPU 2主要执行了surfaceflinger线程和ordinatorlayout进程中的RenderThread线程,我们点击RenderThread色块,会给出RenderThread的相关信息,如下图所示。

图中给出了当前色块所运行的线程和进程、开启时间和持续时间等信息。

应用区域
应用区域会显示应用的帧数,如下图所示。

Systrace会给出应用中的Frames分析,每一帧就是一个F圆圈,F圆圈有三种颜色,其中绿色表示Frame渲染流畅,黄色和红色则代表渲染时间超过了16.6ms,其中红的更严重些。我们点击红色F圆圈,会给出该Frame的信息,如下图所示。

从图中可以看出,Frame给出了问题提示:Scheduling delay(调度延迟),当一帧绘制时间超过19ms会触发该提示,更何况这一帧已经有将近40ms了。导致这一问题产生的原因主要是线程在绘制时,在很长一段时间都没有分配到CPU时间片,因此无法继续进行绘制。按m键来高亮该时间段,我们来查看CPU的情况,如下图所示。

可以看出这个时间段中两个CPU都在满负荷运行。至于具体是什么让CPU繁忙,则需要使用Traceview来进行分析。

Alerts总体分析
点开最右边的Alerts按钮会给出Alert的总体分析,如下图所示。
QQ截图20170312150637.pngQQ截图20170312150637.png

Alerts会给出Alert类型,以及出现的次数。有了这些总体的分析,方便开发者对该时间段的绘制性能有一个整体的大概了解,便于进行下一步分析。
由于Systrace 是以系统的角度返回一些信息,只能为我们提供一个概览,它的深度是有限的,我们可以用它来进行粗略的检查,以便了解大概的情况,但是如果要分析更详细的,比如要找到是什么让CPU繁忙,某些方法的调用次数等,则还要借助另一个工具:Traceview。

4.Traceview

TraceView是Android SDK中自带的数据采集和分析工具。一般来说,通过TraceView我们可以得到以下两种数据:

  • 单次执行耗时的方法。
  • 执行次数多的方法。

4.1 使用Traceview

要分析Traceview,则首先要得到一个trace文件,trace文件的获取有两种方式,分别是在DDMS中使用和在代码中加入调试语句,下面分别对这两种方式进行介绍。

DDMS中使用
1.首先我们要打开Android Studio的Tool中的Android Device Monitor,并连接手机。
2.选择相应的进程,并单击Start Method Profiling按钮。
3.对应用中需要监控的点进行操作。
4.单击Stop Method Profiling按钮,会自动跳到TraceView视图。

代码中加入调试语句
如果开发中出现不好复现的问题,则需要在代码中添加TraceView监控语句,代码如下所示。

Debug.startMethodTracing();
...
Debug.stopMethodTracing();

在开始监控的地方调用startMethodTracing方法,在需要结束监控的地方调用stopMethodTracing方法。系统会在SD卡中生成trace文件,将trace文件导出并用SDK中的Traceview打开即可。当然不要忘了在manifest中加入 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>权限。

4.2 分析Traceview

为了分析Traceview,我们来举一个简单的例子来生成trace文件,这里采用第二种方式:代码中加入调试语句。代码如下所示。

public class CoordinatorLayoutActivity extends AppCompatActivity {
    private ViewPager mViewPager;
    private TabLayout mTabLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tab_layout);
        Debug.startMethodTracing("test");//1
        initView();
   ...
    }
    private void initView() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void onStop() {
        super.onStop();
        Debug.stopMethodTracing();
    }
}

在注释1处调用了startMethodTracing方法开始监控,其中test是生成的trace文件的名称。在initView中我们特意调用sleep方法来做耗时操作。在onStop方法中我们调用了stopMethodTracing方法结束监控。这时会在SD卡根目录生成test.trace文件,我们将该文件导出到桌面,用Traceview来分析test.trace文件,我们在cmd中执行如下语句。

我们进入traceview所在的目录(直接将traceview.bat拖入到cmd中),并执行上图的traceview语句后会弹出Traceview视图,它分为两部分,分别是时间片面板和分析面板,我们先来看时间片面板,如下图所示。


其中x轴代表时间的消耗,单位为ms,y轴代表各个线程。一般会查看色块的长度,明显比较长的方法重点去关注,具体的分析还得看分析面板,如下图所示。

每一列数据的代表的含义如下表所示。

列名含义
Name该线程运行过程中调用的函数名
Incl Cpu Time%某个方法包括其内部调用的方法所占用CPU时间百分比
Excl Cpu Time%某个方法不包括其内部调用的方法所占用CPU时间百分比
Incl Real Time%某个方法包括其内部调用的方法所占用真实时间百分比
Excl Real Time%某个方法不包括其内部调用的方法所占用真实时间百分比
Calls + Recur Calls / Total某个方法次数+递归调用次数
Cpu Time / Call该方法平均占用CPU时间
Cpu Time / Call该方法平均占用真实时间
Incl Cpu Time某个方法包括其内部调用的方法所占用CPU时间
Excl Cpu Time某个方法不包括其内部调用的方法所占用CPU时间
Incl Real Time某个方法包括其内部调用的方法所占用真实时间
Excl Real Time某个方法不包括其内部调用的方法所占用真实时间

因为我们用sleep方法来进行耗时操作,所以这里我们可以单击Incl Real Time来进行降序排列。其中有很多系统调用的方法,我们来进行一一过滤。最终我们发现了CoordinatorLayoutActivity的initView方法Incl Real Time的时间为1000.493ms,这显然有问题,如下图所示。

从图中我们可以看出是调用sleep方法导致的耗时。关于Traceview还有很多种分析情况,就需要大家在平时进行积累了。
好了关于绘制性能分析,就讲到这,如果觉得不过瘾,本系列的后续文章还有大波的内容会持续向你砸来。

参考资料
《Android群英传 神兵利器》
《Android应用性能优化最佳实践》
http://blog.csdn.net/itachi85/article/details/6857324
http://www.cnblogs.com/sunzn/p/3192231.html
http://blog.csdn.net/androiddevelop/article/details/8223805
http://www.tuicool.com/articles/jMfiUjj
http://www.mobile-open.com/2015/85005.html

 

相关文章
Android性能优化系列

前言

我们知道一个界面的测量和绘制是通过递归来完成的,减少布局的层数就会减少测量和绘制的时间,从而性能就会得到提升。当然这只是布局优化的一方面,那么如何来进行布局的分析和优化呢?本篇文章会给你一个满意的答案。

1.布局优化工具

在讲到如何去布局优化前,我们先来学习两种布局优化的工具。

1.1 Hierarchy Viewer

Hierarchy Viewer是Android SDK自带的可视化的调试工具,用来检查布局嵌套和绘制的时间。需要注意的是在在Android的官方文档中提到:出于安全考虑,Hierarchy Viewer只能连接Android开发版手机或是模拟器。
首先我们在Android Studio中选择Tools->Android->Android Device Monitor,在Android Device Monitor中选择Hierarchy Viewer ,如下图所示:


选择Hierarchy Viewer后会进出Hierarchy Viewer窗口,如下图所示。

Hierarchy Viewer中有4个四个子窗口,它们的的作用为:

  • Windows:当前设备所有界面列表。
  • Tree View:将当前Activity的所有View的层次按照高层到低层从左到右显示出来。
  • Tree Overview:全局概览,以缩略的形式显示。
  • Layout View:整体布局图,以手机屏幕上真实的位置呈现出来。单击某一个控件,会在Tree Overview窗口中显示出对应的控件。

根据上面讲到的Hierarchy Viewer的4个四个子窗口,我们可以很容易的查看我们布局控件的层级关系。当然Hierarchy Viewer还可以查看某一个View的耗时,我们可以选择某一个View,然后单击下图红色箭头标识的按钮,这里我们把他简称为Layout Time按钮。


从图中可以看出被选中的RelativeLayout自身的Measure、Layout和Draw的耗时数据都为n/a。单击Layout Time按钮后,就可以查看View的耗时情况了,如下图所示。

QQ截图20170324170337.pngQQ截图20170324170337.png

从图中可以看出,被选中的LinearLayout给出了自身Measure、Layout和Draw的耗时,并且它所包含的View中都有了三个指示灯,分别代表当前View在Measure、Layout和Draw的耗时,绿色代表比其他50%View的同阶段(比如Measure阶段)速度要快,黄色则代表比其他50%View同阶段速度要慢,红色则代表比其他View同阶段都要慢,则需要注意了。如果想要看View的具体耗时,则点击该View就可以了。

1.2 Android Lint

Android lint是在ADT 16提供的新工具,它是一个代码扫描工具,通过代码静态检查来发现代码出现的潜在问题,并给出优化建议。检查的范围主要有以下几点:

  • Correctness 正确性
  • Security 安全性
  • Performance 性能
  • Usability 可用性
  • Accessibility 可达性
  • Internationalization 国际化

Android Lint功能十分强大,这里我们只关注XML布局检查,我们可以通过Android Studio的Analyze->Inspect Code来配置检查的范围,如下图所示。

点击上图的OK按钮后,就会进行代码检查,检查的结果如下图所示。

图中列出了项目中出现的问题种类,以及每个问题种类的个数,问题种类包括我们前面提到的Correctness 、Internationalization 、Performance等。我们点击展开最后的XML一项,点击一个问题,就会出现如下图的提示。

可以看出给出了Namespace declaration is never used的提示,并指出了问题所在的文件和行数,我们点击数字3,直接跳入到问题的代码,发现如下代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    ...

第三行的Namespace 确实没有被用到。如果想要自定义Android Lint的检查提示,可以通过File->Settings->Editor->Inspections中来配置Android Lint,如下图所示。

从图中可以发现,我们可以配置Android Lint检查的范围以及问题的严重等级。

2.布局优化方法

布局的优化方法很多,主要包括合理运用布局、Include、Merge、ViewStub,下面我们来一一对这些内容进行讲解。

2.1 合理运用布局

我们常用的布局主要有LinearLayout、RelativeLayout和FrameLayout等,合理的使用它们可以使得Android绘制工作量变少,性能得到提高。我们来举个简单的例子。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="布局优化" />
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Merge" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ViewStub" />
    </LinearLayout>
</LinearLayout>

上面的代码用了两个LinearLayout来进行布局,运行效果如下图所示。


我们用Hierarchy Viewer来查看层级情况,如下图所示。

可以看到我们的布局共有3层,一共含有5个View。如果我们用RelativeLayout来进行改写呢?代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv_text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="布局优化" />
    <TextView
        android:id="@+id/tv_text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@id/tv_text1"
        android:text="Merge" />
    <TextView
        android:id="@+id/tv_text3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv_text2"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@+id/tv_text1"
        android:text="ViewStub" />
</RelativeLayout>

我们只用了一个RelativeLayout来进行布局。用Hierarchy Viewer来查看层级情况,如下图所示。

布局共有两层,一共含有4个View。从这里我们就可以看出我们用RelativeLayout减少了一层的布局,当然这只是一个简单例子,如果布局复杂,那么合理的用RelativeLayout来替代LinearLayout会减少很多层布局。
如果根据上面的例子来看,RelativeLayout的性能是比LinearLayout低,因为RelativeLayout中的View的排列方式是基于彼此依赖的。但是在实际开发中面对的情况比较多,不能轻易的说谁的性能更好。一般情况下,但是如果布局层数较多时,推荐用RelativeLayout来实现。如果布局嵌套较多,推荐使用LinearLayout来实现。

2.2 使用Include标签来进行布局复用

一个很常见的场景就是,多个布局需要复用一个相同的布局,比如一个TitleBar。如果这些界面都要加上这个相同布局TitleBar,维护起来就就很麻烦,我们需要复制TitleBar的布局到每个需要添加的界面,这样容易发生遗漏。如果需要修改TitleBar则需要去每个引用TitleBar的布局进行修改。为了解决这些问题,我们可以用Include标签来解决。
首先我们先来写一个简单的TitleBar布局:titlebar.xml 如下所示。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:background="@android:color/darker_gray">
    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:src="@drawable/ico_left"
        android:padding="3dp"
        android:layout_gravity="center"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="绘制优化" />
</LinearLayout>

这个TitleBar由ImageView和TextView组成,下面我们将TitleBar引入到我们此前用过的布局中,如下所示。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <include layout="@layout/titlebar" />
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_text1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="布局优化" />
     ...
    </RelativeLayout>
</LinearLayout>

可以看到我们用include标签引入了titlebar布局,运行效果如下图所示。

QQ截图20170324182607.pngQQ截图20170324182607.png

2.3 用Merge标签去除多余层级

Merge意味着合并,在合适的场景使用Merge标签可以减少多余的层级。Merge标签一般和Include标签搭配使用,上面的例子,我们用Hierarchy Viewer来查看布局层级,如下图所示。

可以看到我们用Include标签引用的布局的根布局是一个LinearLayout。如果我们使用Merge标签来替换LinearLayout呢?titlebar.xml 的代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:background="@android:color/darker_gray
   >
    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:src="@drawable/ico_left"
        android:padding="3dp"
        android:layout_gravity="center"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="绘制优化" />
</merge>

这时我们再用Hierarchy Viewer来查看布局层级,如下图所示。

此前的根布局LinearLayout没有了,但是这里用merge标签来替代LinearLayout会导致LinearLayout失效,布局就会错乱。merge标签最好是来替代FrameLayout,或者是布局方向一致的LinearLayout,比如当前父布局LinearLayout的布局方向是垂直的,包含的子布局LinearLayout的布局方向也是垂直的则可以用merge标签,而本场景TitleBar的根布局LinearLayout的布局方向是水平的,显然并不符合这一要求。但是如果执意想要在本场景使用merge标签也是可以的,就是用继承自LinearLayout的自定义View,代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <com.example.liuwangshu.moonlayout.TitleBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:background="@android:color/darker_gray">
    </com.example.liuwangshu.moonlayout.TitleBar>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
    <TextView
            android:id="@+id/tv_text1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="布局优化" />
      ...
    </RelativeLayout>
</LinearLayout>

上面布局中TitleBar就是一个自定义View,它继承LinearLayout。我们在TitleBar标签中添加此前的LinearLayout的属性:android:orientation和android:background。

2.4 使用ViewStub来提高加载速度

一个很常见的开发场景就是我们想要一个布局时,并不是所有的控件都需要显示出来,而是显示出一部分,对于这种情况,我们一般采用的方法就是使用View的GONE和INVISIBLE,但是这种方法效率不高,虽然是达到了隐藏的目的,但是仍在布局当中,系统仍然会解析它们,我们可以用ViewStub来解决这一问题。
ViewStub是轻量级的View,不可见并且不占布局位置。当ViewStub调用inflate方法或者设置可见时,系统会加载ViewStub指定的布局,然后将这个布局添加到ViewStub中,在对ViewStub调用inflate方法或者设置可见之前,它是不占布局空间和系统资源的,它主要的目的就是为目标视图占用一个位置。因此,使用ViewStub可以提高界面初始化的性能,从而提高界面的加载速度。
我们首先在布局中加入ViewStub标签,布局代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
  <ViewStub
      android:id="@+id/viewsub"
      android:layout_width="match_parent"
      android:layout_height="40dp"
      android:layout="@layout/titlebar"/>
   ...
</LinearLayout>

ViewStub标签中用android:layout引用了此前写好的布局titlebar.xml。这时我们运行程序,ViewStub标签所引用的布局是显示不出来的,因为引该局还没有加载到ViewStub中,接下来在代码中使用ViewStub:

public class MainActivity extends AppCompatActivity {
    private ViewStub viewsub;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        viewsub= (ViewStub) findViewById(R.id.viewsub);
//        viewsub.inflate();//1
        viewsub.setVisibility(View.VISIBLE);//2
    }
}

可以使用注释1和注释2处的代码来将ViewStub引用的布局加载到ViewStub中,这样引用的布局就显示了出来。
在使用ViewStub时需要主要以下问题:

  • ViewStub只能加载一次,加载后ViewStub对象会被置为空,这样当ViewStub引用的布局被加载后,就不能用ViewStub来控制引用的布局了。因此,如果一个控件需要不断的显示和隐藏,还是要使用View的Visibility属性。
  • ViewStub不能嵌套Merge标签。
  • ViewStub操作的是布局文件,如果只是想操作具体的View,还是要使用View的Visibility属性。

3.避免GPU过度绘制

什么是过度绘制呢?我们来打个比方,假设你要粉刷房子的墙壁,一开始刷了绿色,接着又刷了黄色,这样黄色就将绿色盖住,也就说明第一次的大量粉刷工作白做了。同样手机屏幕绘制也是如此,过度绘制是指在屏幕上某个像素在同一帧的时间内被绘制多次,从而浪费了GPU和CPU的资源。产生这一原因主要有两个原因:

  • 在XML布局中,控件有重叠且都有设置背景。
  • View的OnDraw中同一区域绘制多次。

过度绘制是不可避免的,但是过多的过度绘制会浪费很多资源,并且导致性能问题,因此,避免过度绘制是十分必要的。我们可以用Android系统中自带的工具来检测过度绘制。首先要保证系统版本在Android 4.1以上,接着在开发者选项中打开调试GPU过度绘制选项就可以进入GPU过度绘制模式,如下图所示。

这时屏幕会出现出各种颜色,主要有以下几种,如下图所示。

各个颜色的定义为:

  • 原色: 没有过度绘制 – 每个像素在屏幕上绘制了一次。
  • 蓝色: 一次过度绘制 – 每个像素点在屏幕上绘制了两次。
  • 绿色: 两次过度绘制 – 每个像素点在屏幕上绘制了三次。
  • 粉色: 三次过度绘制 – 每个像素点在屏幕上绘制了四次。
  • 红色: 四次或四次以上过度绘制 – 每个像素点在屏幕上绘制了五次或者五次以上。

最理想的是蓝色,一个像素只绘制一次,合格的页面绘制是白色、蓝色为主,绿色以上区域不能超过整个的三分之一,颜色越浅越好。

避免过度绘制主要有以下几个方案:
1.移除不需要的background。
2.在自定义View的OnDraw方法中,用canvas.clipRect来指定绘制的区域,防止重叠的组件发生过度绘制。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值