Android性能优化实战(二)----界面布局优化

App界面布局是用户能体验到应用性能好坏最直接的方式,如果布局写得不好,App就容易卡顿,严重影响用户体验。通过这篇博客,来学习总结优化Gallery时用到的View布局优化方法。

优化布局层次结构

我们知道,Android View的绘制分为三个过程:measure、layout和draw,首先绘制的父类布局ViewGroup,绘制完父类布局后再对ViewGroup里面的子View绘制,如果你的app布局层次复杂,就会降低绘制的效率。Android SDK自带一个UI性能检测工具 Hierarchy Viewer,我们可以从SDK的tools目录找到该工具,也可以在Android Studio的Android Device Monitor中找到。

这里写图片描述

Tree View界面就为我们直观的展示了当前Activity的View树结构。点击Profile Node,将会重新绘制View Tree,点击某个节点,可以查看绘制该View时的具体信息。

这里写图片描述

这里我们主要关注下面的三个圆圈,从左到右依次,代表View的measure, layout和draw的性能,不同颜色代表不同的性能等级:
1、 绿: 表示该View的此项性能比该View Tree中的至少一半以上的View都要快;
2、黄: 表示该View的此项性能比该View Tree中的至少一半以上的View都要慢;
3、红: 表示该View的此项性能是View Tree中最慢的。
不过以上的指标都是相对于这个View所在的View Tree来比较的,并不是绝对的,也就是说红色并不意味性能差。不过红色的节点View可以会存在性能问题:
1、如果该节点是父节点,而且只有几个子节点,虽然可能实际体验起来并没有问,我们最好借助Systrace或者Traceview工具来获取更多的信息分析一下,看是否存在问题;
2、如果一个父节点有许多的子节点,并且Measure阶段呈现为红色,则需要观察下子节点的绘制情况;
3、如果视图中的根节点,Measure阶段为红色,Layout阶段为红色,Draw阶段为黄色,这个是比较常见的,因为这个节点是所有其它视图的父类;
4、如果一个有很多个View的子节点在Draw阶段是红色的,这明显是有问题的,需要检查一下代码里面的onDraw方法,是否调用正确。
对于父类ViewGroup,我们最常用到的是RelativeLayout和LinearLayout,我们应该如何选择?源码中查看它们的绘制过程:
RelativeLayout的onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ......
    View[] views = mSortedHorizontalChildren;
        int count = views.length;

        for (int i = 0; i < count; i++) {
            View child = views[i];
            if (child.getVisibility() != GONE) {
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                int[] rules = params.getRules(layoutDirection);

                applyHorizontalSizeRules(params, myWidth, rules);
                measureChildHorizontal(child, params, myWidth, myHeight);

                if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
                    offsetHorizontalAxis = true;
                }
            }
        }

        views = mSortedVerticalChildren;
        count = views.length;
        final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;

        for (int i = 0; i < count; i++) {
            final View child = views[i];
            if (child.getVisibility() != GONE) {
                final LayoutParams params = (LayoutParams) child.getLayoutParams();

                applyVerticalSizeRules(params, myHeight, child.getBaseline());
                measureChild(child, params, myWidth, myHeight);
                if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
                    offsetVerticalAxis = true;
                }

                if (isWrapContentWidth) {
                    if (isLayoutRtl()) {
                        if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                            width = Math.max(width, myWidth - params.mLeft);
                        } else {
                            width = Math.max(width, myWidth - params.mLeft - params.leftMargin);
                        }
                    } else {
                        if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                            width = Math.max(width, params.mRight);
                        } else {
                            width = Math.max(width, params.mRight + params.rightMargin);
                        }
                    }
                }

                if (isWrapContentHeight) {
                    if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                        height = Math.max(height, params.mBottom);
                    } else {
                        height = Math.max(height, params.mBottom + params.bottomMargin);
                    }
                }

                if (child != ignore || verticalGravity) {
                    left = Math.min(left, params.mLeft - params.leftMargin);
                    top = Math.min(top, params.mTop - params.topMargin);
                }

                if (child != ignore || horizontalGravity) {
                    right = Math.max(right, params.mRight + params.rightMargin);
                    bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
                }
            }
        }
    ......
}

LinearLayout的onMeasure:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

从RelativeLayout源码的13行和31行分别调用measureChildHorizontal()和 measureChild(),也就是说RelativeLayout会让子View调用2次onMeasure;而LinearLayout 则简单得很多,只会调用子View1次onMeasure,不过查看一下measureVertical或者measureHorizontal会发现,如果在有weight这个属性的时候,LinearLayout也会让子View调用两次onMeasure。这样看来,在没有weight属性的时候,LinearLayout的花销确实要比RelativeLayout的要少,但是如果在嵌套很多子View的情况下,例如:

这里写图片描述

上图中为了在垂直(水平)LinearLayout中再嵌入一个水平(垂直)的布局,只能在嵌入一个LinearLayout,而如果使用RelativeLayout作为父容器的话,明显可以减少一层布局层数:

这里写图片描述

所以,在不响应层级深度的情况下,使用Linearlayout而不是RelativeLayout。

使用布局标签include、merge和viewStub

<include> 标签

有时候我们经常需要重复用到同一布局,如果总是复制粘贴,未免有些麻烦。其实Android当然也已经充分考虑到开发者的这一需求,为我们提供了<include>标签,这个标签的使用很简单,例如:

    <android.support.v7.widget.ContentFrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <include layout="@layout/toolbar_layout"/>

        <include layout="@layout/toolbar_shadow"/>

        <include layout="@layout/album_page_layout"/>

    </android.support.v7.widget.ContentFrameLayout>

