#ListView的常用优化
ListView概述
- 在Android应用开发中,ListView是最在 Android 应用开发中,ListView是最为常见的组件之一。它将数据以列表的形式展现出来,在我们平时的开发中也是很常见。一般而言,一个ListView 由以下三个元素组成:
- View: 用来展示列表的 view,通常是一个 xml 文件所指定的。
- 适配器:用来把数据映射到 ListView 上,可以理解为 ListView 界面和数据之间的纽带和桥梁。
- 数据:具体被映射的数据和资源,可以是字符串、图片等。
ListViewde的工作原理
- 接下来我们简单介绍一下 ListView加载数据的原理。有了这方面的了解后再说优化才行。ListView 针对每个 item(列表项)都要求 Adapter(适配器)“返回一个视图”(getView)。
- ListView 在开始绘制的时候系统会首先调用 getCount()函数,根据它的返回值得到 ListView 的长度,然后根据这个长度,逐次调用 getView()方法一行一行的绘制ListView 的每一项。getCount()方法的返回值是几就显示几行。
具体怎么绘制呢?当我们调用 getView()方法时首先会通过加载布局文件生成一个新的 View 对象(实际上是一个 ViewGroup),然后通过该对象将布局文件中的各个组件实例化(也就是 findViewByid()),这样便可将数据对应到各个组件上了。但是加载布局文件属于I/O 操作,是很耗时的,当我们的数据量非常庞大或者列表项非常多的时候,很容易导致Anr(应用程序无响应)现象。
那我们到底该如何解决这个问题呢?
ListView的真正优化处理方案
- 一.复用 ConvertView;使用 ViewHolder,setTag
- 二.ListView 中数据的分批及分页加载
- 三.异步加载图片及图片的缓存处理
一. 1. 重点内容复用convertView
- 所谓的复用,就是循环反复的利用。假如说我们有一百条列表项,我们没有必要一一为他们创建新的对象,我们只要创建三个或者五个item 对象,之后对他们循环利用即可。
- 其实 Android 系统本身为我们考虑了 ListView 的优化问题。在 getView()方法中系统就为我们提供了一个复用 View 的历史缓存对象 convertView,当显示第一屏的时候,每一个item都会新创建一个 View 对象,这些 View 都是可以被复用的。
- 此时 convertView 在 getView()中是空值。假如说是item1 滚动出屏幕时会进入到一个叫 Recycler 的 Android构件中去填充 convertView 并缓存起来,当新的项目从屏幕底端上来时,ListView 调用getView()方法,convertView 此时不是空值了,它的值是 item1。
- 你只需要设置新的数据,然后返回 convertView,不必重新创建一个视图 。
2. 缓存item条目的引用–ViewHolder
- findViewById()这个方法是比较耗性能的操作。当我们通过加载布局文件来创建 View 对象的时候,一但该对象生成,其布局文件中子控件的id 也就不会改变了,因此我们在第一次加载的时候,需要将把相关的数据保存起来,下次直接从缓存中读取,从而减少findViewById()的次数,优化显示效率。
- 1. 创建一个静态ViewHolder 内部类(可以抽取)
- 2. 创建自定义的类ViewHolder holder = null ;
- 3. setTag: 在创建新的 ListView 的时候创建新的 ViewHolder 对象,然后通过 findViewById找到子控件并将其引用保存起来。而 View 中有一个方法 setTag,可用来保存一些数据结构。通过 convertView.setTag(holder)将 ViewHolder 对象的引用设置到 view 中。
- 4. 在复用 ListView 中条目的时候,再通过 convertView.getTag(holder)获取 holder对象的引用,此时我们只需要去修改其对象中子控件的值即可,不用 findViewById()
public View getView(final int position, View convertView, ViewGroup parent) {
MyHodler holder = null;
if (view = null){
holder = new MyHodler();
view = View.inflate(context,R.layout.item,null);
holder.tv = (TextView) view.findViewById(R.id.textView);
view.setTag(holder));
} else {
holder = (MyHodler) view.getTag();
holder.tv.setText(list.get(i));
}
return view;
}
static class MyHodler{
TextView tv;
}
二. ListView 中数据的分批及分页加载
- 当一个应用要展示很多的数据时,一般情况下都不会把所有的数据一次性全部展示出来,通过分页的形式来展示数据。这样会有更好的用户体验。因此,很多应用都是采用分批次加载的形式来获取用户所需的数据。
- 例如:看手机QQ空间的时候,用户滑动至列表底端时自动加载下一页数据,或者也见到过底部放置一个”查看更多”按钮,用户点击后,加载下一页数据。(服务器需要提供接口, 一次获取一段的数据. 数据库提供接口 ,一次查询一段的数据. )
- 分页加载的核心技术和大致思路是?
1. 借助 ListView 的滚动监听器 OnScrollListener,去判断何时该加载新数据(判断是否滚到最后一行)。
2. 往服务器传递表示页码的参数:page。而该 page 会每加载一屏数据后自动加一。
3. 利用 addAll()方法不断往 list 集合末端添加新数据,使得适配器的数据源每新加载一屏数据就发生变化。
4. 利用适配器对象的 notifyDatasetChanged()方法。该方法的作用是通知适配器及与该数据有关的 view,数据已经放生变动,要刷新自己、更新数据。 需要注意的是在 OnScrollListener 监听器中有两个需要实现的方法:屏幕滚动状态发生变化时调用 的方法 onScrollStateChanged()和屏幕滚动时调用的方法 onScroll()。 在滚动状态发生改变的方法中,有三种状态:
- 1. 手指按下移动的状态: SCROLL_STATE_TOUCH_SCROLL: // 触摸滑动
- 2. 惯性滑动的状态: SCROLL_STATE_FLING: // 惯性滑翔
- 3. 静止状态: SCROLL_STATE_IDLE: // 静止
对不同的状态进行处理:
- 分批加载数据,只关心静止状态,只关心最后一个可见的条目。如果最后一个可见条目就是数据源集合里的最后一个,此时可以加载更多数据。在每次加载的时候,计算出滚动的数量,当滚动的数量大于等于总数量的时候,可以提示用户无更多数据了。
三. 异步加载图片及图片的缓存处理
三级缓存的原理:
- ①先从内存缓存中获取图片显示(内存缓冲)
- ②获取不到的话从 SD 卡里获取(SD 卡缓冲)
- ③都获取不到的话从网络下载图片并保存到 SD 卡同时加入内存
- 三级缓存的优点:减轻服务器的压力、节省用户的流量开销、提高应用的稳定性和客户端的响应速度,提高用户的体验等等。
- Tips:优化过程中可能出现的问题:图片重复、错位、闪烁
- 产生的原因:出现上述的原因是异步加载及对象被复用造成的。
- 拿图片错位举例:显示错乱是指某行* item* 显示了不属于该行 item 的图片。比如ListView 滑动到第 2 行会异步加载某个图片,但是加载很慢,加载过程中 listView 已经滑动到了第 14 行,第 2 行已不在屏幕内,根据上面介绍的缓存原理,第 2 行的 view 可能被第14 行复用,第 14 行显示了第 2 行的 View,这时之前的图片加载结束,就会显示在第 14 行,造成错乱。
- 解决办法:给 ImageView 设置一个 Tag, 并预设- 一个图片。这个 Tag 中设置的是图片的 url,然后在加载的时候取得这个 url 和要加载那 position 中的 url 对比,如果不相同就加载,相同就是复用以前的就不加载了。
- 当 Item2 比 Item14 图片下载的快时, 你滚下去使 Item14 可见,这时 ImageView 的 tag 被设成了Item14 的 URL, 当 Item2下载完时,由于 Item2 不可见现在的 tag 是 Item14 的 URL,所以不满足条件,虽然下载下来了但不会设置到 ImageView 上, tag 标识的永远是可见 view 中图片的 URL。
其它优化:
1. 在adapter中的getView方法中尽量少使用逻辑
2. 滑动的时候不加载图片
3. 将ListView的scrollingCache和animateCache设置为false
4. item的布局层级越少越好
总计
- 最后讲一下我个人对优化的理解。其实对于任何控件或是程序,一提到优化我们主要从两个方面来考虑:
1.开发者的角度:节省精力物力,提高开发效率
2.使用者的角度:加载速度更快,运行更流畅,用户体验度更好。这就是我们优化的最终目的。