布局优化的核心问题就是要解决因布局渲染性能不佳而导致应用卡顿的问题。
绘制流程
绘制流程:安卓应用的绘制流程是在UI线程上执行的,主要包括以下几个步骤:
- 测量(Measure):从视图树的根节点开始,递归地对每个视图进行测量,确定视图的大小和位置。
- 布局(Layout):根据测量得到的视图大小和位置,递归地对每个视图进行布局,确定视图的具体位置。
- 绘制(Draw):根据布局得到的视图位置,依次从视图树的根节点开始,递归地对每个视图进行绘制。绘制包括背景绘制、绘制自身内容和绘制子视图等操作。
- 重绘(Invalidate):如果视图的状态发生改变或需要更新界面,可以调用
invalidate()
方法,通知系统重新执行绘制流程。
绘制原理
- CPU(Central Processing Unit):
- 视图测量(Measure):CPU负责对视图进行测量,计算出每个视图的大小和位置。这个过程是递归进行的,从根视图开始,通过调用视图的
measure()
方法来确定视图的测量规格。 - 视图布局(Layout):CPU根据测量的结果,对视图进行布局,即确定视图的具体位置。同样是递归过程,从根视图开始,通过调用视图的
layout()
方法来确定视图的位置。
- 视图测量(Measure):CPU负责对视图进行测量,计算出每个视图的大小和位置。这个过程是递归进行的,从根视图开始,通过调用视图的
- GPU(Graphics Processing Unit):
- 绘制(Draw):GPU负责对视图进行绘制,将视图的内容渲染到屏幕上。绘制的过程是在GPU上进行的,而不是在CPU上。绘制操作包括背景绘制、绘制自身内容和绘制子视图等。绘制过程中,GPU会根据绘制指令和纹理数据进行图形计算和渲染操作。
- 刷新机制(Refresh Mechanism):
- 触发刷新:当视图发生变化时,可以通过调用
invalidate()
方法来触发刷新操作。这会导致视图树的绘制无效,需要重新进行视图测量、布局和绘制。 - 刷新流程:刷新操作一般在UI线程上执行,即主线程。在下一次UI线程的绘制周期内,会依次执行视图的测量、布局和绘制操作,最终将变化的内容渲染到屏幕上。
- 触发刷新:当视图发生变化时,可以通过调用
耗时原因
- 视图层级复杂度:视图层级的复杂度是指界面中视图的数量和嵌套关系的复杂程度。当视图层级较深或者视图数量较多时,绘制过程需要进行更多的测量、布局和绘制操作,从而导致耗时增加。
- 视图绘制内容复杂度:视图绘制内容的复杂度是指视图的绘制操作涉及的图形计算和渲染的复杂程度。如果视图的绘制内容包含复杂的图形、动画、滤镜等效果,或者需要进行大量的文本渲染,会增加绘制过程的计算和渲染时间。
- 频繁的刷新操作:如果界面频繁地进行刷新操作,即触发了多次的
invalidate()
方法,会导致绘制过程频繁地执行,增加了CPU和GPU的工作负载,从而导致绘制耗时增加。 - 异步绘制和布局计算:有些情况下,绘制和布局计算可以在后台线程进行,以减轻主线程的工作负载。但是,如果异步绘制和布局计算的时间较长或者频繁地执行,可能会导致主线程等待绘制结果,从而导致绘制耗时增加。
- 不合理的绘制优化:在绘制过程中,可以通过一些优化手段来减少绘制的耗时,如使用绘制缓存、避免不必要的绘制操作、利用硬件加速等。如果在绘制过程中没有合理地进行优化,可能会导致绘制耗时增加。
优化方式
使用标签
merge
可以有效的解决布局的层级关系,可以减少一个层级,在布局中可替代为根视图,可减少内存使用。
因为Activity内容视图的父View即Decor View
继承于FrameLayout
,所以对于我们来说无意中已经多了一层毫无意义的布局。
-
创建一个新的XML布局文件,并在根布局中使用
<merge>
标签<!-- merge_layout.xml --> <merge xmlns:android="http://schemas.android.com/apk/res/android"><!-- 子视图元素,子视图将会被用于添加到父布局 --> </merge>
-
使用
<include>
标签引用merge_layout.xml
文件<!-- main_layout.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><include layout="@layout/merge_layout" /><!-- 其他视图元素 --> </LinearLayout>
需要注意的是,使用
<merge>
标签时,必须确保根布局是LinearLayout
、RelativeLayout
或FrameLayout
等可以容纳子视图的布局容器,而不能是ConstraintLayout
等不支持<merge>
标签的布局容器。此外,<merge>
标签不能设置任何布局属性,如layout_width
、layout_height
等,因为<merge>
标签本身不会显示在界面上,它只是用于优化布局结构。
使用ViewStub
ViewStub
标签与include
一样可以用来引入一个外部布局,但是Viewstub
引入的布局默认不会解析与显示,宽高为0,View
也为null
,这样就会在解析layout
时节省cpu
和内存。简单的理解就是
ViewStub
是include
加上GONE
.ViewStub
常用来引入那些默认不会显示,只在特殊情况下显示的布局,如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局等.
<ViewStubandroid:id="@+id/network_unreachble"android:layout_width="match_parent"android:layout_height="match_parent"android:layout="@layout/viewStub" />//
在代码中通过(ViewStub)findViewById(id)
找到ViewStub
,使用inflate()
展开ViewStub
,然后得到子View
,如下:
ViewStub stub = (ViewStub)findViewById(R.id.viewStub);// 解析并且显示该部分,返回值就是解析后的该`View`viewStubView = stub.inflate();//展开为实际视图Button xxx = (Button)viewStubView.findViewById(R.id.xxx);//获取子View
使用AsyncLayoutInflater异步加载对应的布局
默认情况下
setContentView()
函数在UI线程执行,其中有一系列的耗时动作:Xml的解析、View的反射创建等过程同样是在UI线程执行的,AsyncLayoutInflater 就是来帮我们把这些过程以异步的方式执行,保持UI线程的高响应。
- 工作线程加载布局
- 回调主线程
- 节省主线程时间,将UI线程迁移到了子线程,牺牲了易用性。
AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
inflater.inflate(R.layout.your_layout, parent, new AsyncLayoutInflater.OnInflateFinishedListener() {@Overridepublic void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {// 在这里处理加载完成后的视图// 可以将视图添加到指定的父容器中if (parent != null) {parent.addView(view);}}
});
需要替换
R.layout.your_layout
为你想要异步加载的布局文件的资源ID,parent
是你希望将加载完成的视图添加到的父容器。
AsyncLayoutInflater是通过侧面缓解的方式去缓解布局加载过程中的卡顿,但是它依然存在一些问题:
- 不能设置LayoutInflater.Factory,需要通过自定义AsyncLayoutInflater的方式解决,由于它是一个final,所以需要将代码直接拷处进行修改。
- 因为是异步加载,所以需要注意在布局加载过程中不能有依赖于主线程的操作。
- 可能引起UI不一致:由于异步加载,视图的加载和渲染可能会出现延迟,导致UI在加载期间出现不一致的状态。这可能会影响用户体验,特别是在需要快速响应的界面上。
- 难以调试:由于异步加载是在后台线程执行的,因此在调试时可能会难以追踪和调试布局加载过程中的问题。这增加了调试和排查错误的难度。
X2C方案(XML to Code)
-
引入X2C插件:首先需要在项目中引入X2C插件,该插件会将XML布局文件转换为对应的Java代码。
-
创建布局:在项目中创建一个XML布局文件,定义所需的布局结构和属性。
-
生成Java代码:使用X2C插件将XML布局文件转换为Java代码。可以通过自动构建或手动触发插件来执行此转换。转换后,将在生成的Java类中找到与XML布局文件对应的代码。
X2C方案需要进行额外的配置和构建过程,并且生成的Java类与XML布局文件是一一对应的。因此,在使用X2C方案时,需要确保生成的Java类与对应的XML布局文件是匹配的,否则可能会出现布局错误或找不到控件的情况。
其他优化方案
使用ConstraintLayout降低布局嵌套层级
-
实现几乎完全扁平化的布局
-
构建复杂布局性能更高
Compose方案
还没学。。。。
使用RecyclerView
代替ListView
:
RecyclerView
相对于ListView
具有更高的灵活性和性能。它使用了ViewHolder的复用机制,减少了视图的创建和销毁,提高了滚动的平滑度。