安卓初学者的博客

踽踽而行的安卓应用开发学习者

安卓开发学习之ListView缓存策略中常见的方法

背景

在阅读ListView的测绘流程的过程中,发现ListView很多地方都用到了缓存技术,这主要是由它的父类AbsListView的内部类RecycleBin实现的,我整理了一下测绘过程中里面常用的方法,以备日后查看


fillActiveViews

这个方法是用来把当前ListView显示的全部内容缓存到mActiveViews中,代码如下

        void fillActiveViews(int childCount, int firstActivePosition) {
            if (mActiveViews.length < childCount) {
                mActiveViews = new View[childCount];
            }
            mFirstActivePosition = firstActivePosition; // listView中第一个完整显示的子view的位置

            //noinspection MismatchedReadAndWriteOfArray
            final View[] activeViews = mActiveViews;
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
                // header和footer不缓存到这里
                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; // 把子view添加进mActiveViews中

                    // Remember the position so that setupChild() doesn't reset state.
                    // 更新lp.scrappedFromPosition
                    lp.scrappedFromPosition = firstActivePosition + i;
                }
            }
        }


removeSkippedScrap

这个方法在adapter.getItemViewType = 1时是个摆设,代码如下

        void removeSkippedScrap() {
            // adapter.getItemViewType() == 1 的话,这个方法就是摆设
            if (mSkippedScrap == null) {
                return;
            }
            final int count = mSkippedScrap.size();
            for (int i = 0; i < count; i++) {
                removeDetachedView(mSkippedScrap.get(i), false);
            }
            mSkippedScrap.clear();
        }


getActiveView

用来从mActiveViews缓存中获取view,代码如下

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


getScrapView

用来从mCurrentScrap或mScrapView中获取view,代码如下

        View getScrapView(int position) {
            final int whichScrap = mAdapter.getItemViewType(position);
            if (whichScrap < 0) {
                return null;
            }
            if (mViewTypeCount == 1) { // 最简单的自定义adapter走的是这一步
                return retrieveFromScrap(mCurrentScrap, position);
            } else if (whichScrap < mScrapViews.length) {
                return retrieveFromScrap(mScrapViews[whichScrap], position);
            }
            return null;
        }


addScrapView

把view缓存到mCurrentScrap或mScrapViews中,代码如下

        void addScrapView(View scrap, int position) {
            final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
            if (lp == null) {
                // Can't recycle, but we don't know anything about the view.
                // Ignore it completely.
                return;
            }

            lp.scrappedFromPosition = position;

            // Remove but don't scrap header or footer views, or views that
            // should otherwise not be recycled.
            final int viewType = lp.viewType;
            if (!shouldRecycleViewType(viewType)) { // 判断viewType < 0,一般viewType就是0,进不来
                // Can't recycle. If it's not a header or footer, which have
                // special handling and should be ignored, then skip the scrap
                // heap and we'll fully detach the view later.
                if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // header和footer要直接和list分离,而不是进入回收站
                    getSkippedScrap().add(scrap); // 加入另一个数组列表mScrapViews中
                }
                return;
            }

            scrap.dispatchStartTemporaryDetach(); // 设置标志位,清空view的回调

            // The the accessibility state of the view may change while temporary
            // detached and we do not allow detached views to fire accessibility
            // events. So we are announcing that the subtree changed giving a chance
            // to clients holding on to a view in this subtree to refresh it.
            notifyViewAccessibilityStateChangedIfNeeded(
                    AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);

            // Don't scrap views that have transient state.
            final boolean scrapHasTransientState = scrap.hasTransientState();
            if (scrapHasTransientState) { // 如果要回收的view是短暂状态的view(有标志位标记,一般都不是)
                .. // 忽略
            } else {
                clearScrapForRebind(scrap);
                if (mViewTypeCount == 1) {
                    mCurrentScrap.add(scrap); // 一般就这种情况,直接add进数组列表中去
                } else {
                    mScrapViews[viewType].add(scrap);
                }

                if (mRecyclerListener != null) {
                    mRecyclerListener.onMovedToScrapHeap(scrap); // 通知回调
                }
            }
        }


getSkippedScrap

获取mSkippedScrap,代码如下

        private ArrayList<View> getSkippedScrap() {
            if (mSkippedScrap == null) {
                mSkippedScrap = new ArrayList<>();
            }
            return mSkippedScrap;
        }


scrapActiveViews

