精华内容
参与话题
问答
  • 布局优化的几种方法

    2016-01-27 11:34:15
    布局优化的三种方法

    使用include包含公共布局

    就像在C或者JAVA中,可以将重复的代码段写成子函数调用;在XML中,对于重复的布局也可以写在单独的文件中,然后用<inclulde>来“调用”。
    例如自定义了一个标题栏common_title_bar.xml:

    <?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="50dp"
        android:background="#484E61" >
    
        <ImageButton
            android:id="@+id/switch_city"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="10dp"
            android:background="@drawable/ic_home_white_24dp" />
    
        <TextView
            android:id="@+id/city_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:textColor="#fff"
            android:textSize="24sp" />
    
        <ImageButton
            android:id="@+id/refresh_weather"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="10dp"
            android:background="@drawable/ic_refresh_white_24dp" />
    
    </RelativeLayout>

    在其他的布局中,可以使用<include>标签包含这个标题栏:

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

    使用merge合并布局

    使用<merge>标签可以避免过度嵌套。
    例如,公共布局使用的是垂直方向的LinearLayout,而主布局使用的也是垂直方向的LinearLayout,那么,在主布局include这个公共布局的时候,就会出现两个LinearLayout相互嵌套,而内层的LinearLayout没有发挥任何作用,反而降低了效率。这时候就可以使用<merge>作为公共布局的根元素,这样在include的时候就会自动忽略根元素,避免了嵌套。

    <merge xmlns:android="http://schemas.android.com/apk/res/android">
    
        <Button
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content"
            android:text="@string/add"/>
    
        <Button
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content"
            android:text="@string/delete"/>
    
    </merge>

    使用ViewStub动态加载布局

    如果有一部分布局很少用到,可以使用<ViewStub>标签,在需要的时候再将布局加载到内存中,这样可以大大减少对内存的占用。
    <ViewStub>标签中,可以使用android:layout指定需要加载的布局。例如需要动态加载一个透明的进度条:

    <ViewStub
        android:id="@+id/stub_import"
        android:inflatedId="@+id/panel_import"
        android:layout="@layout/progress_overlay"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom" />

    在代码中加载的方法有两种:

    • 使用setVisibility(View.VISIBLE)方法
    • 调用inflate()方法
    ((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
    // or
    View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

    注意,inflate()方法直接返回了加载的布局,所以不需要再调用findViewById()的方法。

    展开全文
  • Android性能优化篇 题记: 不知道别人是怎么学习的,我总是觉得我学习的效率很低,昨天发生了不愉快的事情后,我痛定思痛,反思了一下自己,还是总结不够,也是工作中接触的东西太少的缘故,但是缺乏思考和总结,...

                                                     Android性能优化篇

    题记:

    不知道别人是怎么学习的,我总是觉得我学习的效率很低,昨天发生了不愉快的事情后,我痛定思痛,反思了一下自己,还是总结不够,也是工作中接触的东西太少的缘故,但是缺乏思考和总结,是我自己的问题,怪不了别人,也不怨天尤人。这段时间我把自己叫做废物,什么时候觉醒了,什么时候再改名吧。

    本章主要围绕Android的性能优化来讲解,主要包含布局优化、绘制优化、内存泄露、响应速度、ListView优化、Bitap优化、线程优化等优化方式和一些目前常见到的优化工具的使用和说明,在讲解中会结合我在工作中遇到的相关问题。具体参照任玉刚-Android开发艺术探索。

    一、基础

    1、ANR

    2、OOM

    3、为什么要进行性能优化

    二、布局优化

    1、布局层级与测量次数

    (1)合理选择父容器

    (2)标签

    (3)使用ConstaintLayout

    2、过度绘制

    (1)去掉Windwo的默认背景

    (2)去掉其他不必要的背景

    (3)优化onDraw方法

    (4)标签

    一、基础

    1、ANR

    ANR,全名Application not response,应用无响应。

    产生ANR的原因:通常在UI线程执行耗时任务可能会导致ANR,一般Activity中5s内无法响应按键或触摸事件,那么就会导致ANR,同样的BroadCast中10s内没有完成操作也会出现anr,service为20s。

    如何分析:一般发生ANR后,系统会在data/anr目录下创建一个traces.txt文件,通过这个文件我们可以分析ANR的原因。

    2、OOM

    OOM,全称Out of Memory,内存溢出

    产生内存溢出的原因:常见的比如有单例持有外部实例,Bimap没有及时释放等

    如何分析:后面会讲到详细的工具和分析

    3、为什么要进行性能优化

    (1)Android设备,通常由于体积的限制,它的cpu和内存是有限制的,因此无法和pc的大内存高cpu相比

    (2)程序无限制使用cpu,会导致当前应用cpu过高,可能会导致系统卡顿甚至程序无响应(cpu都被某一块占用了,其他程序无法获得对应的cpu时间片,因此自然会出现卡顿)

    (3)无限制使用内存,会导致程序使用内存过多,从而引发OOM,发生OOM后,系统的MemoryKiller会杀死一些进程,这样会导致该程序或者其他程序被杀死,从而出现闪退的现象。

     

    二、布局优化

    Android中布局优化主要包含以下三个方面:布局层级和测量次数、布局过度绘制、绘制过程

    1、布局层级与测量次数

    布局层级越多,绘制耗时就会相应增加。考虑使用布局层级比较少的方案.

    (1)合理选择父容器

    在布局层数相同时,我们优先选择测量次数较少的父容器

    通常我们选取的优先级为:FrameLayout、不带Layou_wight的LinearLayuut、RelativeLayout。因为带有Layot_weight的LinearLayout和RelativeLayout会测量两次。

    总结来看,首先优先布局层级少的方案,在布局层级相同时,采用测量次数少的。

     

    那么如何分析布局层级呢?

    (1)Android Device Monitor

    Android studio3.0开始,Google不建议使用它, 因此我们需要手动在sdk目录下的tools中找到它,之后运行你的apk并且选择Hierarchy View,就可以查看对应的层级关系,如下:

     

    (2)Component Tree

    在Android studio.3.0之后,我们可以使用Component Tree,它同样提供了查看组件层级的功能,具体如下:

    选择并查看我们的xm布局,点击左下角的design,则可以看到左侧层级关系。

     

    (2)标签

    除了上面提到的方式外,我们还可以通过使用标签来减少层级和复用组件

    (1)include标签

    include标签的作用就是可以直接引用已有的布局,而不需要重新写布局。而通常会将include和merge标签相结合使用,下面会介绍merge标签。

    例如:

    <?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"
    
    android:paddingBottom="@dimen/activity_vertical_margin"
    
    android:paddingLeft="@dimen/activity_horizontal_margin"
    
    android:paddingRight="@dimen/activity_horizontal_margin"
    
    android:paddingTop="@dimen/activity_vertical_margin"
    
    tools:context="com.jared.layoutoptimise.MainActivity">
    
    <include layout="@layout/item_test_linear_layout" />
    
    </RelativeLayout>

    这里include引用的是一个LinearLayout的布局,虽然我们不需要重复写这个布局,但是却增加了层级关系。

    (2)merge标签

    merge标签通常是作为include标签的辅助扩展,就是为了解决引入include后导致布局层级增加问题,使用merge标签后,引入的布局中的View就会作为父布局的子View。

    如下:

    <?xml version="1.0" encoding="utf-8"?>
    
    <RelativeLyout xmlns:android="http://schemas.android.com/apk/res/android"
    
    android:layout_width="match_parent"
    
    android:layout_height="match_parent">
    
    <ImageView
    
    android:id="@+id/iv_image"
    
    android:layout_width="wrap_content"
    
    android:layout_height="wrap_content"
    
    android:layout_margin="10dp"
    
    android:src="@mipmap/ic_launcher" />
    
    <TextView
    
    android:id="@+id/tv_title"
    
    android:layout_width="wrap_content"
    
    android:layout_height="wrap_content"
    
    android:layout_marginLeft="10dp"
    
    android:layout_marginTop="16dp"
    
    android:layout_toRightOf="@+id/iv_image"
    
    android:text="这个是MergeLayout"
    
    android:textSize="16sp" />
    
    <TextView
    
    android:id="@+id/tv_content"
    
    android:layout_width="wrap_content"
    
    android:layout_height="wrap_content"
    
    android:layout_below="@+id/tv_title"
    
    android:layout_marginLeft="10dp"
    
    android:layout_marginTop="10dp"
    
    android:layout_toRightOf="@+id/iv_image"
    
    android:text="这个是MergeLayout,这个是MergeLayout"
    
    android:textSize="12sp" />
    
    </RelativeLyout>

    使用include标签引入后,层级关系如下:

    现在我们把标签改为merge,层级关系如下:

     

    可以明显看到中间少了一层。

     

    (3)ViewStub标签

    ViewStub继承于View,它是一个轻量级且宽高为0的组件,本身不参与布局和绘制。因此使用它可以做到在不需要的时候不加载,在需要的时候再加载,从而提高性能。

    那么如何做到需要的时候显示呢?

    可以通过以下两种方式:

    setVisiable(View.Visiable)或者findViewById().inflate()

     

    (3)使用ConstaintLayout

    ConstaintLayout允许在不使用任何嵌套的情况下创建复杂布局,与RelativeLayout相似,可以依赖兄弟容器和父控件之间的相对关系。常见属性:

    app:layout_constraintLeft_toLeftOf="parent"

    app:layout_constraintTop_toTopOf="parent"

    app:layout_constraintLeft_toRightOf="@+id/iv_image"

    例如:

    <?xml version="1.0" encoding="utf-8"?>
    
    <android.support.constraint.ConstraintLayout
    
    xmlns:android="http://schemas.android.com/apk/res/android"
    
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
    xmlns:tools="http://schemas.android.com/tools"
    
    android:id="@+id/lay_root"
    
    android:layout_width="match_parent"
    
    android:layout_height="match_parent">
    
    <ImageView
    
    android:id="@+id/iv_image"
    
    android:layout_width="wrap_content"
    
    android:layout_height="wrap_content"
    
    android:layout_margin="10dp"
    
    android:src="@mipmap/ic_launcher"
    
    android:layout_marginStart="16dp"
    
    app:layout_constraintLeft_toLeftOf="parent"
    
    android:layout_marginLeft="16dp"
    
    android:layout_marginTop="16dp"
    
    app:layout_constraintTop_toTopOf="parent" />
    
    <TextView
    
    android:id="@+id/tv_title"
    
    android:layout_width="0dp"
    
    android:layout_height="wrap_content"
    
    android:layout_toRightOf="@+id/iv_image"
    
    android:text="这个是ConstraintLayout"
    
    android:textSize="16sp"
    
    app:layout_constraintLeft_toRightOf="@+id/iv_image"
    
    android:layout_marginStart="20dp"
    
    android:layout_marginLeft="20dp"
    
    android:layout_marginTop="20dp"
    
    app:layout_constraintTop_toTopOf="parent" />
    
    <TextView
    
    android:id="@+id/tv_content"
    
    android:layout_width="0dp"
    
    android:layout_height="wrap_content"
    
    android:layout_below="@+id/tv_title"
    
    android:layout_toRightOf="@+id/iv_image"
    
    android:text="这个是ConstraintLayout,这个是RelativeLayout"
    
    android:textSize="12sp"
    
    app:layout_constraintTop_toBottomOf="@+id/tv_title"
    
    android:layout_marginTop="16dp"
    
    app:layout_constraintLeft_toLeftOf="@+id/tv_title" />
    
    </android.support.constraint.ConstraintLayout>

     

     

    2、过度绘制

    过度绘制其实指的是屏幕内某个像素在同一帧的时间内被绘制了多次。

    而在多层次重叠的UI结构里,如果不可见的UI也在绘制的话,就会导致某些像素区域被绘制多次,从而浪费大量的cpu和gpu资源。

    关于绘制的原理可以参考https://blog.csdn.net/xsf50717/article/details/78444047,这里不再赘述

    目前提供两种方式来检测过度绘制:

    • 手机自带检测工具

    手机的开发者模式中会有一项为调试GPU过度绘制>显示GPU过度绘制,设置后,打开任何一个app,就可以看到界面上出现蓝、绿、粉、红四种颜色中的一种或者多种。

    蓝色:1次过度绘制

    绿色:2次过度绘制

    粉丝:3次过度绘制

    红色:4次过度绘制

    例如:

    • Android device monotor

    打开Hierarchy Viewer(/'haɪərɑːkɪ/),运行模拟器,打开对应的Activity界面,就可以看到如下

    其中每一个View中,下面三个点依次表示测量、布局、绘制的时间,红点和黄点表示速度慢,而蓝绿则相对好一些。

     

    在了解了如何分析过度绘制后,我们如何去处理过度绘制?

    (1)去掉Windwo的默认背景

    一般来说我们使用的Activity都会有一些默认的主题,通常这个主题会有一个对应的背景,被DecoreView持有,我们自定义布局时如果又添加了一个背景图或者设置背景色,就会产生一个overdraw,因此可以考虑去掉默认的背景。

    我们可以在onCreate()的setContentView之前调用

    getWindow().setBackGroundDrawable(null)

    或者是在theme中加入windowbackground=“null”

     

    (2)去掉其他不必要的背景

    有时候我们为layout设置一个背景,而它的子View也有背景,此时就会造成重叠。针对这种情况我们首先考虑添加背景是否需要,如果需要我们可以考虑通过selector普通状态下设置背景为透明,点击状态下设置背景来减少重绘。

    (3)优化onDraw方法

    • 避免在onDraw()方法中分配对象,因为onDraw方法可能被多次调用,这样的话可能会产生很多不必要的对象
    • 使用ClipRect制定个绘制区域

    在使用自定义View时,我们可以利用此方法来指定一块可见区域,用于绘制,其他区域则会被忽略,即只绘制clipRect指定的区域。

    例如在图片层叠时,我们将重叠部分不再绘制,只绘制不重叠部分。直接绘制如下:

    可以看到重叠部分出现了过度绘制,而实际上重叠部分我们不需要绘制底层部分,因为我们只能看到上面的图层,因此我们可以利用clipRect()来指定区域。如下:

    @Override
    
    protected void onDraw(Canvas canvas) {
    
    super.onDraw(canvas);
    
    canvas.save();
    
    int bits = mBitmaps.length;
    
    for (int i = 0; i < bits; i++) {
    
    Bitmap bitmap = mBitmaps[i];
    
    int bitW = bitmap.getWidth();
    
    int bitH = bitmap.getHeight();
    
    if (i != 0) {
    
    canvas.translate(bitW / 2, 0);//每绘制完图片时,画布向前平移width/2的长度
    
    }
    
    canvas.save();
    
    if (i != bits - 1) {
    
    canvas.clipRect(0, 0, bitW / 2, bitH);//选择绘制区域,每次只绘制图片的一半
    
    }
    
    canvas.drawBitmap(bitmap, 0, 0, null);
    
    canvas.restore();
    
    }
    
    canvas.restore();
    
    }
    
    

    (4)标签

    也就是前面提到的include、merge、viewStub标签

    展开全文
  • 布局的性能、布局层级、布局复用性、测量和绘制时间方向讨论。* 选择耗费性能少的布局耗费性能低的布局有FrameLayout、LinearLayout高的有RelativeLayout;布局过程消耗更多的CPU资源和时间嵌套消耗的性能 &gt...
    从布局的性能、布局层级、布局复用性、测量和绘制时间方向讨论。

    * 选择耗费性能少的布局
    耗费性能低的布局有FrameLayout、LinearLayout
    高的有RelativeLayout;布局过程消耗更多的CPU资源和时间
    嵌套消耗的性能 > 单个布局本身的性能

    * 减少布局的嵌套
    布局层级越少绘制的工作量越少,绘制速度越快,可以选择merge标签,减少布局层级。
    <merge>作为布局的根标签时,其他布局通过<include>标签引用的时候,只有merge标签内的内容被引用,这样减少一层布局。

    * 提供布局的复用性
    提取不同布局之间共同的部分,提高布局的测量和绘制时间,使用include标签。

    * 减少初次测量&绘制时间
    使用<ViewStub>标签,轻量级View,不占用显示资源,默认不显示,只有在需要时才显示,比如显示进度、信息出错的提示布局。
    ViewStub标签引入外部布局,类似于include标签。当调用ViewStub.inflate()时,ViewStub指向的布局文件才会被inflate、实例化,显示出来。
    注意:
    ~ ViewStub的layout布局不能使用merge标签,否则报错
    ~ ViewStub的inflate只能执行一次,显示之后,就不能在使用ViewStub控制。
    ~ 与View.setVisible(View.Gone)的区别:View 的可见性设置为 gone 后,在inflate 时,该View 及其子View依然会被解析;而使用          ViewStub就能避免解析其中指定的布局文件,从而节省布局文件的解析时间 & 内存的占用

    * 尽可能少用布局属性wrap_content
    布局属性 wrap_content 会增加布局测量时计算成本


    布局调优工具
    常用的有hierarchy viewer、Lint、Systrace

    Hierarchy Viewer
    Android studio提供的UI性能检测工具。

    Lint
    代码扫描分析工具

    Systrace
    提供性能数据采样和分析工具
    检测Android系统各个组件随着时间的运行状态提供解决方案。



    RelativeLayout和LinearLayout性能比较

    1、View的简单绘制流程
    View的绘制从ViewRoot的performTraversals()方法开始依次调用perfromMeasure、performLayout和performDraw这三个方法。这三个方法分别完成顶级View的measure、layout和draw三大流程,其中perfromMeasure会调用measure,measure又会调用onMeasure,在onMeasure方法中则会对所有子元素进行measure,这个时候measure流程就从父容器传递到子元素中了,这样就完成了一次measure过程,接着子元素会重复父容器的measure,如此反复就完成了整个View树的遍历。同理,performLayout和performDraw也分别完成perfromMeasure类似的流程。通过这三大流程,分别遍历整棵View树,就实现了Measure,Layout,Draw这一过程,View就绘制出来了。

    从RelativeLayout和LinearLayout两大布局的绘制来看,耗时的差别主要体现在Measure过程,Layout和Draw过程相差不多。
    分别看下他们的onMeasure()方法

    RelativeLayout的onMeasure():
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mDirtyHierarchy) {
            mDirtyHierarchy = false;
            sortChildren();
        }
    
        int myWidth = -1;
        int myHeight = -1;
    
        int width = 0;
        int height = 0;
    
        //分别拿到宽高的测量模式和测量大小
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
        .......
    
        //View数组存放水平方向排序的子View
        View[] views = mSortedHorizontalChildren;
        int count = views.length;
    
        //第一次遍历子View
        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);
    
                //应用水平方向设置的规则rules
                applyHorizontalSizeRules(params, myWidth, rules);
                //遍历measure子View
                measureChildHorizontal(child, params, myWidth, myHeight);
    
                if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
                    offsetHorizontalAxis = true;
                }
            }
        }
    
        //数组存放垂直方向排序的子View
        views = mSortedVerticalChildren;
        count = views.length;
        final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
    
        //第二次遍历子view
        for (int i = 0; i < count; i++) {
            final View child = views[i];
            if (child.getVisibility() != GONE) {
                final LayoutParams params = (LayoutParams) child.getLayoutParams();
    
                //应用垂直方向设置的规则rules
                applyVerticalSizeRules(params, myHeight, child.getBaseline());
                //遍历measure子View
                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);
                }
            }
        }
    
        
        .........
    
        //onMeasure之后必须调用的方法
        setMeasuredDimension(width, height);
    }

    根据上述关键代码,RelativeLayout分别对所有子View进行两次measure,横向纵向分别进行一次,这是为什么呢?首先RelativeLayout中子View的排列方式是基于彼此的依赖关系,而这个依赖关系可能和布局中View的顺序并不相同,在确定每个子View的位置的时候,需要先给所有的子View排序一下。又因为RelativeLayout允许A,B 2个子View,横向上B依赖A,纵向上A依赖B。所以需要横向纵向分别进行一次排序测量。 mSortedHorizontalChildren和mSortedVerticalChildren是分别对水平方向的子控件和垂直方向的子控件进行排序后的View数组。


    LinearLayout的onMeasure():
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

    与RelativeLayout相比LinearLayout的measure就简单的多,只需判断线性布局是水平布局还是垂直布局即可,然后才进行测量:
    LinearLayout的Weight>0,有可能是进行两次测量,并不是一定是两次。

    height ==0 ;weight >0 ; heightMode != MeasureSpec.EXACTLY; 三个条件满足,才能跳过测量。
    final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
    if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
        // Optimization: don't bother measuring children who are only
        // laid out using excess space. These views will get measured
        // later if we have space to distribute.
        final int totalLength = mTotalLength;
        mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
        skippedMeasure = true;
    }


    RelativeLayout另一个性能问题
    下面是View的measure方法
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }
    
        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
    
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;//判断是否强制刷新
    
        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec  //传入值MeasureSpec没有变化
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
    
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
    
            resolveRtlPropertiesIfNeeded();
    
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
    
            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }
    
            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
    
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
    
        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }
    View的measure方法里对绘制过程做了一个优化,如果我们或者我们的子View没有要求强制刷新,而父View给子View的传入值也没有变化(也就是说子View的位置没变化),就不会做无谓的measure。但是上面已经说了RelativeLayout要做两次measure,而在做横向的测量时,纵向的测量结果尚未完成,只好暂时使用myHeight传入子View系统,假如子View的Height不等于(设置了margin)myHeight的高度,那么measure中上面代码所做得优化将不起作用,这一过程将进一步影响RelativeLayout的绘制性能。而LinearLayout则无这方面的担忧。解决这个问题也很好办,如果可以,尽量使用padding代替margin。

     


    结论:
    1、RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View 2次onMeasure
    2、RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
    3、在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。
    4、提高绘制性能的使用方式
    根据上面源码的分析,RelativeLayout将对所有的子View进行两次measure,而LinearLayout在使用weight属性进行布局时也会对子View进行两次measure,如果他们位于整个View树的顶端时并可能进行多层的嵌套时,位于底层的View将会进行大量的measure操作,大大降低程序性能。因此,应尽量将RelativeLayout和LinearLayout置于View树的底层,并减少嵌套。


    较低的SDK版本新建Android项目时,默认的布局文件是采用线性布局LinearLayout,但现在自动生成的布局文件都是RelativeLayout,为什么呢?
    Google的意思是“性能至上”, RelativeLayout 在性能上更好,因为在诸如 ListView 等控件中,使用 LinearLayout 容易产生多层嵌套的布局结构,这在性能上是不好的。而 RelativeLayout 因其原理上的灵活性,通常层级结构都比较扁平,很多使用LinearLayout 的情况都可以用一个 RelativeLayout 来替代,以降低布局的嵌套层级,优化性能。所以从这一点来看,Google比较推荐开发者使用RelativeLayout,因此就将其作为Blank Activity的默认布局了。







    展开全文
  • 布局优化方案

    2017-05-22 07:38:13
    Overdraw就是过度绘制,是指在一帧的时间内(16.67ms)像素被绘制了多次,理论上一个像素每次只绘制一次是最优的,但是由于重叠的布局导致一些像素会被多次绘制,而每次绘制都会对应到CPU的一组绘图命令和GPU的一些...

    什么是Overdraw?

    Overdraw就是过度绘制,是指在一帧的时间内(16.67ms)像素被绘制了多次,理论上一个像素每次只绘制一次是最优的,但是由于重叠的布局导致一些像素会被多次绘制,而每次绘制都会对应到CPU的一组绘图命令和GPU的一些操作,当这个操作耗时超过16.67ms时,就会出现掉帧现象,也就是我们所说的卡顿,所以对重叠不可见元素的重复绘制会产生额外的开销,需要尽量减少Overdraw的发生。
    Android提供了测量Overdraw的选项,在开发者选项-调试GPU过度绘制(Show GPU Overdraw),打开选项就可以看到当前页面Overdraw的状态,就可以观察屏幕的绘制状态。该工具会使用三种不同的颜色绘制屏幕,来指示overdraw发生在哪里以及程度如何,其中:
    没有颜色: 意味着没有overdraw。像素只画了一次。
    蓝色: 意味着overdraw 1倍。像素绘制了两次。大片的蓝色还是可以接受的(若整个窗口是蓝色的,可以摆脱一层)。
    绿色: 意味着overdraw 2倍。像素绘制了三次。中等大小的绿色区域是可以接受的但你应该尝试优化、减少它们。
    浅红: 意味着overdraw 3倍。像素绘制了四次,小范围可以接受。
    暗红: 意味着overdraw 4倍。像素绘制了五次或者更多。这是错误的,要修复它们。

    1240
    552dd397d5e53.jpg


    那么我们怎么来消灭overdraw呢?总的原则就是:尽量避免重叠不可见元素的绘制,基于这个原则,我们大概可以想出以下几招:

    第一招:合理选择控件容器

    既然overdraw是因为重复绘制了同一片区域的像素点,那我们首先想到的是解决布局问题。Android提供的Layout控件主要包括LinearLayout、TableLayout、FrameLayout、RelativeLayout。俗话说条条大路通罗马,同一个界面我们可以使用不同的容器控件来表达,但是各个容器控件描述界面的复杂度是不一样的。一般来说LinearLayout最易,RelativeLayout较复杂。但是尺有所短,寸有所长,LinearLayout只能用来描述一个方向上连续排列的控件,而RelativeLayout几乎可以用于描述任意复杂度的界面。但是我又要说但是了,表达能力越强的容器控件,性能往往略低一些,因为系统需要将更多的时间花在计算子控件的位置上。综上所述:LinearLayout易用,效率高,表达能力有限。RelativeLayout复杂,表达能力强,效率稍逊。
    那么对于同一界面而言,作为开发者考虑是使用尽量少的、表达能力强的RelativeLayout作为容器,还是选择多个、表达能力稍弱的LinearLayout来展示。从减少overdraw的角度来看,LinearLayout会增加控件数的层级,自然是RelativeLayout更优,但是当某一界面在使用LinearLayout并不会比RelativeLayout带来更多的控件数和控件层级时,LinearLayout则是首选。所以在表达界面的时候,作为一个有前瞻性的开发者要根据实际情况来选择合适容器控件,在保证性能的同时,尽量避免overdraw。

    第二招:去掉window的默认背景

    当我们使用了Android自带的一些主题时,window会被默认添加一个纯色的背景,这个背景是被DecorView持有的。当我们的自定义布局时又添加了一张背景图或者设置背景色,那么DecorView的background此时对我们来说是无用的,但是它会产生一次Overdraw,带来绘制性能损耗。
    去掉window的背景可以在onCreate()中setContentView()之后调用

    getWindow().setBackgroundDrawable(null);

    或者在theme中添加

    android:windowbackground="null"

    第三招:去掉其他不必要的背景

    有时候为了方便会先给Layout设置一个整体的背景,再给子View设置背景,这里也会造成重叠,如果子View宽度mach_parent,可以看到完全覆盖了Layout的一部分,这里就可以通过分别设置背景来减少重绘。再比如如果采用的是selector的背景,将normal状态的color设置为“@android:color/transparent”,也同样可以解决问题。这里只简单举两个例子,我们在开发过程中的一些习惯性思维定式会带来不经意的Overdraw,所以开发过程中我们为某个View或者ViewGroup设置背景的时候,先思考下是否真的有必要,或者思考下这个背景能不能分段设置在子View上,而不是图方便直接设置在根View上。

    第四招:ClipRect & QuickReject

    为了解决Overdraw的问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少消耗。但是不幸的是,对于那些过于复杂的自定义的View(通常重写了onDraw方法),Android系统无法检测在onDraw里面具体会执行什么操作,系统无法监控并自动优化,也就无法避免Overdraw了。但是我们可以通过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。这个API可以很好的帮助那些有多组重叠组件的自定义View来控制显示的区域。同时clipRect方法还可以帮助节约CPU与GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,仍然会得到绘制。除了clipRect方法之外,我们还可以使用canvas.quickreject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。

    第五招:ViewStub

    ViewStub是个什么东西?一句话总结:高效占位符。
    我们经常会遇到这样的情况,运行时动态根据条件来决定显示哪个View或布局。常用的做法是把View都写在上面,先把它们的可见性都设为View.GONE,然后在代码中动态的更改它的可见性。这样的做法的优点是逻辑简单而且控制起来比较灵活。但是它的缺点就是,耗费资源。虽然把View的初始可见View.GONE但是在Inflate布局的时候View仍然会被Inflate,也就是说仍然会创建对象,会被实例化,会被设置属性。也就是说,会耗费内存等资源。
    推荐的做法是使用android.view.ViewStub,ViewStub是一个轻量级的View,它一个看不见的,不占布局位置,占用资源非常小的控件。可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或是调用了ViewStub.inflate()的时候,ViewStub所向的布局就会被Inflate和实例化,然后ViewStub的布局属性都会传给它所指向的布局。这样,就可以使用ViewStub来方便的在运行时,要还是不要显示某个布局。

    <ViewStub
        android:id="@+id/stub_view"
        android:inflatedId="@+id/panel_stub"
        android:layout="@layout/progress_overlay"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom" />

    当你想加载布局时,可以使用下面其中一种方法:

    ((ViewStub) findViewById(R.id.stub_view)).setVisibility(View.VISIBLE);
    View importPanel = ((ViewStub) findViewById(R.id.stub_view)).inflate();

    第六招:Merge

    Merge标签有什么用呢?简单粗暴点回答:干掉一个view层级。
    Merge的作用很明显,但是也有一些使用条件的限制。有两种情况下我们可以使用Merge标签来做容器控件。第一种子视图不需要指定任何针对父视图的布局属性,就是说父容器仅仅是个容器,子视图只需要直接添加到父视图上用于显示就行。另外一种是假如需要在LinearLayout里面嵌入一个布局(或者视图),而恰恰这个布局(或者视图)的根节点也是LinearLayout,这样就多了一层没有用的嵌套,无疑这样只会拖慢程序速度。而这个时候如果我们使用merge根标签就可以避免那样的问题。另外Merge只能作为XML布局的根标签使用,当Inflate以<merge />开头的布局文件时,必须指定一个父ViewGroup,并且必须设定attachToRoot为true。
    举个简单的例子吧:

    <RelativeLayout 
    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" > 
    <TextView 
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content" 
    android:text="merge标签使用" />
    </RelativeLayout>

    把上面这个XML加载到页面中,布局层级是RelativeLayout-TextView。但是采用下面的方式,把RelativeLayout提换成merge,RelativeLayout这一层级就被干掉了。

    <merge
    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" >
    <TextView  
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
     android:text="merge标签使用" />
    </merge>

    第七招:善用draw9patch

    给ImageView加一个边框,你肯定遇到过这种需求,通常在ImageView后面设置一张背景图,露出边框便完美解决问题,此时这个ImageView,设置了两层drawable,底下一层仅仅是为了作为图片的边框而已。但是两层drawable的重叠区域去绘制了两次,导致overdraw。
    优化方案: 将背景drawable制作成draw9patch,并且将和前景重叠的部分设置为透明。由于Android的2D渲染器会优化draw9patch中的透明区域,从而优化了这次overdraw。 但是背景图片必须制作成draw9patch才行,因为Android 2D渲染器只对draw9patch有这个优化,否则,一张普通的Png,就算你把中间的部分设置成透明,也不会减少这次overdraw。

    第八招:慎用Alpha

    假如对一个View做Alpha转化,需要先将View绘制出来,然后做Alpha转化,最后将转换后的效果绘制在界面上。通俗点说,做Alpha转化就需要对当前View绘制两遍,可想而知,绘制效率会大打折扣,耗时会翻倍,所以Alpha还是慎用。
    如果一定做Alpha转化的话,可以采用缓存的方式。

    view.setLayerType(LAYER_TYPE_HARDWARE);
    doSmoeThing();
    view.setLayerType(LAYER_TYPE_NONE);

    通过setLayerType方式可以将当前界面缓存在GPU中,这样不需要每次绘制原始界面,但是GPU内存是相当宝贵的,所以用完要马上释放掉。

    第九招:避免“OverDesign”

    overdraw会给APP带来不好的体验,overdraw产生的原因无外乎:复杂的Layout层级,重叠的View,重叠的背景这几种。开发人员无节制的View堆砌,究其根本无非是产品无节制的需求设计。有道是“由俭入奢易,由奢入俭难”,很多APP披着过度设计的华丽外衣,却忘了简单易用才是王道的本质,纷繁复杂的设计并不会给用户带来好的体验,反而会让用户有压迫感,产品本身也有可能因此变得卡顿。当然,一切抛开业务谈优化都是空中楼阁,这就需要产品设计也要有一个权衡,在复杂的业务逻辑与简单易用的界面展现中做一个平衡,而不是一味的OverDesign。

        </div>
    
    展开全文
  • 布局优化

    2018-04-11 13:21:20
    一 什么是布局优化 布局优化就是减少视图嵌套层级,减少视图层级可以有效的减少内存消耗,因为视图是一个树形结构,每次刷新和渲染都会遍历一次。二 ViewStub标签此标签只会加载一次,加载后会把所有子控件交付给...
  • 布局优化   我们可以通过手机开发者选项中的调试GPU过度来查看布局绘制的复杂情况。 避免overdraw,使用RelativeLayout来替换掉多层LineraLayout嵌套 减少View树的层数,Google Api文档中建议View树...
  • 布局优化

    2018-01-09 11:46:35
    系统在渲染UI界面的时候将消耗大量的资源,一个好的UI不仅应该具有良好的视觉效果,更应该具有良好的使用体验,因此布局优化显的很重要。 1.AndroidUI渲染机制  人眼所感觉的流程画面,需要画面的帧数达到40帧每...
  • Android最佳性能实践(四)——布局优化技巧

    万次阅读 多人点赞 2015-03-19 09:21:55
    那么本篇文章我们就来学习一下,如何通过优化布局来提供应用程序的性能。 Android系统中已经提供了非常多好用的控件,这让我们在编写布局的时候可以很轻松。但是有些时候我们可能需要反复利用某个已经写好的布局,...
  • 一些你需要知道的布局优化技巧

    万次阅读 多人点赞 2016-10-19 21:50:45
    转载请注明出处:...今天分享一些layout布局书写中的一些技巧,希望看过之后你也一样可以写出性价比高的布局。我个人的目标是用最少的View写出一样效果的布局
  • Android性能优化系列之布局优化

    万次阅读 2017-01-15 22:20:14
    随着UI越来越多,布局的重复性、复杂度也会随之增长,这样使得UI布局的优化,显得至关重要,UI布局不慎,就会引起过度绘制,从而造成UI卡顿的情况,本篇博客,我就来总结一下UI布局优化的相关技巧。学会使用布局标签...
  • 布局优化的核心思想是优化布局嵌套层级(层级越少,View绘制时越快) 一、Android系统屏幕UI刷新机制 首先需要明白一个概念,如果我们想要屏幕流畅的运行,就必须保证UI全部的测量、布局和绘制的时间在16ms内为什么是...
  • android中的布局优化

    千次阅读 2018-05-15 16:04:29
    android中的布局优化 简介 用最少的view写出一样的效果,优化分为重用,合并,按需载入 android中的dp、px、dip相关概念 px:是屏幕的像素点 dp:一个基于density的抽象单位, px = dip * density /...
  • 概述在Android中,layout布局的加载速度会影响到APP的性能。充满不必要的Views和可读性差的layout文件会让你的APP运行缓慢。下面我将会总结Android开发中,提升layout性能的几个常用的技巧。1.在TextView中使用...
  • 在Android中,布局优化越来越受到重视,下面将介绍布局优化的几种方式,这几种方式一般可能都见过,因为现在用的还比较多,我们主要从两个方面来进行介绍,一方面是用法,另一方面是从源码来分析,为什么它能起到...
  • android布局优化的三大标签

    千次阅读 2016-03-30 10:05:20
    1、布局重用 标签能够重用布局文件,简单的使用如下: android:orientation="vertical" android:layout_width=”match_parent” and
  • Android布局优化

    千次阅读 2014-01-25 10:16:19
    Android布局优化 在Android开发中,我们常用的布局方式主要有LinearLayout、RelativeLayout、FrameLayout等,通过这些布局我们可以实现各种各样的界面。与此同时,如何正确、高效的使用这些布局方式来组织UI控件,...
  • 本篇主要聊一下快当中布局优化,也是性能优化中最简单的一部分,可能有人认为布局的修改对性能优化的提升微乎其微,但积少成多性能总是被无数细微的点拖垮的,更何况当你明知道代码当中有可以优化的地方,难道会放着...
  • 布局优化工具Hierarchy Viewer

    千次阅读 2018-09-14 12:04:34
    版权声明:本文为博主原创或汇集文章,欢迎注明来源转载。http://blog.csdn.net/u012792686 https://blog.csdn.net/u012792686/article/details/72921379 ...
  • Android 布局优化之include与merge

    万次阅读 多人点赞 2016-01-15 11:30:26
    官方提供了三个用来优化布局的标签,分别是include、merge与ViewStub,其中ViewStub是动态加载视图到内存,大家可以查阅:Android UI布局优化之ViewStub 一、include布局重用:在Android的应用程序开发
  • 如果想使用Android SDK中提供的优化工具,你需要在开发系统的命令行中工作,如果你不熟悉使用命令行工具,那么你得多下功夫学习了。 我们强烈建议你将Android工具所在的路径添加到操作系统的环境变量中,这样就...

空空如也

1 2 3 4 5 ... 20
收藏数 180,834
精华内容 72,333
关键字:

布局优化