最初的分析文檔爲word,該文檔是直接從word文檔發佈,佈局未作詳細調整,湊合看吧。java
所用源碼版本爲最新的Android 4.4.2(API 19)。更新中…… android
2.1 RecycleBin變量 4eclipse
首先理清listview的層級關係,
使用Google Online Draw 畫出繼承關係圖以下:
圖中單獨畫出Scrollview是爲了說明該ViewGroup並無自帶回收機制,若是要是Scrollview顯示大量view,須要手動作處理。
重要的類有三個:Listview、AbsListView、AdapterView。各個類的大小以下:
從Listview開始, ListView的初始化ListVIew.onLayout過程與普通視圖的layout過程不一樣,流程圖以下。從左向右,從上向下。
視圖的建立過程的都會執行的三個步驟: onMeasure, onLayout, onDraw
圖中能夠看出重要的類有三個:Listview、AbsListView、AdapterView。主要的回收類RecycleBin位於AbsListView中。
位於AbsListView中,6466-6900行。
AbsListView的源碼中能夠看到有個RecycleBin 對象mRecycler。(317行, The data set used to store unused views that should be reused during the next layout to avoid creating new ones. 用於存儲不用的view,以便在下個layout中使用來避免建立新的。)註釋說明:
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.
大意是使用兩級view來進行回收:
:激活view,當期顯示在屏幕上的激活的view。
:廢棄view,被刪除的ActiveView會被自動加入ScrapView。
而後看看RecycleBin內部的重要的的變量和方法:
: 當發生View回收時,mRecyclerListener如有註冊,則會通知給註冊者.RecyclerListener接口只有一個函數onMovedToScrapHeap,指明某個view被回收到了scrap heap. 該view再也不被顯示,任何相關的昂貴資源應該被丟棄。該函數是處理回收時view中的資源釋放。
:The position of the first view stored in mActiveViews.存儲在mActiveViews中的第一個view的位置,即getFirstVisiblePosition。
: 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.佈局開始時屏幕顯示的view,這個數組會在佈局開始時填充,佈局結束後全部view被移至mScrapViews。
:ArrayList<View>[] Unsorted views that can be used by the adapter as a convert view.能夠被適配器用做convert view的無序view數組。 這個ArrayList就是adapter中getView方法中的參數convertView的來源。注意:這裏是一個數組,由於若是adapter中數據有多種類型,那麼就會有多個ScrapViews。
:view類型總數,列表中可能有多種數據類型,好比內容數據和分割符。
:跟mScrapViews的卻別是,mScrapViews是個隊列數組,ArrayList<View>[]類型,數組長度爲mViewTypeCount,而默認ViewTypeCount = 1的狀況下mCurrentScrap=mScrapViews[0]。
下面三個參數分別對應addScrapView中scrapHasTransientState的三個狀況
():爲每一個子類調用forceLayout()。將mScrapView中回收回來的View設置同樣標誌,在下次被複用到ListView中時,告訴viewroot從新layout該view。forceLayout()方法只是設置標誌,並不會通知其parent來從新layout。
():判斷給定的view的viewType指明是否能夠回收回。viewType < 0能夠回收。指定忽略的( ITEM_VIEW_TYPE_IGNORE = -1),或者是 HeaderView / (FootViewITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2)是不被回收的。若有特殊須要能夠將本身定義的viewType設置爲-1,不然,將會浪費內存,致使OOM。
() :Clears the scrap heap.清空廢棄view堆,並將這些View從窗口中Detach。
(int childCount, int firstActivePosition):Fill ActiveViews with all of the children of the AbsListView. childCount:The minimum number of views mActiveViews should hold. firstActivePosition:The position of the first view that will be stored in mActiveViews.用AbsListView.的全部子view填充ActiveViews,其中childCount是mActiveViews應該保存的最少的view數,firstActivePosition是mActiveViews中存儲的首個view的位置。從代碼看該方法的處理邏輯爲將當前AbsListView的0-childCount個子類中的非header、footer類添加到mActiveViews數組中。當Adapter中的數據個數未發生變化時,此時用戶可能只是滾動,或點擊等操做,ListView中item的個數會發生變化,所以,須要將可視的item加入到mActiveView中來管理。
(int position):Get the view corresponding to the specified position. The view will be removed from mActiveViews if it is found. 獲取mActiveViews中指定位置的view,若是找到會將該view從mActiveViews中移除。position是adapter中的絕對下標值,mFirstActivePosition前面說過了,是當前可視區域的下標值,對應在adapter中的絕對值,若是找到,則返回找到的View,並將mActiveView對應的位置設置爲null。
:Dump any currently saved views with transient state.清掉當前處於transient(轉換)狀態的全部保存的view。內部爲mTransientStateViews和mTransientStateViewsById的clear()調用。
(View scrap, int position):將view放入scrapview list中。If the list data hasn't changed or the adapter has stable IDs, views with transient state will be preserved for later retrieval. scrap:要添加的view。Position:view在父類中的位置。放入時位置賦給scrappedFromPosition 。有transient狀態的view不會被scrap(廢棄),會被加入mSkippedScrap。
就是將移出可視區域的view,設置它的scrappedFromPosition,而後從窗口中detach該view,並根據viewType加入到mScrapView中。
該方法會調用mRecyclerListener 接口的函數onMovedToScrapHeap(6734)
if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } |
mRecyclerListener的設置可經過AbsListView的setRecyclerListener方法。
當view被回收準備再利用的時候設置要通知的監聽器, 能夠用來釋放跟view有關的資源。這點彷佛頗有用。
public void setRecyclerListener(RecyclerListener listener) { mRecycler.mRecyclerListener = listener; } |
(int position) :A view from the ScrapViews collection. These are unordered.該方法中調用了retrieveFromScrap(ArrayList<View> scrapViews, int position)。
(ArrayList<View> scrapViews, int position):無註釋。(6902)該方法屬於AbsListView。
int size = scrapViews.size(); if (size > 0) { // See if we still have a view for this position. for (int i=0; i<size; i++) { View view = scrapViews.get(i); if (((AbsListView.LayoutParams)view.getLayoutParams()) .scrappedFromPosition == position) { scrapViews.remove(i); return view; } } return scrapViews.remove(size - 1); } else { return null; } |
其中scrappedFromPosition :The position the view was removed from when pulled out of the scrap heap.(6412)根據position,從mScrapView中找:
1. 若是有view.scrappedFromPosition = position的,直接返回該view;
2. 不然返回mScrapView中最後一個;
3. 若是緩存中沒有view,則返回null;
a. 第三種狀況,這個最簡單:
一開始,listview穩定後,顯示N個,此時mScrapView中是沒有緩存view的,當咱們向上滾動一小段距離(第一個此時仍顯示部分),新的view將會顯示,此時listview會調用Adapter.getView,可是緩存中沒有,所以convertView是null,因此,咱們得分配一塊內存來建立新的convertView;
b. 第二種狀況:
在a中,咱們繼續向上滾動,直接第一個view徹底移出屏幕(假設沒有新的item),此時,第一個view就會被detach,並被加入到mScrapView中;而後,咱們還繼續向上滾動,直接後面又將要顯示新的item view時,此時,系統會從mScrapView中找position對應的View,顯然,是找不到的,則將從mScrapView中,取最後一個緩存的view傳遞給convertView;
c. 第一種狀況:
緊接着在b中,第一個被徹底移出,加入到mScrapView中,且沒有新增的item到listview中,此時,緩存中就只有第一個view;而後,我此時向下滑動,則以前的第一個item,將被顯示出來,此時,從緩存中查找position對應的view有沒有,固然,確定是找到了,就直接返回了。
:Finish the removal of any views that skipped the scrap heap.清空mSkippedScrap。
():Move all views remaining in mActiveViews to mScrapViews.將mActiveViews 中剩餘的view放入mScrapViews。實際上就是將mActiveView中未使用的view回收(由於,此時已經移出可視區域了)。會調用mRecyclerListener.onMovedToScrapHeap(scrap);
():確保mScrapViews 的數目不會超過mActiveViews的數目 (This can happen if an adapter does not recycle its views)。mScrapView中每一個ScrapView數組大小不該該超過mActiveView的大小,若是超過,系統認爲程序並無複用convertView,而是每次都是建立一個新的view,爲了不產生大量的閒置內存且增長OOM的風險,系統會在每次回收後,去檢查一下,將超過的部分釋放掉,節約內存下降OOM風險。
(List<View> views):Puts all views in the scrap heap into the supplied list.將mScrapView中全部的緩存view所有添加到指定的view list中,只看到有AbsListView.reclaimViews有調用到,但沒有其它方法使用這個函數,可能在特殊狀況下會使用到,但目前從framework中,看不出來。
(int color):Updates the cache color hint of all known views.更新view的緩存顏色提示setDrawingCacheBackgroundColor。爲全部的view繪置它們的背景色。
1479-1729
1583-當數據發生改變的時候,把當前的view放到scrapviews裏面,不然標記爲activeViews。
// Pull all children into the RecycleBin. // These views will be reused if possible final int firstPosition = mFirstPosition; final RecycleBin recycleBin = mRecycler; if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); } }else { recycleBin.fillActiveViews(childCount, firstPosition); } // Clear out old views detachAllViewsFromParent(); recycleBin.removeSkippedScrap();//移除全部old views ...... // Flush any cached views that did not get reused above recycleBin.scrapActiveViews();//刷新緩存,將當前的ActiveVies 移動到 ScrapViews。 |
dataChanged,從單詞的意思咱們就能夠,這裏的優化規則就是基於數據是否有變化,mDataChanged在makeAndAddView(見下文)中有使用。
step1:若是數據發生變化,就將全部view加入到mScrapView中,不然,將全部view放到mActiveView中;
step2:添加view到listview中;
step3:回收mActiveView中的未使用的view到mScrapView中;
注:在step1中,若是是addScrapView,則全部的view將會detach,若是是fillActiveViews,則不會detach,只有在step3中,未用到的view纔會detach。
(int position, int y, boolean flow, int childrenLeft,boolean selected)(1772)
Obtain the view and add it to our list of children. The view can be made fresh, converted from an unused view, or used as is if it was in the recycle bin.
View child; if (!mDataChanged) {// 數據沒有更新時,使用之前的view // Try to use an existing view for this position child = mRecycler.getActiveView(position); if (child != null) { // Found it -- we're using an existing child // This just needs to be positioned // 對複用的View針對當前須要進行配置。定位而且添加這個view到ViewGrop中的children列表,從回收站獲取的視圖不須要measure,因此最後一個參數爲true setupChild(child, position, y, flow, childrenLeft, selected, true); return child; } } // Make a new view for this position, or convert an unused view if possible // 建立或者重用視圖 child = obtainView(position, mIsScrap); // This needs to be positioned and measured setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; |
(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled)(1812)
Add a view as a child and make sure it is measured (if necessary) and positioned properly.
(ListAdapter adapter) (457)
Sets the data behind this ListView. The adapter passed to this method may be wrapped by a WrapperListAdapter, depending on the ListView features currently in use. For instance, adding headers and/or footers will cause the adapter to be wrapped.
if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver);//移除了與當前listview的adapter綁定數據集觀察者DataSetObserver } resetList();//重置listview,主要是清除全部的view,改變header、footer的狀態 mRecycler.clear();//清除掉RecycleBin對象mRecycler中全部緩存的view,RecycleBin後面着重介紹,主要是關係到Listview中item的重用機制,它是AbsListview的一個內部類 if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {//判斷是否有headerview和footview mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); } else { mAdapter = adapter; } mOldSelectedPosition = INVALID_POSITION; mOldSelectedRowId = INVALID_ROW_ID; // AbsListView#setAdapter will update choice mode states. super.setAdapter(adapter);
if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; mItemCount = mAdapter.getCount(); checkFocus(); mDataSetObserver = new AdapterDataSetObserver();//註冊headerview的觀察者 mAdapter.registerDataSetObserver(mDataSetObserver);//在RecycleBin對象mRecycler記錄下item類型的數量 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); int position; if (mStackFromBottom) { position = lookForSelectablePosition(mItemCount - 1, false); } else { position = lookForSelectablePosition(0, true); } setSelectedPositionInt(position);//AdapterView中的方法,記錄當前的position setNextSelectedPositionInt(position);//AdapterView中的方法,記錄下一個position if (mItemCount == 0) { // Nothing selected checkSelectionChanged(); } } else { mAreAllItemsSelectable = true; checkFocus(); // Nothing selected checkSelectionChanged(); } requestLayout(); |
(int amount)(3012-3082)
對子view滑動必定距離,添加view到底部或者移除頂部的不可見view。從註釋看,不可見的item 的自動移除是在scrollListItemsBy中進行的。
private void scrollListItemsBy(int amount) { offsetChildrenTopAndBottom(amount); final int listBottom = getHeight() - mListPadding.bottom;//獲取listview最底部位置 final int listTop = mListPadding.top; //獲取listview最頂部位置 final AbsListView.RecycleBin recycleBin = mRecycler; if (amount < 0) { // shifted items up // may need to pan views into the bottom space int numChildren = getChildCount(); View last = getChildAt(numChildren - 1); while (last.getBottom() < listBottom) {//最後的view高於底部時添加下一個view final int lastVisiblePosition = mFirstPosition + numChildren - 1; if (lastVisiblePosition < mItemCount - 1) { last = addViewBelow(last, lastVisiblePosition); numChildren++; } else { break; } } // may have brought in the last child of the list that is skinnier // than the fading edge, thereby leaving space at the end. need // to shift back if (last.getBottom() < listBottom) {//到達最後一個view offsetChildrenTopAndBottom(listBottom - last.getBottom()); } // top views may be panned off screen View first = getChildAt(0); while (first.getBottom() < listTop) {//頂部view移除屏幕時 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams(); if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) { recycleBin.addScrapView(first, mFirstPosition); //回收view } detachViewFromParent(first); //從父類中移除 first = getChildAt(0); //這行好像沒用啊。。。。 mFirstPosition++; } } else { // shifted items down View first = getChildAt(0); // may need to pan views into top while ((first.getTop() > listTop) && (mFirstPosition > 0)) {//頂部view上部有空間時添加view。 first = addViewAbove(first, mFirstPosition); mFirstPosition--; } // may have brought the very first child of the list in too far and // need to shift it back if (first.getTop() > listTop) {//到達第一個view offsetChildrenTopAndBottom(listTop - first.getTop()); } int lastIndex = getChildCount() - 1; View last = getChildAt(lastIndex); // bottom view may be panned off screen while (last.getTop() > listBottom) {//底部view移除屏幕的狀況 AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams(); if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) { recycleBin.addScrapView(last, mFirstPosition+lastIndex); } detachViewFromParent(last); last = getChildAt(--lastIndex); } } } |
從以上代碼能夠看出,Android中view回收的計算是其父view中再也不顯示的,若是scrollview中包含了一個wrap_content屬性的listview,裏面的內容並不會有任何回收,引發listview 的getheight函數獲取的是一個足以顯示全部內容的高度。
(int position, boolean[] isScrap)(2227)
Get a view and have it show the data associated with the specified position. 當這個方法被調用時,說明Recycle bin中的view已經不可用了,那麼,如今惟一的方法就是,convert一個老的view,或者構造一個新的view。
position: 要顯示的位置
isScrap: 是個boolean數組, 若是view從scrap heap獲取,isScrap [0]爲true,不然爲false。
isScrap[0] = false; View scrapView; scrapView = mRecycler.getTransientStateView(position); if (scrapView == null) { // 查看回收站中是否有廢棄無用的View,若是有,則使用它,無需New View。 scrapView = mRecycler.getScrapView(position); } View child; if (scrapView != null) { //此時說明能夠從回收站中從新使用scrapView。 child = mAdapter.getView(position, scrapView, this); if(child.getImportantForAccessibility()== IMPORTANT_FOR_ACCESSIBILITY_AUTO) { child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } if (child != scrapView) { //若是重用的scrapView和adapter得到的view是不同的,將scrapView進行回收 mRecycler.addScrapView(scrapView, position);// scrapView 仍然放入回收站 if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } } else { //若是重用的view和adapter得到的view是同樣的,將isScrap[0]值爲true,不然默認爲false isScrap[0] = true; // Clear any system-managed transient state so that we can // recycle this view and bind it to different data. if (child.isAccessibilityFocused()) { child.clearAccessibilityFocus(); } child.dispatchFinishTemporaryDetach(); } }else {//回收站中沒有拿到數據,就只可以本身去inflate一個xml佈局文件,或者new一個view child = mAdapter.getView(position, null, this); //當getview中傳入的 converView=null的時候會在getView的方法中進行新建這個view if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } } |
(int deltaY, int incrementalDeltaY)(4991)
監視滑動動做
deltaY: Amount to offset mMotionView. This is the accumulated delta since the motion began. 正數表示向下滑動。
incrementalDeltaY :Change in deltaY from the previous event.
....... // 滾動時,不在可見範圍內的item放入回收站。。。。。。。 if (down) { int top = -incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { top += listPadding.top; } for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getBottom() >= top) { break; } else { count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. if (child.isAccessibilityFocused()) { child.clearAccessibilityFocus(); } mRecycler.addScrapView(child, position);//放入回收站 } } } } else { int bottom = getHeight() - incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { bottom -= listPadding.bottom; } for (int i = childCount - 1; i >= 0; i--) { final View child = getChildAt(i); if (child.getTop() <= bottom) { break; } else { start = i; count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. if (child.isAccessibilityFocused()) { child.clearAccessibilityFocus(); } mRecycler.addScrapView(child, position);//放入回收站 } } } } |
在listview中當有多種viewtype的時候,在adapter中繼承設置getItemViewType方法能夠更有效率 。示例以下:
....... private static final int TYPE_ITEM = 0; private static final int TYPE_SEPARATOR = 1; private static final int TYPE_MAX_COUNT = TYPE_SEPARATOR + 1;
@Override public int getItemViewType(int position) { return mSeparatorsSet.contains(position) ? TYPE_SEPARATOR : TYPE_ITEM; } @Override public int getViewTypeCount() { return TYPE_MAX_COUNT; }
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; int type = getItemViewType(position); if (convertView == null) { holder = new ViewHolder(); switch (type) { case TYPE_ITEM: convertView = mInflater.inflate(R.layout.item1, null); holder.textView = (TextView)convertView.findViewById......; break; case TYPE_SEPARATOR: convertView = mInflater.inflate(R.layout.item2, null); holder.textView = (TextView)convertView.findViewById......; break; } convertView.setTag(holder); } else { holder = (ViewHolder)convertView.getTag(); } ........ } |
若是實現了RecyclerListener接口,當一個View因爲ListView的滑動被系統回收到RecycleBin的mScrapViews數組時,會調用RecyclerListener中的onMovedToScrapHeap(View view)方法。RecycleBin至關於一個臨時存儲不須要顯示的那部分Views的對象,隨着列表滑動,這些Views須要顯示出來的時候,他們就被從RecycleBin中拿了出來,RecycleBin自己並不對mScrapViews中的對象作回收操做。
因而在工程裏,爲ListView添加RecyclerListener接口,並在onMovedToScrapHeap方法中釋放ListItem包含的Bitmap資源,這樣能夠極大的減小內存佔用。
用來標記這個view的瞬時狀態,用來告訴app無需關心其保存和恢復。從註釋看,這種具備瞬時狀態的view,用於在view動畫播放等狀況中。