【Android性能优化】UI布局优化

1,730 阅读5分钟

这篇文章记录日常开发中一些UI方面的优化点

1.减少布局的嵌套层级

我们都知道布局层级嵌套的多会有以下问题

  1. 解析xml的时间会随着增加
  2. 多级嵌套意味着系统需要进行更多的测量和布局计算
  3. onDraw同样需要递归去绘制
  4. 嵌套的越多需要创建的view对象越多,占用的内存也会增加

那在日常的开发中就需要注意这些地方,尽量去避免多层嵌套

2.使用ConstraintLayout

约束布局一种可以不用嵌套就可以实现复UI的一种布局,Google也极力推荐使用这种布局,比如我们直接创建一个新的工程activcity_main就是使用的ConstraintLayout

不会使用的推荐阅读

真的是可以只使用ConstraintLayout不嵌套其他ViewGroup就可以完成复杂布局,也是我在项目中最优先选择的方式

但是听说ConstraintLayout如果使用在RecyclerViewitemView上,在低版本设备有卡顿问题,我自己是没有遇到

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或者FragmentRoot View设置背景色

5. 使用merge、ViewStub标签、include标签

merge标签

可以减少布局的嵌套层级,可以看我之前写的一篇博客 自定义ViewGroup结合merge标签减少嵌套

ViewStub标签

假如有些界面中部分View不需要一开始就显示,或者只有当用户触发一些操作后才显示的情况就可以使用ViewStub标签来延迟显示,不会使用的建议百度,下面我大概说一下原理

  1. 构造方法中调用setVisibility(GONE);
  2. onMeasure宽高都是0
  3. drawdispatchDraw是空方法
  4. 调用inflate()时才会调用LayoutInflater.inflate解析view

通过上述四点就可以知道,第一次进入时不会加载ViewStublayout,真正的加载需要触发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 避免requestLayoutRecyclerView 可以优化布局计算,因为它不需要在每次数据变化时重新测量视图。

注意设置该值需要调用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的复用机制消失,原因是计算不到高度

可以使用 RecyclerViewgetViewType分组实现复杂布局

总结

这这边能想到的就以上几点,如有遗漏欢迎评论区补充,大家在平时开发中也需要注意,养成良好的习惯

当然使用Google推出的Compose就更好了

计划后面会写一系列性能优化的文章包括,持续更新

  1. 内存方面的优化
  2. 启动优化
  3. Apk瘦身
  4. 卡顿优化
  5. Anr检测
  6. 线程池优化