性能优化-布局优化
屏幕的UI刷新机制
布局的选择
优化控件的使用
原生View的优化
1、屏幕的UI刷新机制
超过16ms会感觉卡顿,
刷新率(Refresh Rate):指一秒内刷新屏幕的次数,例如60HZ;
帧率(Frame Rate):指GPU在一秒内操作画面的帧数,例如30fps,60fps;
UI刷新的过程
刷新率和帧率不能总保持同步,即GPU存储数据时间和display读取数据时间不一致,帧率超过刷新率,可能会导致画面撕裂。
引入VSync,既垂直同步,可理解为帧同步,保障GPU生成帧的速度和display刷新的速度保持一致,Android系统每16ms发出一次VSync信号,触发UI渲染更新
2、布局的选择
- FrameLayout能实现的优先使用FrameLayout,查看源代码行数最少,代码逻辑更简单;
- 优选使用RelativeLayout,能实现嵌套才能实现的复杂布局
- 挡在
RelativeLayout
和LinearLayout
同事能够满足需求时,优先选择LinearLayout;RelativeLayout有重复绘制的问题。
2.1 重复绘制
它指的是屏幕上的某个像素在同一帧内被绘制了多次。这会导致不必要的GPU工作,从而消耗更多的电池电量和降低应用的性能。
1.使用开发者选项中的“显示GPU视图更新”:
2.优化你的布局:
- 避免在布局中使用不必要的嵌套视图。
- 使用ViewStub来延迟加载不常用的视图。
- 考虑使用
ConstraintLayout
或RecyclerView
等更高效的布局工具。
3.使用背景颜色:
- 确保你的视图和布局只设置了必要的背景颜色。
- 使用
android:windowBackground
或android:windowIsTranslucent
来优化窗口背景。
4.使用透明度时要小心:
- 透明度会增加绘制成本,因为它需要额外的GPU工作来混合颜色。
- 如果可能的话,尝试避免使用完全透明的视图,或者使用View.setVisibility(View.GONE)来完全移除它们。
5. 优化自定义视图:
- 如果你有自定义视图,确保它们的
onDraw()
方法被正确优化。 - 避免在
onDraw()
中执行昂贵的操作,如创建新的位图或执行复杂的计算。
2.2 避免Overdraw
要避免Android应用中的Overdraw(过度绘制),可以遵循以下建议,这些建议基于参考文章中的信息并进行了适当的总结和归纳:
1.了解Overdraw的概念:
- Overdraw(过度绘制)是指在一帧的时间内(约16.67ms),同一像素被绘制了多次。这会导致额外的CPU和GPU工作,影响应用的性能和响应速度。
2.使用开发者选项检测Overdraw:
- 在Android设备的开发者选项中启用“调试GPU过度绘制”功能。这将使用不同颜色来指示Overdraw的程度,帮助开发者快速定位问题区域。
- 颜色指示:
- 无色:表示没有Overdraw,像素只被绘制了一次。
- 蓝色:表示1倍Overdraw,像素被绘制了两次。
- 绿色:表示2倍Overdraw,像素被绘制了三次。
- 浅红色:表示3倍Overdraw,像素被绘制了四次。
- 深红色:表示4倍及以上Overdraw,像素被绘制了五次或更多次。
3.优化布局以减少层级:
- 尽量避免使用嵌套的布局容器,特别是嵌套的RelativeLayout,因为它们可能导致更复杂的布局过程和更多的Overdraw。
- 考虑使用
LinearLayout、ConstraintLayout或RecyclerView
等更高效的布局工具。
4.去除不必要的背景:
- 移除不必要的背景色和背景图片,特别是当它们与父视图的背景色相同时。
- 自定义视图的背景时,使用透明或与父视图相同的颜色来避免额外的绘制。
5.使用ViewStub:
- ViewStub允许你延迟加载不常用的视图,从而减少初始布局时的Overdraw。
6.合并绘制调用:
- 使用merge标签合并多个小的绘制调用,以减少Overdraw。
7.避免使用高Alpha值:
- 透明度和混合颜色会增加绘制成本。尽量避免使用接近完全透明的视图,或使用View.setVisibility(View.GONE)来完全移除视图。
8.利用硬件加速:
- 确保你的应用启用了硬件加速(在Android 3.0及以上版本中默认启用)。硬件加速可以将绘制工作从CPU转移到GPU,但请注意它并不总是能减少Overdraw。
9.分析绘制调用:
- 使用Android Studio的Profiler工具或第三方库(如LeakCanary)来分析应用的绘制调用,找出性能瓶颈并进行优化。
10.持续更新和测试:
- 随着Android系统和工具链的更新,不断关注新的性能优化建议和方法,并在你的应用中进行测试和应用。
通过上述建议,你可以有效地减少Android应用中的Overdraw,提高应用的性能和用户体验。
2.3 ConstraintLayout
ConstraintLayout是Android开发中一个非常重要的布局工具,它允许开发者通过约束来定义视图的位置和大小,从而创建灵活且复杂的用户界面。以下是关于ConstraintLayout的详细介绍:
2.3.1 优点和特性
优点:
a.灵活性:ConstraintLayout可以在不嵌套ViewGroup的情况下实现非常庞大、复杂的布局。
b.性能:由于减少了布局的层级,ConstraintLayout通常比传统的布局方式具有更好的性能。
c.直观性:ConstraintLayout的约束方式直观易懂,使得布局设计更加容易。
特性:
a.Constraint Bias(约束偏移):通过设置约束偏移来控制控件相对于约束区域的位置。
b.Circular Constraints(圆形约束):通过设置控件的圆心位置和半径来实现圆形约束。
c.Chains(链式约束):可以将多个控件链接在一起形成一个链,实现类似于LinearLayout的效果。
d.Barrier(屏障):通过屏障来自动调整控件的位置,使得布局更加灵活。
e.Group(组合):可以将多个控件组合在一起形成一个组,方便对组内的所有控件进行操作。
f.Placeholder(占位符):使用占位符来替代实际的控件,实现动态的布局。
2.3.2 使用场景
- 固定比例视图:通过DimensionRatio属性,可以直接在XML中实现比例视图的控制。
- N等分布局:ConstraintLayout可以直接实现N等分布局,无需进行动态计算。
- 复杂布局:对于需要精确控制控件位置和大小的复杂布局,ConstraintLayout通常是一个更好的选择。
2.3.3 使用方法
- 在app的Gradle文件中添加ConstraintLayout的依赖。
- 在XML布局文件中使用
<androidx.constraintlayout.widget.ConstraintLayout>
作为根元素。 - 为子控件添加水平和垂直约束,定义其位置。
综上所述,ConstraintLayout是一个强大而灵活的布局工具,通过约束来定义视图的位置和大小,可以实现复杂且高效的用户界面。对于需要精确控制布局的应用来说,ConstraintLayout通常是一个很好的选择。
3、优化控件的使用
原生View的优化:include标签、merge标签、ViewStub标签
3.1 include标签
用于在布局文件中直接引用另一个布局文件。
目的:提高代码的复用性,减少代码;将布局中公共部分抽取供其他layout使用。在ConstraintLayout或其他布局中特别有用。
用途:如果你有一个复杂的按钮布局,并且需要在多个地方使用它,你可以将该布局定义在一个单独的XML文件中,并在需要的地方使用include标签来引用它。以下是使用include标签的基本语法:
<include
android:id="@+id/included_layout"
layout="@layout/layout_to_include"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp" />
在这个例子中:
- android:id 是为这个被包含的布局定义的唯一ID(可选)。
- layout 属性指定了要包含的布局文件的名称(不包括.xml扩展名)。
android:layout_width
和android:layout_height
定义了包含布局的尺寸(可以是具体值,如match_parent
、wrap_content
或具体的dp值)。- 其他属性,如
android:layout_margin
,可以用于调整包含布局的位置和外观。
3.2 merge标签
目的:解决布局层级的优化,减少布局嵌套的层次,提高布局加载的效率;
解释:<merge>
标签是一个特殊的标签,用于优化布局嵌套层级。它主要用于在通过 <include>
标签包含布局时,避免额外的视图层级。<merge>
标签通常作为被包含布局文件的根元素,但它本身不会在最终的视图层级中出现。
1.性能优化:
- Android去解析和展示一个布局是需要消耗时间的,布局嵌套的越多,解析起来就越耗时,性能也就越差。因此,使用merge标签可以减少布局嵌套的层级,从而提高应用的性能。
2.注意事项:
- merge标签不能单独使用,它必须作为其他布局文件的子布局被引用。
- 当使用merge标签时,需要确保被引用的布局在被合并到主布局后仍然保持其布局结构的正确性。
通过合理地使用merge标签,开发者可以有效地优化Android应用的布局结构,提高应用的性能和用户体验。
在Android开发中
以下是使用<merge>
标签的基本步骤和示例:
1.创建包含<merge>
的布局文件:
创建一个新的XML布局文件,并使用<merge>
作为根元素。在这个<merge>
标签内,你可以放置其他视图元素。
2.在父布局中使用<include>
包含该布局:
在需要使用这个布局的地方,使用<include>
标签来包含它。<include>
标签将<merge>
标签的内容直接合并到父布局中,而不是作为一个新的视图层级。
示例:1. 创建包含<merge>
的布局文件(例如:header_layout.xml)
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/header_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="这是一个标题" />
<Button
android:id="@+id/header_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击我" />
</merge>
- 在父布局中使用 包含该布局(例如:activity_main.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 使用 <include> 包含 header_layout.xml -->
<include layout="@layout/header_layout" />
<!-- 其他视图元素 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="这里是主布局的内容" />
</LinearLayout>
在上面的示例中,header_layout.xml
使用 <merge>
作为根元素,包含了两个子视图(一个 TextView
和一个 Button
)。在 activity_main.xml 中,我们通过 <include>
标签包含了 header_layout.xml,并且由于使用了 <merge>
,这两个子视图(TextView 和 Button)被直接合并到了 LinearLayout 中,而不是作为一个新的层级。
注意事项
<merge>
标签只能作为布局文件的根元素,并且不能设置任何视图属性(如 android:layout_width 或 android:layout_height
),因为这些属性在合并时不会被使用。
- 当使用
<include>
标签包含带有<merge>
的布局时,如果父布局是一个FrameLayout 或 LinearLayout
,并且你希望<merge>
的内容遵循父布局的某些布局参数(如layout_gravity
或layout_weight
),你可能需要在被包含的布局中为相应的子视图设置这些参数,而不是在<include>
标签中设置。 - 在某些情况下,你可能需要为被包含的布局设置ID(例如,如果你需要在代码中引用整个布局)。在这种情况下,你不能在
<merge>
标签上设置ID,因为<merge>
本身不是一个视图。相反,你需要在<merge>
的一个子视图上设置ID,并在代码中引用该子视图。
3.3 ViewStub标签
ViewStub
用于实现视图延迟加载的一种轻量级视图组件,只有加载该布局的时候才占用资源,INVISIBLE状态时不会绘制出来的。
特点
1.不可见且无尺寸:ViewStub在布局文件中定义时,是不可见的,并且其大小为0,不会占用任何屏幕空间。
2.延迟加载:ViewStub
允许开发者在需要时才加载其对应的布局资源,从而避免了不必要的资源浪费和渲染时间。
3.一次性加载:一旦ViewStub
的inflate()
方法被调用,它将被替换为所加载的布局视图,并且该ViewStub实例将不再存在。这意味着你不能多次调用inflate()来加载相同的布局。
用法
1.定义在XML布局文件中:通过在XML布局文件中添加<ViewStub>
标签来定义ViewStub
。需要指定其ID、要加载的布局资源(通过android:layout
属性)以及其他可选属性。
<ViewStub
android:id="@+id/my_view_stub"
android:layout="@layout/my_view_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
2.在代码中加载布局:在Activity或Fragment的代码中,首先通过findViewById()方法找到ViewStub实例,然后调用其inflate()方法来加载布局。
ViewStub myViewStub = findViewById(R.id.my_view_stub);
View inflatedView = myViewStub.inflate();
3.操作加载后的视图:加载后的视图可以通过返回的View对象进行操作,如设置控件的值、添加事件监听器等。
优势
1.节省内存:由于ViewStub在加载前不占用任何资源,因此可以显著减少应用的内存占用。
2.提高性能:通过延迟加载视图,可以减少应用的初始化时间和渲染时间,从而提高应用的性能。
3.灵活性:ViewStub允许开发者根据需要在运行时动态加载视图,增加了布局的灵活性和可扩展性。
综上所述,ViewStub是Android开发中一种非常有用的工具,可以帮助开发者实现视图的延迟加载和动态加载,从而提高应用的性能和用户体验。
4、原生View的优化
4.1 ListView的优化
ListView在Android开发中是一个常用的UI组件,用于展示垂直滚动的列表项。然而,当ListView需要展示大量数据时,性能问题可能会变得突出。以下是一些关于ListView优化的方法:
1.使用ViewHolder模式:
- 在getView()方法中使用ViewHolder来缓存View,避免重复的findViewById操作,从而提高性能。
- ViewHolder是一个简单的类,用于存储View组件,并作为Tag附加到convertView上。
2.分页加载:
- 当ListView中的数据量非常大时,可以使用分页加载的方式,每次只加载部分数据,减少内存的占用。
- 这可以通过在滚动到底部时加载更多数据来实现。
3.图片缓存:
- 当ListView中包含大量的图片时,使用图片缓存技术可以避免图片的重复加载,提高性能。
- 常见的图片缓存库有LruCache、DiskLruCache和Glide、Picasso等第三方库。
4.异步加载:
- 对于数据量非常大或者数据加载比较耗时的ListView,可以使用异步加载的方式,将数据加载操作放在后台线程中进行,避免阻塞UI线程。
5.懒加载:
- 懒加载是只有当用户滚动到某一项时,才加载该项的数据。这可以进一步减少内存占用和提高性能。
6.使用固定高度:
- 当ListView中的项的高度是固定的时,通过设置固定高度可以避免每次重新计算高度,从而提高性能。
7.优化Adapter中的getView方法:
- 在getView()方法中尽量减少复杂的逻辑操作,避免频繁创建新的View对象。
- 充分利用convertView参数,尽可能重新利用已有的View对象。
8.减少布局层级:
- 优化ListView项的布局,减少布局层级,可以提高渲染性能。
9.使用内存优化技术:
- 对于ListView中的图片资源,可以使用等比例缩小图片、对图片采用软引用等技术来减少内存占用。
10.考虑使用RecyclerView:
- RecyclerView是ListView的一个更强大、更灵活的替代品。它提供了更多的功能,如不同的布局方向、动画和滚动效果等。
- RecyclerView还支持更复杂的列表项布局和更高效的内存管理。
通过上述优化方法,可以显著提高ListView的性能,尤其是在处理大量数据时。不过,具体使用哪种优化方法取决于应用的需求和场景。
4.2WebView的优化
1.全局WebView:
- 初始化全局WebView:在应用启动时初始化一个全局的WebView对象,并预先加载资源,减少后续使用时的加载时间。
2.加载优化:通过预加载,延迟加载,可以有效减少启动的时间。
- 预加载:在WebView加载当前页面的同时,预加载即将显示的下一页内容,提高页面切换时的加载速度。
- 延迟加载:对于非关键资源,如图片,可以延迟加载,即在页面加载完成后再加载这些资源,加快首次加载速度。
3.请求优化:通过并行、拦截请求策略,可以加快网络耗时,与减少重复的耗时。
- 并行请求:在加载H5页面时,同时发起多个请求以获取模板和数据,减少总耗时。
- 拦截请求:通过自定义WebViewClient来拦截WebView的请求,实现请求优化和性能监控。
4.缓存优化:合理使用缓存,减少网络请求,提高加载速度。
- 启用缓存:通过设置WebView的缓存策略,如
WebSettings.LOAD_DEFAULT
,让WebView在加载页面时自动缓存页面内容,以便在后续访问相同页面时可以快速加载。 - 缓存模式选择:选择
LOAD_CACHE_ELSE_NETWORK
模式,以在缓存存在时优先使用缓存数据,减少网络请求。
5.渲染优化:合理的启动硬件加速,可以有效的提高渲染速度。
6.进程优化:启用多进程模式,可以避免主线程阻塞,内存泄漏、异常crash等问题。
走过路过,麻烦关注下微信公众号,不胜感激~
微信公众号链接
: 性能优化-布局优化