把mActiveViews里的数据移到mCurrentScrap中去,代码如下

        void scrapActiveViews() {
            final View[] activeViews = mActiveViews;
            final boolean hasListener = mRecyclerListener != null;
            final boolean multipleScraps = mViewTypeCount > 1;

            ArrayList<View> scrapViews = mCurrentScrap;
            final int count = activeViews.length;
            for (int i = count - 1; i >= 0; i--) {
                final View victim = activeViews[i];
                if (victim != null) {
                    final AbsListView.LayoutParams lp
                            = (AbsListView.LayoutParams) victim.getLayoutParams();
                    final int whichScrap = lp.viewType;

                    activeViews[i] = null; // 把第i个置为空

                    if (victim.hasTransientState()) {
                        .. // transient_view处理,忽略
                    } else if (!shouldRecycleViewType(whichScrap)) {
                        .. // view_type < 0,忽略
                    } else { // 一般就是这里的逻辑
                        // Store everything else on the appropriate scrap heap.
                        if (multipleScraps) { // 不覆写adapter里的getViewType的话,这里走不到
                            scrapViews = mScrapViews[whichScrap];
                        }

                        lp.scrappedFromPosition = mFirstActivePosition + i;
                        removeDetachedView(victim, false); // 移除子view的一些处理
                        scrapViews.add(victim); // 给mCurrent这个回收站添加子项

                        if (hasListener) { // 必要的话,通知回调
                            mRecyclerListener.onMovedToScrapHeap(victim);
                        }
                    }
                }
            }
            pruneScrapViews(); // 处理多种viewType和transient的情况,忽略
        }

shouldRecycleViewType

判断viewType是否有效,有效的才能被回收缓存,代码如下

        public boolean shouldRecycleViewType(int viewType) {
            return viewType >= 0;
        }


removeDetachedView

对分离出去的view进行一些清除工作,代码如下

        private void removeDetachedView(View child, boolean animate) {
            child.setAccessibilityDelegate(null);
            AbsListView.this.removeDetachedView(child, animate);
        }

调用了AbsListView.removeDetachedView(),实则是ViewGroup的同名方法,代码如下

    protected void removeDetachedView(View child, boolean animate) {
        if (mTransition != null) {
            mTransition.removeChild(this, child);
        }

        // 清除焦点
        if (child == mFocused) {
            child.clearFocus();
        }
        if (child == mDefaultFocus) {
            clearDefaultFocus(child);
        }
        if (child == mFocusedInCluster) {
            clearFocusedInCluster(child);
        }

        child.clearAccessibilityFocus();

        cancelTouchTarget(child); // 取消触摸事件,如果能dispatchTochEvent,就分发下去
        cancelHoverTarget(child); // 鼠标移动事件,忽略

        if ((animate && child.getAnimation() != null) ||
                (mTransitioningViews != null && mTransitioningViews.contains(child))) {
            addDisappearingView(child);
        } else if (child.mAttachInfo != null) { // 走这里
            child.dispatchDetachedFromWindow(); // 清理或触发一些回调
        }

        if (child.hasTransientState()) {
            childHasTransientStateChanged(child, false);
        }

        dispatchViewRemoved(child); // 必要的话,触发mOnHierarchyChangeListener.onChildViewRemoved方法
    }


retrieveFromScrap

从回收站中读取view,代码如下

        private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
            final int size = scrapViews.size();
            if (size > 0) {
                // See if we still have a view for this position or ID.
                // Traverse backwards to find the most recently used scrap view
                for (int i = size - 1; i >= 0; i--) {
                    final View view = scrapViews.get(i);
                    final AbsListView.LayoutParams params =
                            (AbsListView.LayoutParams) view.getLayoutParams();

                    if (mAdapterHasStableIds) { // 适配器继承自BaseAdapter的话,默认为false
                        final long id = mAdapter.getItemId(position);
                        if (id == params.itemId) {
                            return scrapViews.remove(i);
                        }
                    } else if (params.scrappedFromPosition == position) { // 可以看到,view在回收站中的位置是保存在自己的LayoutParams里的
                        final View scrap = scrapViews.remove(i); // 这里返回的是最后一个位置信息符合要求的view,但不一定是正确的view
                        clearScrapForRebind(scrap); // 无障碍处理
                        return scrap;
                    }
                }
                final View scrap = scrapViews.remove(size - 1); // 找不到对应的view,就把回收站中最后一个view返回出去
                clearScrapForRebind(scrap);
                return scrap;
            } else {
                return null;
            }
        }


markChildrenDirty

强制所有缓存的子view进行forceLayout,代码如下

        public void markChildrenDirty() {
            if (mViewTypeCount == 1) {
                final ArrayList<View> scrap = mCurrentScrap;
                final int scrapCount = scrap.size();
                for (int i = 0; i < scrapCount; i++) {
                    scrap.get(i).forceLayout();
                }
            } else {
                .. // 多种viewType处理
            }
            .. // transient处理
        }

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

安卓开发学习之ListView缓存策略中常见的方法

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