Android源码学习之ListView的复用回收机制剖析.

本博客参考了地址:点击打开链接

在刚开始接触学习Android基础的时候,ListView算是一个比较神奇的控件了,因为那时候好多效果都可以用它实现,而且用它就得用到一个设计模式,[适配器].结果昨天遗留下来一个bug,带这个解决这个Bug去翻看了5.0.1 API22的ListView部分源码分析复用.

复用到底有什么用.?简单的举个例子,假如你想要展示一万条item,作为手机不可能一下将一万条同时加载进去,这样肯定会OOM的,所以Google开发者想到了复用,也算是ListView高级的一个特点.

竟然复用的作用明白了,那Android到底是怎么复用的啊,?先看看ListView的结构图.

可以看到ScrollView,ListView,ExpanableListView,GridView都是继承于ViewGroup,说到这儿我想起一个隐藏的CallBack:OverScrollBy();

该方法算是Google隐藏起来了的,应该是为了模仿IOS的回弹阻尼效果,结果....

不过通过该方法还是能很轻易的实现,当然在新版本的RecycleView并不会回调该方法了.

关系中复用的核心类主要是放在了:AbsListView中的RecycleBin类中.该类源码中也详细说了两个重要的对象:ActiveViews,ScrapViews.先看说明然后再分析两个对象.

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

ActiveViews顾名思义:当前活动,什么叫当前活动的,就是当前屏幕上可视的VIew,并且第一次被创建.

ScrapViews:被废弃回收的视图,就是指当前手指滑出屏幕外的被回收的视图.

可以看出来这两个对象非常重要,该类中的一些方法:

/**
         * Puts all views in the scrap heap into the supplied list.
         */
        void reclaimScrapViews(List<View> views) {
            if (mViewTypeCount == 1) {  // 这的Type是默认的,表示说如果你的Item只有一种类型,就只有一个集合回收,
                views.addAll(mCurrentScrap);//添加到回收
            } else { // 如果是多个类型就要创建多个不同类型的回收集合.
                final int viewTypeCount = mViewTypeCount;
                final ArrayList<View>[] scrapViews = mScrapViews;
                for (int i = 0; i < viewTypeCount; ++i) {
                    final ArrayList<View> scrapPile = scrapViews[i];
                    views.addAll(scrapPile);
                }
            }
        }
/**
     * Returns the height of the view for the specified position.
     *
     * @param position the item position
     * @return view height in pixels
     */
    int getHeightForPosition(int position) {
        final int firstVisiblePosition = getFirstVisiblePosition();
        final int childCount = getChildCount();
        final int index = position - firstVisiblePosition; // 计算索引
        if (index >= 0 && index < childCount) { // 在Childs范围内
            // Position is on-screen, use existing view.
            final View view = getChildAt(index);// 直接查找
            return view.getHeight();
        } else {
            // Position is off-screen, obtain & recycle view.
            final View view = obtainView(position, mIsScrap); // 超出屏幕外,Obtain,
            view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);//测量子孩子.
            final int height = view.getMeasuredHeight();
            mRecycler.addScrapView(view, position); // 添加到废弃的Views中,
            return height;
        }
    }
 /**
     * Scroll the children by amount, adding a view at the end and removing
     * views that fall off as necessary.
     *
     * @param amount The amount (positive or negative) to scroll.
     */
    private void scrollListItemsBy(int amount) {
        offsetChildrenTopAndBottom(amount);

        final int listBottom = getHeight() - mListPadding.bottom;//底部位置
        final int listTop = mListPadding.top;// 顶部位置
        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); // 获取最后一个item
            while (last.getBottom() < listBottom) {// 最后一个Item的底部比较.
                final int lastVisiblePosition = mFirstPosition + numChildren - 1;
                if (lastVisiblePosition < mItemCount - 1) {
                    last = addViewBelow(last, lastVisiblePosition);//将新的View添加到下面,
                    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) {
                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); // 顶部是直接添加的了回收对象集合中去了.
                }
                detachViewFromParent(first);//并从ViewGroup集合中移除该View,这的移除待会分析.
                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)) {
                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) {
                offsetChildrenTopAndBottom(listTop - first.getTop());
            }

            int lastIndex = getChildCount() - 1;
            View last = getChildAt(lastIndex);

            // bottom view may be panned off screen
            while (last.getTop() > listBottom) {
                AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
                if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
                    recycleBin.addScrapView(last, mFirstPosition+lastIndex);
                }
                detachViewFromParent(last);
                last = getChildAt(--lastIndex);
            }
        }
    }
// This method also sets the child's mParent to null
    private void removeFromArray(int index) {
        final View[] children = mChildren;
        if (!(mTransitioningViews != null && mTransitioningViews.contains(children[index]))) {
            children[index].mParent = null; // 并未直接Remove,等待GC去回收.
        }
        final int count = mChildrenCount;
        if (index == count - 1) {
            children[--mChildrenCount] = null;
        } else if (index >= 0 && index < count) {
            System.arraycopy(children, index + 1, children, index, count - index - 1);
            children[--mChildrenCount] = null;
        } else {
            throw new IndexOutOfBoundsException();
        }
        if (mLastTouchDownIndex == index) {
            mLastTouchDownTime = 0;
            mLastTouchDownIndex = -1;
        } else if (mLastTouchDownIndex > index) {
            mLastTouchDownIndex--;
        }
    }

