这篇文章记录日常开发中一些UI方面的优化点
1.减少布局的嵌套层级
我们都知道布局层级嵌套的多会有以下问题
- 解析
xml的时间会随着增加 - 多级嵌套意味着系统需要进行更多的测量和布局计算
onDraw同样需要递归去绘制- 嵌套的越多需要创建的view对象越多,占用的内存也会增加
那在日常的开发中就需要注意这些地方,尽量去避免多层嵌套
2.使用ConstraintLayout
约束布局一种可以不用嵌套就可以实现复UI的一种布局,Google也极力推荐使用这种布局,比如我们直接创建一个新的工程activcity_main就是使用的ConstraintLayout
真的是可以只使用ConstraintLayout不嵌套其他ViewGroup就可以完成复杂布局,也是我在项目中最优先选择的方式
但是听说ConstraintLayout如果使用在RecyclerView的itemView上,在低版本设备有卡顿问题,我自己是没有遇到
3.静止使用RelativeLayout和LiearLayout的layout_weight
RelativeLayout会测量两次,性能方面有影响
LiearLayout设置layout_weight同样会测量两次
可以使用ConstraintLayout完全代替
4.统一背景色
为了避免过度绘制,我们可以在AndroidManifest中<application>设置theme
<style name="Theme" parent="AppBaseTheme">
<item name="android:windowBackground">@color/main_bg</item>
</style>
不需要再给每个Activity或者Fragment的Root View设置背景色
5. 使用merge、ViewStub标签、include标签
merge标签
可以减少布局的嵌套层级,可以看我之前写的一篇博客 自定义ViewGroup结合merge标签减少嵌套
ViewStub标签
假如有些界面中部分View不需要一开始就显示,或者只有当用户触发一些操作后才显示的情况就可以使用ViewStub标签来延迟显示,不会使用的建议百度,下面我大概说一下原理
- 构造方法中调用
setVisibility(GONE); onMeasure宽高都是0draw和dispatchDraw是空方法- 调用
inflate()时才会调用LayoutInflater.inflate解析view
通过上述四点就可以知道,第一次进入时不会加载ViewStub的layout,真正的加载需要触发inflate(),感兴趣可以看看源码
include标签
如果有相同的组件需要共用,可以使用include标签,可以复用layout减少代码量,这里不在展开细说
6. AsyncLayoutInflater
LayoutInflater是同步方法,AsyncLayoutInflater是异步方法,对于界面相对复杂并且对启动速度有要求的,可以使用AsyncLayoutInflater 异步加载,先显示loading,等onInflateFinished方法结束,再添加到root view中
AsyncLayoutInflater 是 Android 框架提供的一个类,用于在后台线程中异步地加载布局,这样可以避免在主线程(UI线程)中加载布局时导致的界面卡顿。使用 AsyncLayoutInflater 可以提高应用的响应性和流畅度。
简单使用如下
AsyncLayoutInflater inflater = new AsyncLayoutInflater(getActivity());
inflater.inflate(R.layout.my_layout, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(@NonNull View view, int resId, @Nullable ViewGroup parent) {
// 在这里处理加载完成的布局
// 你可以在这里将view添加到布局中
}
});
7.RecyclerView一些优化
RecyclerView应该是每个App都离不开的一个组件,那么我们可以早日常开发阶段注意以下几个优化点
7.1 setHasFixedSize
对于item固定的宽高的使用setHasFixedSize 避免requestLayout, RecyclerView 可以优化布局计算,因为它不需要在每次数据变化时重新测量视图。
注意设置该值需要调用onItemRangexxx方法才生效,例如
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
//省略代码........
triggerUpdateProcessor();
}
void triggerUpdateProcessor() {
//这里重点,mHasFixedSize需要=true减少requestLayout();的调用
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
7.2 复用RecyclerViewPool
如果不同的RecyclerView对象的Adapter是同一个,或者说可以复用Adapter那么就可以复用RecyclerViewPool,减少缓存池的占用内存
public class RecycledPoolHelper {
private static final RecycledPoolHelper sInstance = new RecycledPoolHelper();
private RecyclerView.RecycledViewPool mRecycledViewPool;
private RecycledPoolHelper() {
}
public static RecycledPoolHelper getInstance() {
return sInstance;
}
/**
* 当单例中,recyclerView 回收池为空时,设置回收池
*
* @param pool
*/
public void setPool(RecyclerView.RecycledViewPool pool) {
if (mRecycledViewPool == null && pool != null) {
mRecycledViewPool = pool;
}
}
/**
* 获取recyclerView 回收池
*
* @return
*/
public RecyclerView.RecycledViewPool getRecycledViewPool() {
return mRecycledViewPool;
}
/**
* 在合适的时机置空回收池
*/
public void clear() {
mRecycledViewPool = null;
}
}
使用
RecycledPoolHelper.getInstance().setPool(recyclerView.getRecycledViewPool());
使用场景
相信有大部分人做过一种需求,首页多类型布局,TabLayout+Fragment+ViewPager+RecyclerView列表,每个Tab下面的列表的Fragment是共用的,这种情况下不同的Fragment对象中的RecyclerView可以复用RecyclerViewPool
7.3 setHasStableIds
setHasStableIds 方法的作用是告诉 RecyclerView 它的每个 Item 都有一个唯一且稳定的 ID
帮助RecyclerView更好地识别和复用ViewHolder,避免频繁创建和销毁ViewHolder,减少内存消耗
需要重写getItemId方法指定唯一Id,别忘了哦
7.4 DiffUtil
使用DiffUtil来判断列表中哪些item data数据发生了变化,然后局部刷新
只更新列表中发生变化的部分,而不是整个列表,这样可以显著提高性能。
这里不再具体使用,百度一下吧
7.5 最好不要使用ScrollView 中嵌套一个 RecyclerView
尽量不要使用这种嵌套的方式,容易产生滑动冲突,使用不当还会使RecyclerView的复用机制消失,原因是计算不到高度
可以使用 RecyclerView的getViewType分组实现复杂布局
总结
这这边能想到的就以上几点,如有遗漏欢迎评论区补充,大家在平时开发中也需要注意,养成良好的习惯
当然使用Google推出的Compose就更好了
计划后面会写一系列性能优化的文章包括,持续更新
- 内存方面的优化
- 启动优化
- Apk瘦身
- 卡顿优化
- Anr检测
- 线程池优化