Android性能优化实战(二)----界面布局优化
来源:互联网 发布:python爬虫书籍pdf 编辑:程序博客网 时间:2024/05/21 14:02
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的绘制情况。可以看到,在我司的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
- Android性能优化实战(二)----界面布局优化
- Android性能优化(二)布局优化
- Android 性能优化之布局优化 (二)
- Android性能优化(二),Layout布局优化
- Android性能优化(二)之布局优化面面观
- Android 性能优化(二)之布局优化
- Android性能优化之布局篇(二)
- android UI性能优化(2)--高性能界面布局
- Android性能优化---布局优化
- Android性能优化---布局优化
- android性能优化--布局优化
- Android性能优化-布局优化
- Android性能优化-布局优化
- Android性能优化-布局优化
- Android界面性能优化
- Android内存优化(二)--布局优化
- Android绘制优化(二)布局优化
- android布局性能优化
- ionic——页面切换(相当于Android中的Fragment)
- Android串口通信:串口读写实例
- logistic Regression 存在的限制 与 解决办法
- 伪知识之了解python中_init_.py的含义及作用持续更新:【内向即失败--王奕君】
- 【BZOJ】4804 欧拉心算 莫比乌斯函数+欧拉函数+数论分块
- Android性能优化实战(二)----界面布局优化
- 三层架构和MVC
- call to unavailable function system not available on ios
- 遇到的问题,数组
- CodeForces 848C Goodbye Souvenir(CDQ分治+平衡树/set+树状数组)
- 余光中老爷爷走好!!!
- JSONObject解析-json串中字典类型解析
- linux下非oracle用户访问数据库
- 大地坐标系转换为地心空间直角坐标系