所以添加到ScrapViews中后,该View仅仅是处于游离状态.

ListView可以设置setRecyclerListener,该接口会调用到RecycleBin中的reclaimView中,也是上面分析,

@Override
    public void onMovedToScrapHeap(View view) {
        //view是Item的Viewgroup.
    }

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-08 18:17:24

Android源码学习之ListView的复用回收机制剖析.的相关文章

Android源码学习之装饰模式应用

主要内容: 装饰模式定义 装饰模式优势 装饰模式在Android源码中的应用 一.装饰模式定义 装饰模式定义: Attach additional responsibilities to an object dynamically keeping the same interface. Decoators provide a flexible alternative to subclassing for extending functionality. 动态地给一个对象添加一些额外的职责.就增加

Android源码学习(一) 数据集观察者

查看Android源码发现这个,决定记下下来. 1.在android.database这个包下面,存在这样一个抽象类DataSetObserver,里面包括onChanged()和onInvalidated()这个两个方法,用来监听数据的改变,在方法内要执行的操作. /* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "Lic

OSChina客户端源码学习(3)--轮询机制的实现

主要以OSChina Android客户端源码中Notice的轮询机制进行解读. 一.基础知识 一般IM(即使通讯)的实现有两种方式:推送和轮询,推送就是服务器主动向客户端发送消息,用特定的协议比如XMPP.MQTT.另一种是轮询,实时性并不高,而且比较耗电.这种有分为两种情况:一段时间发起一次查询和死循环进行查询. 参考: http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/0401/1609.html 远端Service调用: a.服

【转】Android源码学习(2)使用Git和Repo进行版本管理

原文网址:http://blog.chinaunix.net/uid-26074270-id-2458828.html Android项目采用Git和Repo进行版本管理.在大多数情况下,Git都可以满足用户的需求.然而,由于Android项目过于庞大,想要简单.高效的管理这一百多个Git库,并不是一件容易的事情.Repo正是基于此需求,对Git命令部分封装,用来简化一些跨网络的操作. 安装Repo 创建repo所在的目录,并将目录加到PATH环境变量中 $ mkdir ~/bin $ PATH

Android源码学习-----Handler机制

Handler 1.为什么要使用Handler 在Android4.0之后,google公司为从系统使用及用户体验方面考虑,如果做一些比较耗时的操作,就不允许直接在主线程中进行,而是要通过handler发送Message对象的方法来修改主线程的UI界面 2.Handler原理简介 在所有的UI操作界面中,都在执行一个死循环(Looper)在不断接收和监听用户发出的指令,一但接受到指令,就立即执行.   当子线程需要修改UI界面时,调用Handler的sendMessage()方法,向主线程发送消

Android源码学习初步

目前,互联网行业正在朝着移动互联网方向强劲地发展,而移动互联网的发展离不开背后的移动平台的支撑.众所周知,如今在移动平台市场上,苹果的iOS.谷歌的Android和微软的Windows Phone系统已经形成了三足鼎立的形势,而Android系统的市场占有率是最高的.Android系统之所以能够在市场上占据着第一的位置,一来是因为它依托着谷歌的品德效应和技术实力,二来是因为它是开放的,任何人都可以得到它的源代码,并且能够自由地使用它.既然Android系统是开放的,作为一个移动平台开发人员来说,

Android源码学习-----HandlerThread

HandlerThread 1.run()方法 HandlerThread 从继承关系上看, 它继承Thread类, 由此可以得知这个类其实是一个线程类,既然是一个线程类, 那么肯定是要重写Thread中的run()方法, 所以可以浏览下run()方法 从红色箭头的三个方法中, 看到有三个方法, Looper.prepare(), Looper.myLooper(), Looper.loop(),  这三个方法其实是, 在子线程创建Handler时所要调用的三个方法, 在HandlerThrea

Android 源码解析View的touch事件分发机制

概述 本篇主要分析的是touch事件的分发机制,网上关于这个知识点的分析文章非常多.但是还是想通过结合自身的总结,来加深自己的理解.对于事件分发机制,我将使用两篇文章对其进行分析,一篇是针对View的事件分发机制解析,一篇是针对ViewGroup的事件分发机制解析.本片是对View的事件分发机制进行解析,主要采用案例结合源码的方式来进行分析. 前言 在分析事件分发机制之前,我们先来学习一下基本的知识点,以便后面的理解. View中有两个关键方法参与到Touch事件分发 dispatchTouch

Android 源码分析(十一) 事件传递机制

一.介绍 Android三种事件类型:ACTION_DOWN,ACTOIN_MOVE,ACTION_UP. 事件传递的三个阶段: 分发(Dispatch) 方法:public boolean dispatchTouchEvent(MotionEvent ev) 拦截(Intercept) 方法:public boolean onInterceptTouchEvent(MotionEvent ev) 消费(Consume) 方法:public boolean onTouchEvent(Motion