Android ListView工做原理彻底解析(转自 郭霖老师博客)

原文地址:http://blog.csdn.net/guolin_blog/article/details/44996879java

 

Android全部经常使用的原生控件当中,用法最复杂的应该就是ListView了,它专门用于处理那种内容元素不少,手机屏幕没法展现出全部内容的状况。ListView可使用列表的形式来展现内容,超出屏幕部分的内容只须要经过手指滑动就能够移动到屏幕内了。mysql

 

另外ListView还有一个很是神奇的功能,我相信你们应该都体验过,即便在ListView中加载很是很是多的数据,好比达到成百上千条甚至更多,ListView都不会发生OOM或者崩溃,并且随着咱们手指滑动来浏览更多数据时,程序所占用的内存居然都不会跟着增加。那么ListView是怎么实现这么神奇的功能的呢?当初我就抱着学习的心态花了很长时间把ListView的源码通读了一遍,基本了解了它的工做原理,在感叹Google大神可以写出如此精妙代码的同时我也有所敬畏,由于ListView的代码量比较大,复杂度也很高,很难用文字表达清楚,因而我就放弃了把它写成一篇博客的想法。那么如今回想起来这件事我已经肠子都悔青了,由于没过几个月时间我就把当初梳理清晰的源码又忘的一干二净。因而如今我又从新定下心来再次把ListView的源码重读了一遍,那么此次我必定要把它写成一篇博客,分享给你们的同时也当成我本身的笔记吧。android

首先咱们先来看一下ListView的继承结构,以下图所示:算法

 

能够看到,ListView的继承结构仍是至关复杂的,它是直接继承自的AbsListView,而AbsListView有两个子实现类,一个是ListView,另外一个就是GridView,所以咱们从这一点就能够猜出来,ListView和GridView在工做原理和实现上都是有不少共同点的。而后AbsListView又继承自AdapterView,AdapterView继承自ViewGroup,后面就是咱们所熟知的了。先把ListView的继承结构了解一下,待会儿有助于咱们更加清晰地分析代码。sql

 

 

Adapter的做用

 

 

Adapter相信你们都不会陌生,咱们平时使用ListView的时候必定都会用到它。那么话说回来你们有没有仔细想过,为何须要Adapter这个东西呢?总感受正由于有了Adapter,ListView的使用变得要比其它控件复杂得多。那么这里咱们就先来学习一下Adapter到底起到了什么样的一个做用。数据库

 

其实说到底,控件就是为了交互和展现数据用的,只不过ListView更加特殊,它是为了展现不少不少数据用的,可是ListView只承担交互和展现工做而已,至于这些数据来自哪里,ListView是不关心的。所以,咱们能设想到的最基本的ListView工做模式就是要有一个ListView控件和一个数据源。数组

 

不过若是真的让ListView和数据源直接打交道的话,那ListView所要作的适配工做就很是繁杂了。由于数据源这个概念太模糊了,咱们只知道它包含了不少数据而已,至于这个数据源究竟是什么样类型,并无严格的定义,有多是数组,也有多是集合,甚至有多是数据库表中查询出来的游标。因此说若是ListView真的去为每一种数据源都进行适配操做的话,一是扩展性会比较差,内置了几种适配就只有几种适配,不能动态进行添加。二是超出了它自己应该负责的工做范围,再也不是仅仅承担交互和展现工做就能够了,这样ListView就会变得比较臃肿。缓存

 

那么显然android开发团队是不会容许这种事情发生的,因而就有了Adapter这样一个机制的出现。顾名思义,Adapter是适配器的意思,它在ListView和数据源之间起到了一个桥梁的做用,ListView并不会直接和数据源打交道,而是会借助Adapter这个桥梁来去访问真正的数据源,与以前不一样的是,Adapter的接口都是统一的,所以ListView不用再去担忧任何适配方面的问题。而Adapter又是一个接口(interface),它能够去实现各类各样的子类,每一个子类都能经过本身的逻辑来去完成特定的功能,以及与特定数据源的适配操做,好比说ArrayAdapter能够用于数组和List类型的数据源适配,SimpleCursorAdapter能够用于游标类型的数据源适配,这样就很是巧妙地把数据源适配困难的问题解决掉了,而且还拥有至关不错的扩展性。简单的原理示意图以下所示:布局

