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究竟是怎么实现的。鉴于源码实在太长。仅仅能大概的依据代码了解一下原理。


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





  • 相关阅读:
    372. Super Pow
    224. Basic Calculator + 227. Basic Calculator II
    263. Ugly Number + 264. Ugly Number II + 313. Super Ugly Number
    169. Majority Element
    225. Implement Stack using Queues + 232. Implement Queue using Stacks
    551. Student Attendance Record I + Student Attendance Record II
    765. Couples Holding Hands
    547. Friend Circles
    535. Encode and Decode TinyURL
    87. Scramble String
  • 原文地址:https://www.cnblogs.com/blfshiye/p/5178378.html
Copyright © 2011-2022 走看看