此外我们还可以更改<include>标签当中的属性:

        <include layout="@layout/album_page_layout"
            android:layout_height="match_parent"
            android:layout_width="match_parent"/>

这样,以后我们要修改布局文件,只需要修改一处,就可以一劳永逸了。

<merge> 标签

上面我们说道,应该尽量减少我们的布局层次,提高View的绘制效率,<merge> 应运而生,它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套。就拿我们最常用的setContentView(R.layout.activity_main)这个方法来说吧,其实最终系统是把activity_layout这个布局放到id为content的FrameLayout中去,此时,如果我们的activity_main这个布局的根节点也是一个FrameLayout,就产生了一个多余的层次:

这里写图片描述

此时我们就可以用<merge> 来去除多余的嵌套:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <LinearLayout
        android:id="@+id/mainPanel"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:orientation="vertical" >
            <com.tct.gallery3d.filtershow.crop.CropView
                android:id="@+id/cropView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
            <ProgressBar
                android:id="@+id/loading"
                style="@android:style/Widget.Holo.ProgressBar.Large"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:indeterminate="true"
                android:indeterminateOnly="true"
                android:background="@android:color/transparent" />
    </LinearLayout>
</merge>

来看一下修改后的效果,只有一个FrameLayout了:

这里写图片描述

<ViewStub> 标签

在很多时候,会在运行时动态地显示某个布局,通常的做法是设置该布局invisible或者gone,然后在代码中动态的更改它的可见性。虽然把View的初始状态设置为invisible或者gone,但是在加载布局的时候View仍然会被inflate,浪费资源。那么我们如何才能让这些不常用的元素仅在需要时才去加载呢?Android为此提供了一种非常轻量级的控件ViewStub。ViewStub虽说也是View的一种,但是它没有大小,没有绘制功能,也不参与布局,资源消耗非常低,将它放置在布局当中基本可以认为是完全不会影响性能的.

<ViewStub android:id="@+id/stub"
    android:layout="@layout/mySub"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

在使用时候:

    ViewStub stub = findViewById(R.id.stub);
     View inflated = stub.inflate();

但是有一点需要注意的是,ViewStub不支持<merge> 标签。

尽量避免Overdraw

Overdraw(过度绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次重叠的UI结构里面,如果不可见的UI也在做绘制的操作,会导致某些像素区域被绘制了多次。这样就会浪费大量的CPU以及GPU资源。为了获取更好的性能,我们应该尽量避免过度绘制。为了查看我们的app界面是否过度绘制,我们可以通过手机设置里面的开发者选项,打开Show GPU Overdraw的选项,观察UI上的Overdraw情况
这里写图片描述
这里写图片描述

颜色越深,代表过度绘制的情况越严重。下面,我们通过这个工具来查看一下Gallery的绘制情况。可以看到,该界面的过度绘制还是很严重的。通过查看xml文件,我们发现,我们的主题原本就设置了背景,但是在子布局上又重复设置了背景,这样在onDraw的时候就需要多次绘制布局的背景:
这里写图片描述

    <style name="Theme.Gallery" parent="Theme.GalleryBase">
        <item name="android:displayOptions"></item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:actionBarStyle">@style/Holo.ActionBar</item>
        <item name="android:windowBackground">@android:color/black</item>
        <item name="android:colorBackground">@null</item>
        <item name="android:colorBackgroundCacheHint">@null</item>
    </style>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent"
    android:orientation="vertical">
        android:background="@drawable/photopage_actionbar_background"/>

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/bottom_bar_background"
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:background="@drawable/bottom_control_background"
        android:layout_gravity="center_horizontal|bottom"/>

</FrameLayout>

我们在OnCreate()中将background设置为空之后再来看一下界面:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        getWindow().setBackgroundDrawable(null);
    }

修改之后,颜色明显变淡了

这里写图片描述

通常,优化绘制可以通过以下方法:
移除Window默认的Background
移除XML布局文件中非必需的Background
按需显示占位背景图片

以上,就是我在优化Gallery布局时所用到的一些方法,此外Android Studio上为卡发着提供了一个性能检测工具Lint,这样我们就不用手动去查找那个布局是否存在优化的空间,其实Lint的功能远不止这些,详情请看Android官网上的介绍:https://developer.android.google.cn/studio/write/lint.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
文件分类大师只关注有用的文件。 传统的文件管理器不方便对重要资料的查找和分类,使得用户使用起来不方便。文件分类大师能够帮您托管重要资料,让您快速查找,使用和分享。文件分类大师具有以下功能: 1.文件分类大师能够即时拍照,即时归类,让你的在拍照时候也能分门别类的记录管理起来。这样您出去旅游的风景,您调研照片,您上课拍的ppt随拍随分类,就不会再混在相册里鱼龙混珠了。 2.常规的文件管理器只是所有文件包括系统文件等大杂烩,我们找文件很麻烦,有了文件分类大师,我们只需要关心我们需要的文件,再也不必每次都要在成千上万个系统文件夹下找我们的办公和需要的文件了。 3.支持 doc docx ppt pptx xls xlsx mp3 mp4 txt 等等文件格式的打开,支持世界上所有文件。 4.加密功能能够安全保护用户文件。 5.支持多选,批量复制 批量剪切,批量删除 批量导出 批量分享等功能。 一键批量压缩zip格式,一键分享多个文件到QQ,微信,钉钉等等 6.支持秒查,拒绝长时间遍历查找文件夹,一秒就能查到有无文件。 ———————————————— 版权声明:本文为CSDN博主「shaoduo」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/shaoduo/article/details/78145756

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值