固然Adapter的做用不只仅只有数据源适配这一点,还有一个很是很是重要的方法也须要咱们在Adapter当中去重写,就是getView()方法,这个在下面的文章中还会详细讲到。学习

 

RecycleBin机制

 

那么在开始分析ListView的源码以前,还有一个东西是咱们提早须要了解的,就是RecycleBin机制,这个机制也是ListView可以实现成百上千条数据都不会OOM最重要的一个缘由。其实RecycleBin的代码并很少,只有300行左右,它是写在AbsListView中的一个内部类,因此全部继承自AbsListView的子类,也就是ListView和GridView,均可以使用这个机制。那咱们来看一下RecycleBin中的主要代码,以下所示:

/**
 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin
 * has two levels of storage: ActiveViews and ScrapViews. ActiveViews are
 * those views which were onscreen at the start of a layout. By
 * construction, they are displaying current information. At the end of
 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews
 * are old views that could potentially be used by the adapter to avoid
 * allocating views unnecessarily.
 * 
 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
 * @see android.widget.AbsListView.RecyclerListener
 */
class RecycleBin {
	private RecyclerListener mRecyclerListener;

	/**
	 * The position of the first view stored in mActiveViews.
	 */
	private int mFirstActivePosition;

	/**
	 * Views that were on screen at the start of layout. This array is
	 * populated at the start of layout, and at the end of layout all view
	 * in mActiveViews are moved to mScrapViews. Views in mActiveViews
	 * represent a contiguous range of Views, with position of the first
	 * view store in mFirstActivePosition.
	 */
	private View[] mActiveViews = new View[0];

	/**
	 * Unsorted views that can be used by the adapter as a convert view.
	 */
	private ArrayList<View>[] mScrapViews;

	private int mViewTypeCount;

	private ArrayList<View> mCurrentScrap;

	/**
	 * Fill ActiveViews with all of the children of the AbsListView.
	 * 
	 * @param childCount
	 *            The minimum number of views mActiveViews should hold
	 * @param firstActivePosition
	 *            The position of the first view that will be stored in
	 *            mActiveViews
	 */
	void fillActiveViews(int childCount, int firstActivePosition) {
		if (mActiveViews.length < childCount) {
			mActiveViews = new View[childCount];
		}
		mFirstActivePosition = firstActivePosition;
		final View[] activeViews = mActiveViews;
		for (int i = 0; i < childCount; i++) {
			View child = getChildAt(i);
			AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
			// Don't put header or footer views into the scrap heap
			if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
				// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in
				// active views.
				// However, we will NOT place them into scrap views.
				activeViews[i] = child;
			}
		}
	}

	/**
	 * Get the view corresponding to the specified position. The view will
	 * be removed from mActiveViews if it is found.
	 * 
	 * @param position
	 *            The position to look up in mActiveViews
	 * @return The view if it is found, null otherwise
	 */
	View getActiveView(int position) {
		int index = position - mFirstActivePosition;
		final View[] activeViews = mActiveViews;
		if (index >= 0 && index < activeViews.length) {
			final View match = activeViews[index];
			activeViews[index] = null;
			return match;
		}
		return null;
	}

	/**
	 * Put a view into the ScapViews list. These views are unordered.
	 * 
	 * @param scrap
	 *            The view to add
	 */
	void addScrapView(View scrap) {
		AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
		if (lp == null) {
			return;
		}
		// Don't put header or footer views or views that should be ignored
		// into the scrap heap
		int viewType = lp.viewType;
		if (!shouldRecycleViewType(viewType)) {
			if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
				removeDetachedView(scrap, false);
			}
			return;
		}
		if (mViewTypeCount == 1) {
			dispatchFinishTemporaryDetach(scrap);
			mCurrentScrap.add(scrap);
		} else {
			dispatchFinishTemporaryDetach(scrap);
			mScrapViews[viewType].add(scrap);
		}

		if (mRecyclerListener != null) {
			mRecyclerListener.onMovedToScrapHeap(scrap);
		}
	}

	/**
	 * @return A view from the ScrapViews collection. These are unordered.
	 */
	View getScrapView(int position) {
		ArrayList<View> scrapViews;
		if (mViewTypeCount == 1) {
			scrapViews = mCurrentScrap;
			int size = scrapViews.size();
			if (size > 0) {
				return scrapViews.remove(size - 1);
			} else {
				return null;
			}
		} else {
			int whichScrap = mAdapter.getItemViewType(position);
			if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
				scrapViews = mScrapViews[whichScrap];
				int size = scrapViews.size();
				if (size > 0) {
					return scrapViews.remove(size - 1);
				}
			}
		}
		return null;
	}

	public void setViewTypeCount(int viewTypeCount) {
		if (viewTypeCount < 1) {
			throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
		}
		// noinspection unchecked
		ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
		for (int i = 0; i < viewTypeCount; i++) {
			scrapViews[i] = new ArrayList<View>();
		}
		mViewTypeCount = viewTypeCount;
		mCurrentScrap = scrapViews[0];
		mScrapViews = scrapViews;
	}

}

 

