最初的分析文档为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动画播放等状况中。