首页
登录 | 注册

ListView.setAdapter流程以及缓存机制

1. ListView数据显示的流程分析

我们往往放入数据就直接这样:

listView.adapter=adapter

实现机制到底是怎么样的呢,那么我们来看看listView的setAdapter方法是怎么实现的吧:

ListView.java
 public void setAdapter(ListAdapter adapter) {
 
    ....
     requestLayout();
    ....
    
 }
 
 AbsListView.java---ListView父类
     public void requestLayout() {
        if (!mBlockLayoutRequests && !mInLayout) {
            //最后还是View的requestLayout
            super.requestLayout();
        }
    }

这不就是View的绘制过程嘛。我们知道ListView它是一个ViewGroup,requestLayout方法,就是View的OnMeasure,OnLayout,OnDraw方法,对于OnMeasure方法,并没有什么特别的地方,终归是个View,占用的空间最多也就整个屏幕,对于OnDraw,ListView本身不参与绘制,都是由listView当中的子元素来绘制的,那我们来看看OnLayout方法的具体实现吧,这段比较长,得跟着分析去看代码:

AbsListView.java

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    ......
    //是空实现,我们得看ListView的实现
     layoutChildren();
     
     .......
}

ListView.java:

//ListView的layoutChildren很长很长,我们就看关键的一部分吧
protected void layoutChildren() {
    .......
     // Pull all children into the RecycleBin.
     //看这RecycleBin注释就知道,这块跟缓存有很大的关系,我们稍后再看缓存这块内容
    final RecycleBin recycleBin = mRecycler; //-------------1
    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();//---------------2
    .....
    if (childCount == 0) {//------------3
        .....
        //会调用fillDown方法
        sel = fillFromTop(childrenTop);
        .....
}


//nextTop是第一个子元素顶部距离整个ListView顶部的像素值
//pos则是刚刚传入的mFirstPosition的值
    private View fillDown(int pos, int nextTop) {
        View selectedView = null;
        //end是ListView底部减去顶部所得的像素值
        int end = (mBottom - mTop);
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            end -= mListPadding.bottom;
        }
        //子元素已经超出当前屏幕了,或者pos大于等于mItemCount时,也就是所有Adapter中的元素都被遍历结束了
        while (nextTop < end && pos < mItemCount) {
            // is this the selected item?
            boolean selected = pos == mSelectedPosition;
            View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);

            nextTop = child.getBottom() + mDividerHeight;
            if (selected) {
                selectedView = child;
            }
            pos++;
        }

        setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
        return selectedView;
    }

    
    //先从缓存中获取View,如果没有则obtainView
    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        .....
        //这个方法是在父类实现的
        final View child = obtainView(position, mIsScrap);
         // This needs to be positioned and measured.
         //这方法就是绘制child
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
        
        return child;
    }
    
    
AbsListView.java:

    View obtainView(int position, boolean[] outMetadata) {
        .....
        //这里这个方法很熟悉吧
        final View child = mAdapter.getView(position, scrapView, this);
        
        ......
        
        return child;
    }  
    

总结

我们再来回顾下整个流程:ListView.setAdapter会调用requestLayout方法,这个方法其实就是View的绘制过程,由于ListView是个GroupView,只需看onLayout方法,ListView使用onLayoutChildren来实现onLayout方法的。在这个方法中,根据Adapter里面的count值来循环“生成”子View,每生成一个子View就绘制这个view

2. 缓存

那我们来讲讲ListView缓存机制吧,我们先来看看RecycleBin这个类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.

看这注释知道这个类主要是为了有两级缓存 ActiveViews and ScrapViews, mCurrentScrap

 /**
     * 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 ArrayList<View> mCurrentScrap;
private ArrayList<View> mSkippedScrap;

在上述的流程中第一次出现RecycleBin这个对象的地方就是layoutChildren方法中,接下来我们来看看RecycleBin的addScrapView以及fillActiveViews方法:

ListView.java:
 protected void layoutChildren() {
    ......
 
    if (dataChanged) {
        for (int i = 0; i < childCount; i++) {
            recycleBin.addScrapView(getChildAt(i), firstPosition+i);
        }
    } else {
        recycleBin.fillActiveViews(childCount, firstPosition);
    }
    
     ......
}



AbsListView&RecycleBin.java:

 void addScrapView(View scrap, int position) {
    ........
    //这里childView缓存
      if (mViewTypeCount == 1) {
          mCurrentScrap.add(scrap);
      } else {
          mScrapViews[viewType].add(scrap);
      }
    .......
  }

这个方法可以看出mScrapViews和mCurrentScrap算是同级的缓存了,这两个的区别就是ViewType。然后再看看 recycleBin的fillActiveViews:

 /**
         * 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
         */
         //firstActivePosition表示第一个可见元素的position值
            void fillActiveViews(int childCount, int firstActivePosition) {
            if (mActiveViews.length < childCount) {
                mActiveViews = new View[childCount];
            }
            mFirstActivePosition = firstActivePosition;

            //noinspection MismatchedReadAndWriteOfArray
            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
                //headView和footView不能放入
                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;
                    // Remember the position so that setupChild() doesn't reset state.
                    lp.scrappedFromPosition = firstActivePosition + i;
                }
            }
        }

mActiveViews这个缓存跟上述的mScrapViews和mCurrentScrap不同的是没有区别ViewType

接下来我们看看makeAndAddView方法。根据上面分析整个流程后,我们知道,在这个方法里面,先从缓存中取出View,如果没有才新建一个View:

    /**
     * Obtains the view and adds 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.
     *
     * @param position logical position in the list
     * @param y top or bottom edge of the view to add
     * @param flow {@code true} to align top edge to y, {@code false} to align
     *             bottom edge to y
     * @param childrenLeft left edge where children should be positioned
     * @param selected {@code true} if the position is selected, {@code false}
     *                 otherwise
     * @return the view that was added
     */
    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        if (!mDataChanged) {
            // Try to use an existing view for this position.
            final View activeView = mRecycler.getActiveView(position);
            if (activeView != null) {
                // Found it. We're reusing an existing child, so it just needs
                // to be positioned like a scrap view.
                //Adds a view as a child and make sure it is measured (if necessary) and positioned properly.主要就是加入子元素,并测绘
                setupChild(activeView, position, y, flow, childrenLeft, selected, true);
                return activeView;
            }
        }
        
        // Make a new view for this position, or convert an unused view if
        // possible.
        final View child = obtainView(position, mIsScrap);
        
        // This needs to be positioned and measured.
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
        return child;
    }

在这个方法中,我们看到了getActiveView方法:


        /**
         * 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;
        }

值得注意的是,它会把相应的位置的View全部取出来,然后置为null,我们着重 跟踪 obtainView方法内部是如何实现的:

    View obtainView(int position, boolean[] outMetadata) {
        .........
        
        // Check whether we have a transient state view. Attempt to re-bind the
        // data and discard the view if we fail.
        //这里是与View的hasTransientState()方法有关,不需要深入了解
        //其目的是通过参数position的值来获取刚消失的View对象
        final View transientView = mRecycler.getTransientStateView(position);
        ........
        
        final View scrapView = mRecycler.getScrapView(position);
        ///----------------------1
        final View child = mAdapter.getView(position, scrapView, this);
        if (scrapView != null) {
            if (child != scrapView) {
                // Failed to re-bind the data, return scrap to the heap.
                mRecycler.addScrapView(scrapView, position);
            } else if (child.isTemporarilyDetached()) {
                outMetadata[0] = true;

                // Finish the temporary detach started in addScrapView().
                child.dispatchFinishTemporaryDetach();
            }
        }
        ......
        return child;
    }

    View getScrapView(int position) {
    //这里比较熟悉了,获取View的类型
        final int whichScrap = mAdapter.getItemViewType(position);
        if (whichScrap < 0) {
           return null;
         }
        if (mViewTypeCount == 1) {
        //也就是从数组里面取出View,然后把数组相应的位置的值置为null
            return retrieveFromScrap(mCurrentScrap, position);
         } else if (whichScrap < mScrapViews.length) {
            return retrieveFromScrap(mScrapViews[whichScrap], position);
        }
            return null;
        }

很明显就是从那mCurrentScrap|mScrapViews数组中取出相应的View。值得注意的是标记1的地方,这就是我们经常重写getView方法需要判空的原因了,传入的scrapView是缓存中的View,它也有可能会为null的

总结

结合这两种缓存的方式,先从数组mActiveViews中取出缓存的View,取出来的同时还会移除掉这个缓存View,如果没有,则进行下一个缓存的读取,就算这个缓存中读取到了View,但是还是需要通过Adapter.getView()方法去封装View。



2020 jeepxie.net webmaster#jeepxie.net
10 q. 0.009 s.
京ICP备10005923号