标签(空格分隔): android 性能优化 xml
前期知识储备
1. LayoutInflate
- 主要是用于加载布局的.其实setContentView()方法的内部也是使用LayoutInflater来加载布局的.只不过这部分源码是internal的,不太容易查看到。
- LayoutInflater技术广泛应用于需要动态添加View的时候,比如在ScrollView和ListView中,经常都可以看到LayoutInflater的身影。
- LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的.
- 在setContentView()方法中,Android会自动在布局文件的最外层再嵌套一个FrameLayout.
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs){ ... }
平时我们经常使用layout_width和layout_height来设置View的大小,就好像这两个属性确实是用于设置View的大小的。而实际上则不然,它们其实是用于设置View在布局中的大小的.也就是说,首先View必须存在于一个布局中,之后如果将layout_width设置成match_parent表示让View的宽度填充满布局,如果设置成wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height。
2.View绘制流程
每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw()。
2.1 onMeasure()
- 其内部调用View的measure()方法。
- measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。
- measure()这个方法是final的,因此我们无法在子类中去重写这个方法,说明Android是不允许我们改变View的measure框架的。
- MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。
说明父视图会在一定程度上决定子视图的大小。 - 根视图总是会充满全屏的。
- 一个界面的展示可能会涉及到很多次的measure,因为一个布局中一般都会包含多个子视图,每个视图都需要经历一次measure过程。ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小。
- onMeasure()方法是可以重写的,也就是说,如果你不想使用系统默认的测量方式,可以按照自己的意愿进行定制,比如:
public class MyView extends View {
......
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(200, 200);
}
}
这样的话就把View默认的测量流程覆盖掉了,不管在布局文件中定义MyView这个视图的大小是多少,最终在界面上显示的大小都将会是200*200。
需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。
由此可见,视图大小的控制是由父视图、布局文件、以及视图本身共同完成的,父视图会提供给子视图参考的大小,而开发人员可以在XML文件中指定视图的大小,然后视图本身会对最终的大小进行拍板。
2.2 onLayout()
- 这个方法是用于给视图进行布局的,也就是确定视图的位置.
- getWidth()方法和getMeasureWidth()方法到底有什么区别呢?首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。
2.3 onDraw()
- 对视图进行绘制。
- View是不会帮我们绘制内容部分的,因此需要每个视图根据想要展示的内容来自行绘制.
- 重写onDraw(),绘制的方式主要是借助Canvas这个类,它会作为参数传入到onDraw()方法中,供给每个视图使用。
3.view的机制
3.1 view的状态
- enabled
- focused
- selected
- window_focused
- pressed
3.2 视图重绘
- invalidate()
- 调用视图的invalidate()方法后确实会走到performTraversals()方法中,然后重新执行绘制流程。
- invalidate()方法虽然最终会调用到performTraversals()方法中,但这时measure和layout流程是不会重新执行的,因为视图没有强制重新测量的标志位,而且大小也没有发生过变化,所以这时只有draw流程可以得到执行。而如果你希望视图的绘制流程可以完完整整地重新走一遍,就不能使用invalidate()方法,而应该调用requestLayout()了。这个方法中的流程比invalidate()方法要简单一些,但中心思想是差不多的,这里也就不再详细进行分析了。
4. 自定义view
4.1 完全自定义View控件
4.2 组合控件
4.3 继承控件
布局优化方法
1.布局标签灵活使用(详细使用方法和场景参考底部blog)
- include
- merge
- viewstub
2.布局调优工具
- hierarchy viewer
- lint
优化建议
- 去除不必要的嵌套和View节点
- 减少不必要的infalte
- 使用RenderScript
- 使用OpenGL绘图
- 尽量为所有分辨率创建资源-减少不必要的硬件缩放,这会降低UI的绘制速度,可借助Android asset studio。
- 尽量多使用RelativeLayout和LinearLayout,在布局层次一样的情况下, 建议使用LinearLayout代替RelativeLayout, 因为LinearLayout性能要稍高一点,但往往RelativeLayout可以简单实现LinearLayout嵌套才能实现的布局。
需要注意的地方
- 所有xml界面在通过setContentView()时,会把FrameLayout作为根节点插入。(思考:将setContentView()注释后,运行App,可正常显示,一块白色区域而已,界面渲染前状态,这块白色区域是什么?),因此,当xml中的根节点为FrameLayout时,并且没有其它特殊属性,可去掉,再用《merge》 来包裹。
- 一些blog上说多使用RelativeLayout,并不是因为它性能高,而是可避免使用复杂的层级关系。
- 使用include最常见的问题就是findViewById查找不到目标控件,如下,先设置include标签的id,编码中先找到include的id,在基于其找子id。
View titleView = findViewById(R.id.my_title_ly) ;
TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ;
titleTextView.setText("new Title");
参考文章:
- http://blog.csdn.net/guolin_blog/article/details/17357967 郭霖的View系列文章(1~4)
- http://www.trinea.cn/android/layout-performance/ Trinea性能优化之布局详细
- http://www.stormzhang.com/android/2014/04/10/android-optimize-layout/ 布局优化
- http://www.androidchina.net/2485.html 布局优化
- http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0125/2356.html 推荐阅读较详细-布局优化
- http://www.infoq.com/cn/articles/android-optimise-layout hierarchyviewer 的使用介绍
- http://www.codekk.com/open-source-project-analysis/detail/Android/lightSky/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7%82%B9%E4%B9%8B%20View%20%E7%BB%98%E5%88%B6%E6%B5%81%E7%A8%8B View绘制流程的源码解析