原文地址: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相信你们都不会陌生,咱们平时使用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()方法,这个在下面的文章中还会详细讲到。学习
那么在开始分析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的工做原理将会有很大的帮助。
了解了RecycleBin中的主要方法以及它们的用处以后,下面就能够开始来分析ListView的工做原理了,这里我将仍是按照之前分析源码的方式来进行,即跟着主线执行流程来逐步阅读并点到即止,否则的话要是把ListView全部的代码都贴出来,那么本篇文章将会很长很长了。
无论怎么说,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中实现的,代码以下所示: