【Android面试题】高级UI面试题——ListView卡顿的原因以及优化策略

188 阅读6分钟

ListView卡顿的原因以及优化策略

这道题想考察什么?

  1. 是否了解ListView卡顿的原因以及优化策略与真实场景使用,是否熟悉ListView卡顿的原因以及优化策略在工作中的表现是什么?

考察的知识点

  1. ListView卡顿的原因以及优化策略的概念在项目中使用与基本知识

考生应该如何回答

​ 导致ListView卡顿的原因有很多,主要包括:Item没有复用、层级过深、数据绑定逻辑过多、滑动时不必要的图片刷新以及频繁的notifyDataSetChanged。

Item没有复用

​ ListView的Item没有复用是导致卡顿的常见原因。在滑动的过程中,有些Item离开屏幕,有些Item需要进入屏幕。离开屏幕的Item一般会加入到缓存容器中,而不是让item直接被GC的回收。如果有缓存,那么滑动进入屏幕的Item会优先从缓存容器中读取。读取到的缓存会通过给convertView赋值来更新UI。如果没有课复用的item,那么之后每次都会重新创建这些Item,也就是通过LayoutInflater进行Item的创建,LayoutInflater创建Item是采用反射去解析xml因此是比较耗费时间的,这就会带来性能损耗。

布局的层级过深

​ 布局的层级过深是非常容易引起卡顿,原因是ViewGroup会对子View进行多次测量。假设有一个这样的场景:父布局的布局属性是wrap_content、子布局是match_parent,此时的布局过程是:

  1. 父布局先以0为强制宽度测量子View、然后继续测量剩下的其他子View
  2. 再用其他子View里最宽的宽度,二次测量这个match_parent的子 View,最终得出它的尺寸,并把这个宽度作为自己最终的宽度。

即 这个过程就对单个子View进行了二次测量。

「而布局嵌套对性能影响则是指数形式的」,即:父布局会对每个子view做两次测量,子view也会对下面的子view进行两次测量,即相当于是 O(2ⁿ)测量。

​ 总的来说,层级过深会导致多次测量,不必要的测量造成了多余的性能消耗,最终有可能会引起页面的卡顿。

数据绑定逻辑过多

​ 数据绑定逻辑过多也容易导致卡顿。在适配器中,我们通过onCreateViewHolder创建Item,创建之后的Item要通过onBindViewHolder进行数据绑定。过程大致如下:先通过position找到数据源中对应的数据,然后再将数据设置到控件的属性中。简单的设置以及一些简单的计算是不会消耗很多的时间的,如果是包含大量的控件属性设置、数据遍历、转化的话时间就会成倍的增长,最终可能就会导致ListView的卡顿。

滑动时不必要的图片刷新

​ 图片的刷新也有可能导致卡顿。如果Item中包含图片并且我们以非常快的速度滑动列表,那么代表着有Item快速离开屏幕,也有Item快速的进入屏幕。快速进入屏幕的Item必须以极短的时间完成数据绑定和图片加载刷新,但图片的加载是需要耗费较大的性能和时间的,频繁的图片加载会延缓Item的数据绑定过程,容易造成卡顿。

频繁的notifyDataSetChanged

​ 频繁的notifyDataSetChanged也是导致ListView卡顿的一大原因。notifyDataSetChanged是刷新ListView的所有Item的界面上展示的数据,所有的Item都刷新数据肯定是十分消耗性能的,如果是频繁的notifyDataSetChanged,那带来的性能消耗肯定更加严重,最终非常有可能导致到卡顿。

解决办法

​ Item没有复用,这个问题的解决就是加上复用代码,让item的界面和展示的数据可以得以复用,减少之后创建新的item和绑定数据的次数。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
        final ViewHolder holder;
        ListViewItem itemData = items.get(position);
        if(convertView == null){
            convertView = View.inflate(context, R.layout.list_item_layout, null);
            holder = new ViewHolder();
            holder.userImg = (ImageView) convertView.findViewById(R.id.user_header_img);
            holder.userName = (TextView) convertView.findViewById(R.id.user_name);
            holder.userComment = (TextView) convertView.findViewById(R.id.user_coomment);
            convertView.setTag(holder);
        }else{
            holder = (ViewHolder) convertView.getTag();
        }
        holder.userImg.setImageResource(itemData.getUserImg());
        holder.userName.setText(itemData.getUserName());
        holder.userComment.setText(itemData.getUserComment());
        return convertView;
}

static class ViewHolder{
        ImageView userImg;
        TextView userName;
        TextView userComment;
}

​ 如上面代码所示,可以先对convertView进行判断,如果为空再通过LayoutInflater创建新的Item,如果不为空则可以复用。然后通过ViewHolder进一步优化,不用ViewHolder的情况下每次设置Item的值都会需要通过convertView.findViewById寻找相关的控件,不方便且耗时,如果在创建Item时将各类控件引用设置到ViewHolder中,以后就可以直接从ViewHolder找控件,是十分节约性能的。

​ 布局的层级过深的解决方式是减少层级,尽量使用约束布局ConstraintLayout。约束布局ConstraintLayout是Google 参考了ios的约束布局创建的,他的功能相当于 RelativeLayout + LinearLayout,而性能相对于他们提升了40%左右,所以通过使用ConstraintLayout可以达到优化性能的目的。

​ 数据绑定逻辑过多的话,首先是区分出与界面有关的逻辑计算和与界面无关的逻辑计算,与界面有关的逻辑计算只能通过优化算法逻辑、数据结构处理,与界面无关的逻辑计算不影响界面的显示可以开线程用于计算。

​ 滑动时不必要的图片刷新这个解决办法就十分明确,如果快速滑动带来了很多不必要的刷新,那么可以在快速滑动的时候设置为不刷新图片,这样可以减少非常多不必要的性能消耗。

​ 频繁的notifyDataSetChanged是容易导致卡顿的,这种情况可以优化代码逻辑,减少频繁的notifyDataSetChanged。

详细关注公众号:Android老皮
还能解锁  《Android十大板块文档》 ,让学习更贴近未来实战。已形成PDF版

内容如下

1.Android车载应用开发系统学习指南(附项目实战)
2.Android Framework学习指南,助力成为系统级开发高手
3.2023最新Android中高级面试题汇总+解析,告别零offer
4.企业级Android音视频开发学习路线+项目实战(附源码)
5.Android Jetpack从入门到精通,构建高质量UI界面
6.Flutter技术解析与实战,跨平台首要之选
7.Kotlin从入门到实战,全方面提升架构基础
8.高级Android插件化与组件化(含实战教程和源码)
9.Android 性能优化实战+360°全方面性能调优
10.Android零基础入门到精通,高手进阶之路