这段时间研究Android的UI引擎什么的,顺便看了好多有关如何加速Android UI界面的东西,这里分享一下,第一篇是关于AdapterView的。

ListView是我们在写Android程序的时候最经常会使用的控件之一,但是很多应用当中的ListView在滑动和加载过程中都会出现卡的现象,这儿分享如何避免这种卡的两个方法。

1. 使用convertView。

在Adapter中,我们必须要重载也是最重要的一个函数就是

getView(int position, View convertView, ViewGroup parent)

那么通常情况下呢,我们都会在getView当中,利用LayoutInflater来inflate一个xml或者自己new出一些控件来,可能很多人觉得Android会自动缓存这些View,然后只有在没有缓存的时候才会调用我们的这个函数,但是实际上AdapterView的工作原理并不是这样的。

AdapterView会在每次需要View的时候都去调用一下我们的getView函数以获得最新的View,超出屏幕之外的不可见的View基本都不会缓存,具体的话我记得Gallery会缓存左右多余的一个,ListView好像也是多一个。这样子的话,如果用户在上下scroll的过程当中,AdapterView会不断的调用我们的getView,如果这个时候我们一直在不停的创建一大堆的View的话,那么不仅每次getView所需的时间会很长,而且由于产生了一大堆的垃圾,也会导致在滑动过程中GC会突然跳出来暂停你的程序几百毫秒的时间,这几百毫秒通常就是用户滑动不流畅的原因所在。

那么我们该怎么样避免产生过多的垃圾呢,这里要介绍一个技巧。

在AdapterView调用我们的getView的时候,第二个参数是convertView,很多人可能从来都没有注意过这个参数,但实际上这个参数就是我们避免产生过多垃圾的关键点。当用户在滑动的过程中,有一些View从可见状态变成了不可见状态,AdapterView会将这些不可见状态的View加入到一个回收垃圾箱当中,当然这个回收垃圾箱是有大小限制的,超过的部分就会被扔掉然后被GC给回收,当每次AdapterView调用我们的getView的时候,它会首先看看回收垃圾箱当中有没有合适的View可以用来作为convertView这个参数传递给我们的,如果有的话,那么就会把它从回收垃圾箱当中取出并作为参数传递给我们。也就是说这个convertView是我们之前返回给AdapterView的东西,而我们如果有了这个东西,就不需要再次不断的创建出新的View来,而只需要去改变这个convertView当中的内容,使其符合当前position的内容即可,所以需要的只是一点点的改变就可以将效率提升一大截,请看下面的代码。

假设我们通常的getView是这样的,这个Adapter只是很简单显示了一堆文字:

public View getView(int position, View convertView, ViewGroup parent) {TextView text = new TextView(parent.getContext());text.setText(mData[position]);return text;}

那么运用了上面我所说的技巧之后,只需要将代码改为:

public View getView(int position, View convertView, ViewGroup parent) {if (convertView == null) {convertView == new TextView(parent.getContext());}convertView.setText(mData[position]);return convertView;}

改动并不大吧,只需要简单的判断一下convertView是否为null,只有在null的情况下我们才会去创建一个新的View,否则我们就会直接利用convertView来显示内容。

但是这个改动能够带来的效果提升非常明显,特别是getView所返回的View越复杂,那么效率提升越大,通常在滑动过程中能够fps大概能够提升一倍左右。


2. 更好的使用convertView

那好,上面的例子太简单了,每个View都只是一个TextView,但是如果我们需要每个View根据不同的位置呈现不一样的东西那该怎么办呢,简单一点的,有些条目是一个icon加一个标题和一个简介,而有些内容没有icon只是标题和简介。

这个时候我们可以有两种办法,一种呢,是将每个View都初始化成一个LinearLayout,然后对于没有icon的条目,将icon设置为gone不可见,这样的话呢,每个convertView都是一样的东西了,我们只需要判断一下是否有icon,并且根据情况将那个ImageView显示或者隐藏就可以了。

但是为什么我们要将多余的ImageView也显示在View Hierarchy中呢,而且针对更复杂的情况,如果有些是条目需要返回一个LinearLayout,有些情况需要返回一个RelativeLayout的话,我们要怎么办呢,是不是就不能利用convertView这个技巧了呢?

当然不是,Adapter当中除了getView还提供了很多函数给我们重载,其中还有两个很重要的函数可以来配合convertView这个技巧。分别是

public int getViewTypeCount (); // 这个函数应该返回我们的getView会返回几种类型的View,默认这个是1,但是针对我上面所说的例子,这个就应该是2public int getItemViewType (int position); // 这个函数应该返回position位置上的View是哪个类型的,针对上面的例子,我们可以让有icon的返回0,没有icon的返回1
利用这两个函数,AdapterView就会知道传递给我们的convertView应该是什么类型,这样我们就可以继续利用传递给我们的convertView了。这个的实现原理是因为AdapterView的垃圾回收箱当中是分类存放的。


3. 使用ViewHolder代替findViewById

在getView当中,当我们使用的都是Android自带控件的时候,我们最常用的一个函数就是findViewById,这个函数的实现呢,是针对当前View的所有子View(包括孙子View等)进行遍历,然后比对id是否相同。那么如果用户在上下滑动的过程中,我每次getView的时候都需要重新遍历很多个子树的时候,就会降低效率,虽然这个可能只是1ms的事情,但是当我们想要fps达到60,每一帧也只有16ms的情况下,1ms还是很值得的。

我们可以利用ViewHolder的技巧来避免这个步骤,比如我们这次需要返回的是一个图标加上一个标题,我们利用一个LinearLayout来实现(这个其实可以只用一个TextView,请见加速技巧(二)),那么原来的就会是这样子的:

public View getView(int position, View convertView, ViewGroup parent) {if (convertView == null) {convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.listitem, parent, false);}((TextView) convertView.findViewById(R.id.title)).setText(mData[position].getTitle());((ImageView) convertView.findViewById(R.id.icon)).setImageDrawable(mData[position].getIcon());return convertView;}


当然上面的这段代码由于只有两个View,所以速度应该也会很快,但是对于复杂的View来说,下面的这个技巧应该就会使得其加速很多。

static class ViewHolder {TextView title;ImageView icon;}public View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder = null;if (convertView == null) {convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.listitem, parent, false);holder = new ViewHolder();holder.title = (TextView) convertView.findViewById(R.id.title);holder.icon = (ImageView) convertView.findViewById(R.id.icon);convertView.setTag(holder);} else {holder = convertView.getTag();}holder.title.setText(mData[position].getTitle());holder.icon.setImageDrawable(mData[position].getIcon());return convertView;}
简单来说,我们就是利用了一个ViewHolder是的findViewById只会在创建View的时候被执行,之后就只需要getTag即可,由于Tag在View中是一个属性(mTag)或者一个SparseArray<Object>,所以速度是O(1)的,这样对于getView来说就会很快。


更多相关文章

  1. android app中如何获取电源锁保持屏幕常亮
  2. Android(安卓)== 简单的binder通信
  3. Android(安卓)HAL的作用及编程实例
  4. android 4.4 Settings分析
  5. 红茶一杯话Binder(ServiceManager篇)
  6. Android(安卓)adb “push pull”中文支持解决方案
  7. Koin in Android: 更简单的依赖注入
  8. android 用户界面事件
  9. android嵌入lua

随机推荐

  1. Android(安卓)WebSettings设置
  2. android附件上传下载(图片,语音,视频)
  3. Android(安卓)中自定义控件和属性
  4. Android(安卓)init简介
  5. 关于android应用--内存的优化
  6. [置顶] android性能测试工具之dumpsys
  7. 11.3、Libgdx的音频之播放PCM音频
  8. android libs下的源码和文档配置
  9. 获取Android设备的唯一识别码UUID
  10. Android单元测试-Robolectric 浅析