这里的RecycleBin代码并不全,我只是把最主要的几个方法提了出来。那么咱们先来对这几个方法进行简单解读,这对后面分析ListView的工做原理将会有很大的帮助。

  • fillActiveViews() 这个方法接收两个参数,第一个参数表示要存储的view的数量,第二个参数表示ListView中第一个可见元素的position值。RecycleBin当中使用mActiveViews这个数组来存储View,调用这个方法后就会根据传入的参数来将ListView中的指定元素存储到mActiveViews数组当中。
  • getActiveView() 这个方法和fillActiveViews()是对应的,用于从mActiveViews数组当中获取数据。该方法接收一个position参数,表示元素在ListView当中的位置,方法内部会自动将position值转换成mActiveViews数组对应的下标值。须要注意的是,mActiveViews当中所存储的View,一旦被获取了以后就会从mActiveViews当中移除,下次获取一样位置的View将会返回null,也就是说mActiveViews不能被重复利用。
  • addScrapView() 用于将一个废弃的View进行缓存,该方法接收一个View参数,当有某个View肯定要废弃掉的时候(好比滚动出了屏幕),就应该调用这个方法来对View进行缓存,RecycleBin当中使用mScrapViews和mCurrentScrap这两个List来存储废弃View。
  • getScrapView 用于从废弃缓存中取出一个View,这些废弃缓存中的View是没有顺序可言的,所以getScrapView()方法中的算法也很是简单,就是直接从mCurrentScrap当中获取尾部的一个scrap view进行返回。
  • setViewTypeCount() 咱们都知道Adapter当中能够重写一个getViewTypeCount()来表示ListView中有几种类型的数据项,而setViewTypeCount()方法的做用就是为每种类型的数据项都单独启用一个RecycleBin缓存机制。实际上,getViewTypeCount()方法一般状况下使用的并非不少,因此咱们只要知道RecycleBin当中有这样一个功能就好了。

 

了解了RecycleBin中的主要方法以及它们的用处以后,下面就能够开始来分析ListView的工做原理了,这里我将仍是按照之前分析源码的方式来进行,即跟着主线执行流程来逐步阅读并点到即止,否则的话要是把ListView全部的代码都贴出来,那么本篇文章将会很长很长了。

 

第一次Layout

 

无论怎么说,ListView即便再特殊最终仍是继承自View的,所以它的执行流程还将会按照View的规则来执行,对于这方面不太熟悉的朋友能够参考我以前写的 Android视图绘制流程彻底解析,带你一步步深刻了解View(二) 。

 

View的执行流程无非就分为三步,onMeasure()用于测量View的大小,onLayout()用于肯定View的布局,onDraw()用于将View绘制到界面上。而在ListView当中,onMeasure()并无什么特殊的地方,由于它终归是一个View,占用的空间最多而且一般也就是整个屏幕。onDraw()在ListView当中也没有什么意义,由于ListView自己并不负责绘制,而是由ListView当中的子元素来进行绘制的。那么ListView大部分的神奇功能其实都是在onLayout()方法中进行的了,所以咱们本篇文章也是主要分析的这个方法里的内容。

 

若是你到ListView源码中去找一找,你会发现ListView中是没有onLayout()这个方法的,这是由于这个方法是在ListView的父类AbsListView中实现的,代码以下所示:

相关文章
相关标签/搜索