其显示结果如下:
移除控件中不需要的背景.png
可以看到,2个使用了跟父布局同样背景的TextView会导致了一次过度绘制。
那么,我们平时只需要遵循以下两个原则就可以减少次过度绘制:
1.对于子控件,如果其背景颜色跟父布局一致,那么就不用再给子控件添加背景了。
2.如果子控件背景五颜六色,且能够完全覆盖父布局,那么父布局就可以不用添加背景了。
2.3.2 将layout层级扁平化
往往我们在写界面的时候都会使用基本布局来实现,这可能会出现一些性能问题。比如:使用嵌套的LinearLayout
可能会导致布局的层次结构变得过深。另外,如果在LinearLayout
中使用了layout_weight
的话,那么他的每一个子 view
都需要测量两次。特别是用在 ListView
和 GridView
时,他们会被反复测量。
布局嵌套过多的话会导致过度绘制,从而降低性能,因此我们需要将布局的层次结构尽量扁平化。
2.3.2.1 使用Layout Inspector去查看layout的层次结构
之前的Android SDK工具包含了一个名为Hierarchy Viewer
的工具,可以在应用运行时分析布局。但是在Android Studio 3.1之后,Hierarchy Viewer
就给移除掉了。并且Android的团队表示不再开发Hierarchy Viewer
。所以这里就不介绍Hierarchy Viewer
。
这里使用Android推荐的Layout Inspector
来查看layout的层次结构。
在Android Studio中点击Tools > Android > Layout Inspector
。然后在出现的 Choose Process
对话框中,选择想要检查的应用进程即可。
Layout Inspector
会自动捕获快照,然后会显示以下内容:
- View Tree:视图在布局中的层次结构。
- Screenshot:每个视图可视边界的设备屏幕截图。
- Properties Table:选定视图的布局属性。
layout-inspector.png
通过左侧View Tree
即可看到布局中的层次结构。
偷偷提一句,
Layout Inspector
也可以用来分析别人APP的布局。
2.3.2.2 使用嵌套少的布局
假如要实现以下布局:
layout-listitem.png
我们可以使用LinearLayout
和RelativeLayout
来完成。但是LinearLayout
相比于RelativeLayout
,就多了一层,所以RelativeLayout
明显是一个更优的选择。如下图所示:
过度嵌套LinearLayout.png
所以,合理选择不同的布局能够减少嵌套。
2.3.2.3 使用merge标签减少嵌套
通过<include>
标签能够复用布局。
比如,我们要复用如下的一个布局,一个垂直的线性布局包含一个ImageView
和TextView
,其布局文件layout_include.xml
如下:
<ImageView
…
/>
<TextView
…
/>
然后我们就可以通过<include>
来复用这个布局了,其布局文件activity_include.xml
如下:
但是上面这个例子会有个问题:其父布局是垂直的线性布局,include
进来的也是垂直的线性布局,这就会造成了布局嵌套,而且这种嵌套是没必要的,那么就可以使用<merge>
标签来减少这种嵌套。将layout_include.xml
改成以下即可:
<ImageView
…
/>
<TextView
…
/>
我们可以用Layout Inspector
来看下使用<merge>
标签优化前后的布局层次结构:
使用merge标签减少嵌套.png
2.3.2.4 使用lint来优化布局的层次结构
lint
是一个静态代码分析工具,可以用来协助优化布局的性能。要使用lint
,点击Analyze
> Inspect Code
即可,如下图所示:
lint-inspect-code.png
布局性能方面的信息位于Android
> Lint
> Performance
下,我们可以点开它来看下一些优化建议。
lint-display.png
下面是lint的一些优化技巧:
-
使用复合图片
如果一个线性布局中包含一个ImageView
和一个TextView
,可以使用复合图片来替换掉 -
合并根节点
如果一个FrameLayout
是整个布局的根节点,并且也没有提供背景、留白等等,那么可以使用<merge>
标签来替换掉,因为DecorView
本身就是一个FrameLayout
。 -
移除布局中无用的叶子
布局是一个树形的结构,如果一个布局没有子View
或者背景,那么可以把它移除掉(这布局本身就不可见了)。 -
移除无用的父布局
如果一个布局没有兄弟,也不是ScrollView
或者根View
,并且也没有背景,那么可以把这个父布局移除掉,然后把它的子view
移到它的父布局下。 -
避免过深的层次结构
过多的布局嵌套不利于性能,可以使用更扁平化的布局,如RelativeLayout
、GridLayout
、ConstraintLayout
等布局来提高性能。布局默认的最大深度为10。
lint
的功能其实很强大,可以用来检测优化各个方面,平时我们遇到lint的一些警告,能修复优化的话就尽量去完善掉。
2.3.3 减少透明度的使用
对于不透明的view
,只需要渲染一次即可把它显示出来。但是如果这个view
设置了alpha
值,则至少需要渲染两次。这是因为使用了alpha
的view
需要先知道混合view
的下一层元素是什么,然后再结合上层的view
进行Blend混色处理。透明动画、淡入淡出和阴影等效果都涉及到某种透明度,这就会造成了过度绘制。可以通过减少渲染这些透明对象来改善过度绘制。比如:在TextView
上设置带透明度alpha
值的黑色文本可以实现灰色的效果。但是,直接通过设置灰色的话能够获得更好的性能。
2.3.4 减少自定义View的过度绘制,使用clipRect()
下面我们自定义一个View用来显示多张重叠的表情包,效果图如下:
自定义view_1.png
其onDraw()
方法也很简单,就是遍历所有表情包,然后绘制出来:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < imgs.length; i++) {
canvas.drawBitmap(imgs[i], i * 100, 0, mPaint);
}
}
显示过度绘制区域:
自定义view_2.png
五颜六色的,过度绘制比较严重,那么如何解决?
我们先来分析一下为什么会出现过度绘制:以第一张图为例,上面的代码会把整张图都绘制出来了,第二张在第一张上面继续绘制,这就造成了过度绘制。
那么,解决办法也很简单,对于前面的n-1张图,我们只需要绘制一部分即可,对于最后一张才绘制完整的。
Canvas
中的clipRect()
方法能够设置一个裁剪矩形,只在这个矩形区域内的内容才能够绘制出来。
优化后的代码如下:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < imgs.length; i++) {
canvas.save();
if (i < imgs.length - 1) {
//前面的n-1张图,只裁剪一部分
canvas.clipRect(i * 100, 0, (i + 1) * 100, imgs[i].getHeight());
} else if (i == imgs.length - 1) {
//最后一张,完整的
canvas.clipRect(i * 100, 0, i * 100 + imgs[i].getWidth(), imgs[i].getHeight());
}
canvas.drawBitmap(imgs[i], i * 100, 0, mPaint);
canvas.restore();
}
优化后的效果图如下:
自定义view_3.png
所有区域都是蓝色的,即只有1次过度绘制。
Canvas
除了clipRect()
方法外,还有clipPath()
等方法,优化时选择合理的方法去裁剪即可。
3.一些布局优化技巧
除了避免过度绘制之外,还有一些其他的优化技巧能够帮我们提升性能。这里简单介绍一下一些比较常用的技巧。
3.1 使用性能更优的布局
- 在无嵌套布局的情况下,
FrameLayout
和LinearLayout
的性能比RelativeLayout
更好。因为RelativeLayout
会测量每个子节点两次。 ConstraintLayout
的性能比RelativeLayout
更好,推荐使用ConstraintLayout
。后面会介绍ConstraintLayout
的使用。
3.2 使用include标签提高布局的复用性
使用<include>
标签提取布局的公用部分,能够提高布局的复用性。具体例子这里就不写了,可以回头看看<merge>
标签那一小节的例子。
3.3 使用ViewStub标签延迟加载
在项目中,有些复杂的布局很少使用到,比如进度指示器等等。那么我们可以通过<ViewStub>
标签来实现在需要时才加载布局。使用<ViewStub>
能够减少内存的使用并且加快渲染速度。
ViewStub
是一个轻量级的视图,它没有尺寸,也不会绘制任何内容和参与布局。下面是一个ViewStub
的例子:
最后
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
上面分享的百度、腾讯、网易、字节跳动、阿里等公司2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
【Android高级架构视频学习资源】
**Android部分精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!