標籤:重寫 結構 .com ram 分享 .net 擷取資料 add sql
原文地址:http://blog.csdn.net/guolin_blog/article/details/44996879
在Android所有常用的原生控制項當中,用法最複雜的應該就是ListView了,它專門用於處理那種內容元素很多,手機螢幕無法展示出所有內容的情況。ListView可以使用列表的形式來展示內容,超出螢幕部分的內容只需要通過手指滑動就可以移動到螢幕內了。
另外ListView還有一個非常神奇的功能,我相信大家應該都體驗過,即使在ListView中載入非常非常多的資料,比如達到成百上千條甚至更多,ListView都不會發生OOM或者崩潰,而且隨著我們手指滑動來瀏覽更多資料時,程式所佔用的記憶體竟然都不會跟著增長。那麼ListView是怎麼實現這麼神奇的功能的呢?當初我就抱著學習的心態花了很長時間把ListView的源碼通讀了一遍,基本瞭解了它的工作原理,在感歎Google大神能夠寫出如此精妙代碼的同時我也有所敬畏,因為ListView的代碼量比較大,複雜度也很高,很難用文字表達清楚,於是我就放棄了把它寫成一篇部落格的想法。那麼現在回想起來這件事我已經腸子都悔青了,因為沒過幾個月時間我就把當初梳理清晰的源碼又忘的一乾二淨。於是現在我又重新定下心來再次把ListView的源碼重讀了一遍,那麼這次我一定要把它寫成一篇部落格,分享給大家的同時也當成我自己的筆記吧。
首先我們先來看一下ListView的繼承結構,如所示:
可以看到,ListView的繼承結構還是相當複雜的,它是直接繼承自的AbsListView,而AbsListView有兩個子實作類別,一個是ListView,另一個就是GridView,因此我們從這一點就可以猜出來,ListView和GridView在工作原理和實現上都是有很多共同點的。然後AbsListView又繼承自AdapterView,AdapterView繼承自ViewGroup,後面就是我們所熟知的了。先把ListView的繼承結構瞭解一下,待會兒有助於我們更加清晰地分析代碼。
Adapter的作用
Adapter相信大家都不會陌生,我們平時使用ListView的時候一定都會用到它。那麼話說回來大家有沒有仔細想過,為什麼需要Adapter這個東西呢?總感覺正因為有了Adapter,ListView的使用變得要比其它控制項複雜得多。那麼這裡我們就先來學習一下Adapter到底起到了什麼樣的一個作用。
其實說到底,控制項就是為了互動和展示資料用的,只不過ListView更加特殊,它是為了展示很多很多資料用的,但是ListView只承擔互動和展示工作而已,至於這些資料來自哪裡,ListView是不關心的。因此,我們能設想到的最基本的ListView工作模式就是要有一個ListView控制項和一個資料來源。
不過如果真的讓ListView和資料來源直接打交道的話,那ListView所要做的適配工作就非常繁雜了。因為資料來源這個概念太模糊了,我們只知道它包含了很多資料而已,至於這個資料來源到底是什麼樣類型,並沒有嚴格的定義,有可能是數組,也有可能是集合,甚至有可能是資料庫表中查詢出來的遊標。所以說如果ListView真的去為每一種資料來源都進行適配操作的話,一是擴充性會比較差,內建了幾種適配就只有幾種適配,不能動態進行添加。二是超出了它本身應該負責的工作範圍,不再是僅僅承擔互動和展示工作就可以了,這樣ListView就會變得比較臃腫。
那麼顯然androidTeam Dev是不會允許這種事情發生的,於是就有了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 heapif (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 heapint 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 uncheckedArrayList<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中實現的,代碼如下所示:
Android ListView工作原理完全解析(轉自 郭霖老師部落格)