zoukankan      html  css  js  c++  java
  • Android AdapterView View的复用机制 分析

    对于ListView、GridView相信大家都不陌生,重写个BaseView,实现对于的几个方法,然后就完成了我们的界面展示,并且在大部分情况下,我们加载特别多的Item也不会发生OOM,大家也都明白内部有缓存机制,都遇到过ItemView复用带来的一些问题,比如异步加载图片,最终造成界面显示的混乱,我们一般会使用setTag,然后回调显示时,避免造成混乱。

    设想1:拿ListView为例,如果ListView的ItemView复用机制,所有的ItemView复用同一个,如果在多线程下载图片的情况下,可能最终只有最后一个View显示图片吧,因为你前面的设置setTag(url),后面马上就会将你的Tag的值覆盖掉,最终findViewByTag找到的都是最后一个。由此可见ListView缓存的不是一个,至少是一屏幕可显示的数量。也就是说ListView维护着一个ItemView的池子。

    跟大家解释下,为啥缓存了一个屏幕的可显示最大的ItemView数量的池子,我们可能上千个ItemView,仅依靠Tag就能实现不混乱呢。

    情景:屏幕每次显示7个Item,ListView一共1000个Item,每个Item上显示一张从网络下载的图片。

    getView的代码大概是这样的:

    @Override
    	public View getView(int position, View convertView, ViewGroup parent)
    	{
    		final String url = getItem(position);
    		View view;
    		if (convertView == null)
    		{
    			view = LayoutInflater.from(getContext()).inflate(R.layout.photo_layout, null);
    		} else
    		{
    			view = convertView;
    		}
    		final ImageView photo = (ImageView) view.findViewById(R.id.photo);
    		// 给ImageView设置一个Tag,保证异步加载图片时不会乱序
    		photo.setTag(url);
    		new LoadImgTask(photo).execute(url);
    		return view;
    	}
    下载完成图片,进行photo.getTag().equals(url)来防止图片显示的混乱。

    如果我们打开界面,开启了7个线程去下载,此时缓存了这7个ItemView,现在滑动屏幕显示另外下一屏,此时7个ItemView都会复用,会把第一屏设置的Tag全部覆盖掉,没错就是覆盖掉了,又开启7个线程去下载图片,当第一屏的ItemView的图片下载完成后,如果直接findViewByTag然后设置图片会显示在第二屏上,就混乱了,所以一般在显示前都会判断photo.getTag().equals(url);确定了再显示,也就是说第一屏的ItemView图片下载完了,但是Tag被覆盖了,所以即使下载完成了,也不会有任何显示。这就解释了为什么我们防止混乱的代码需要那样去写。

    好了,下面从源码角度看一眼ListView内部到底是如何进行缓存的:

    跟着ListView,进入父类AbsList,会发现这样一个变量:

     /**
         * The data set used to store unused views that should be reused during the next layout
         * to avoid creating new ones
         */
        final RecycleBin mRecycler = new RecycleBin();

    注释的意思上用一个数据集来存储应当在下一个布局重用的View,避免重新创建新的布局。这个对象应该就是对我们缓存管理的核心类了。继续看这个类,这是一个内部类:

    	/**
         * 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
         */
        class RecycleBin {    private View[] mActiveViews = new View[0];                                                                                                                private ArrayList<View>[] mScrapViews;                                                                                                               	  ....                                                                                                                                                 }   
    大概意思:这个类是用来帮助在滑动布局时重用View的,RecycleBin包含了两个级别的存储,ActiveViews和ScrapViews,ActiveViews存储的是第一次显示在屏幕上的View;所有的ActiveViews最终都会被移到ScrapViews,ScrapViews存储的是有可能被adapter复用的View。
    现在很明确了AbsListView缓存依赖于两个数组,一个数组存储屏幕上当前现实的ItemView,一个显示从屏幕下移除的且可能会被复用的ItemView。下面看ListView里面的代码:

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

     /**
    	* 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
    	*/
    	void fillActiveViews(int childCount, int firstActivePosition) 
    	{
    		if (mActiveViews.length < childCount) 
    		{
    			mActiveViews = new View[childCount];
    		}
    		mFirstActivePosition = firstActivePosition;
    
    		final View[] activeViews = mActiveViews;
    		for (int i = 0; i < childCount; i++) 
    		{
    			View child = getChildAt(i);
    			activeViews[i] = child; 
    		}
    	}

    可以看出,如果数据发生变化则把当前的ItemView放入ScrapViews中,否则把当前显示的ItemView放入ActiveViews中。那么咱们关键的getView方法到底是在哪调用呢,下面看RecycleBin中的方法:

    /**
         * Get a view and have it show the data associated with the specified
         * position. This is called when we have already discovered that the view is
         * not available for reuse in the recycle bin. The only choices left are
         * converting an old view or making a new one.
         *
         * @param position The position to display
         * @param isScrap Array of at least 1 boolean, the first entry will become true if
         *                the returned view was taken from the scrap heap, false if otherwise.
         * 
         * @return A view displaying the data associated with the specified position
         */
        View obtainView(int position, boolean[] isScrap) 
    	{
    		isScrap[0] = false;
            View scrapView;
            scrapView = mRecycler.getScrapView(position);
            View child;
            if (scrapView != null) 
    		{
              
                child = mAdapter.getView(position, scrapView, this);
                if (child != scrapView) 
    			{
                    mRecycler.addScrapView(scrapView);
                   
                } else 
    			{
                    isScrap[0] = true;
                    child.dispatchFinishTemporaryDetach();
                }
            } else 
    		{
                child = mAdapter.getView(position, null, this);         
            }
    
            return child;
        }
    

    可以看到,这个方法就是返回当前一个布局用户当前Item的显示,首先根据position去ScrapView中找,找到后调用我们的getView,此时getView里面的convertView!=null了,然后getView如果返回的View发生变化,缓存下来,否则convertView==null了。

    好了,主要是为了让大家了解,AbsListView为什么我们可以通过一个Tag的设置保证其正确的显示,以及缓存机制在AbsListView到底是怎么实现的,鉴于源代码实在太长,只能大概的根据代码了解一下原理。


    最后,各位看官,没事留个言,顶一个呗~





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

  • 相关阅读:
    ZOJ 2158 Truck History
    Knight Moves (zoj 1091 poj2243)BFS
    poj 1270 Following Orders
    poj 2935 Basic Wall Maze (BFS)
    Holedox Moving (zoj 1361 poj 1324)bfs
    ZOJ 1083 Frame Stacking
    zoj 2193 Window Pains
    hdu1412{A} + {B}
    hdu2031进制转换
    openjudge最长单词
  • 原文地址:https://www.cnblogs.com/dingxiaoyue/p/4924973.html
Copyright © 2011-2022 走看看