How exactly does GridView recycle / reuse view in Android?

I think I understand the view reuse mechanism for the ListView, but for the GridView, the way it recycles views is not pending.

in my example, i just just recycle the view if convertView is not null in getView()

from adapter.

Also I record the position of the view.

enter image description here

but a weird thing after checking the logcat, I found that even if I didn't scroll at all, the view from position 0 (first vi! ew) is not null ( first time no reworked view, so convertView for positon 0 must be null? ) ...

enter image description here

even weirder thing, after it registers all 54 items (since you can see that the whole screen can hold 54 views), it registers server position 0 of the reworked view

enter image description here

so it completely confuses me how the GridView recycles views. I think it should be something like a ListView, if I scroll down, the top views come out of the screen and then come back to the bottom of the list one by one, but it looks like it isn't in the GridView.

I even use HashMap to calculate the cycle time for each view (by String to Integer Entry, Key is the value toString()

for the view), I found that the recycling times are not equally distributed, while some of the view have 30 times the turn and some of them have only one time.


Edit (add return codes as well as screenshot for exception)

enter image description here

enter image description here

(Edit) even though I tried using `setImageDrawable(null)` it still throws exception, as noticed you can see the exception will be thrown no matter I called it before or after the recycle

      

enter image description hereenter image description here

I think the point of view on this question is: Android does not handle the redesigned view as I expected, so I guess even if convertView is not null it doesn’t prove that it goes off the screen, as you answered already, android will use position 0 for the initial measurement, so that might be the root cause.

**Edit Again**

      

Adapter code:

 package com.example.photoswalldemo;

    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.AsyncTask;
    import android.util.LruCache;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.AbsListView;
    import android.widget.AbsListView.OnScrollListener;
    import android.widget.ArrayAdapter;
    import android.widget.GridView;
    import android.widget.ImageView;

    import com.android.volley.RequestQueue;
    import com.android.volley.Response;
    import com.android.volley.VolleyError;
    import com.android.volley.toolbox.ImageRequest;
    import com.android.volley.toolbox.Volley;

    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.HashSet;
    import java.util.Set;

    /**
     * GridView的适配器,负责异步从网络上下载图片展示在照片墙上。
     * 
     * @author guolin
     */
    public class PhotoWallAdapter extends ArrayAdapter<String> implements OnScrollListener {

        /**
         * 记录所有正在下载或等待下载的任务。
         */
        private Set<BitmapWorkerTask> taskCollection;

        /**
         * 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
         */
        private LruCache<String, Bitmap> mMemoryCache;

        /**
         * GridView的实例
         */
        private GridView mPhotoWall;

        /**
         * 第一张可见图片的下标
         */
        private int mFirstVisibleItem;

        /**
         * 一屏有多少张图片可见
         */
        private int mVisibleItemCount;

        /**
         * 记录是否刚打开程序,用于解决进入程序不滚动屏幕,不会下载图片的问题。
         */
        private boolean isFirstEnter = true;


        private RequestQueue queue= Volley.newRequestQueue(getContext());



        public PhotoWallAdapter(Context context, int textViewResourceId, String[] objects,
                GridView photoWall) {
            super(context, textViewResourceId, objects);
            mPhotoWall = photoWall;
            taskCollection = new HashSet<BitmapWorkerTask>();
            // 获取应用程序最大可用内存
            int maxMemory = (int) Runtime.getRuntime().maxMemory();
            int cacheSize = maxMemory / 8;
            // 设置图片缓存大小为程序最大可用内存的1/8
            mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
                @Override
                protected int sizeOf(String key, Bitmap bitmap) {
                    return bitmap.getByteCount();
                }
            };
            mPhotoWall.setOnScrollListener(this);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
           // Log.e("Monitor View","This View is on position "+position+" and its "+convertView);
            final String url = getItem(position);
            View view;
            if (convertView == null) {
                view = LayoutInflater.from(getContext()).inflate(R.layout.photo_layout, null);
            } else {

                View tmp=convertView;

    //            ImageView tmp1=(ImageView)tmp.findViewById(R.id.photo);
    //            if(tmp1.getDrawable()!=null){
    //                //Toast.makeText(getContext(),"it has Drawable!,",Toast.LENGTH_LONG).show();
    //                Log.e("Monitor View", "This View is on position " + position + " and its view " + convertView+" and this drawable is "+tmp1.getDrawable());
    //
    //                Bitmap bitmap=((BitmapDrawable)tmp1.getDrawable()).getBitmap();
    //                tmp1.setImageDrawable(null);
    //                tmp1.setImageBitmap(null);
    //                bitmap.recycle();
    //                bitmap=null;
    //                //tmp1.setImageDrawable(null);


            //    }


                view = convertView;


            }
            final ImageView photo = (ImageView) view.findViewById(R.id.photo);
            // 给ImageView设置一个Tag,保证异步加载图片时不会乱序
            photo.setTag(url);
            setImageView(url, photo);
            return view;
        }

        /**
         * 给ImageView设置图片。首先从LruCache中取出图片的缓存,设置到ImageView上。如果LruCache中没有该图片的缓存,
         * 就给ImageView设置一张默认图片。
         * 
         * @param imageUrl
         *            图片的URL地址,用于作为LruCache的键。
         * @param imageView
         *            用于显示图片的控件。
         */
        private void setImageView(String imageUrl, ImageView imageView) {
            Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
            if (bitmap != null) {
                imageView.setImageBitmap(bitmap);
            } else {
                imageView.setImageResource(R.drawable.empty_photo);
            }
        }

        /**
         * 将一张图片存储到LruCache中。
         * 
         * @param key
         *            LruCache的键,这里传入图片的URL地址。
         * @param bitmap
         *            LruCache的键,这里传入从网络上下载的Bitmap对象。
         */
        public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
            if (getBitmapFromMemoryCache(key) == null) {
                mMemoryCache.put(key, bitmap);
            }
        }

        /**
         * 从LruCache中获取一张图片,如果不存在就返回null。
         * 
         * @param key
         *            LruCache的键,这里传入图片的URL地址。
         * @return 对应传入键的Bitmap对象,或者null。
         */
        public Bitmap getBitmapFromMemoryCache(String key) {
            return mMemoryCache.get(key);
        }

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // 仅当GridView静止时才去下载图片,GridView滑动时取消所有正在下载的任务
            if (scrollState == SCROLL_STATE_IDLE) {
                loadBitmaps(mFirstVisibleItem, mVisibleItemCount);
            } else {
                cancelAllTasks();
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                int totalItemCount) {
            mFirstVisibleItem = firstVisibleItem;
            mVisibleItemCount = visibleItemCount;
            // 下载的任务应该由onScrollStateChanged里调用,但首次进入程序时onScrollStateChanged并不会调用,
            // 因此在这里为首次进入程序开启下载任务。
            if (isFirstEnter && visibleItemCount > 0) {
                loadBitmaps(firstVisibleItem, visibleItemCount);
                isFirstEnter = false;
            }
        }

        /**
         * 加载Bitmap对象。此方法会在LruCache中检查所有屏幕中可见的ImageView的Bitmap对象,
         * 如果发现任何一个ImageView的Bitmap对象不在缓存中,就会开启异步线程去下载图片。
         * 
         * @param firstVisibleItem
         *            第一个可见的ImageView的下标
         * @param visibleItemCount
         *            屏幕中总共可见的元素数
         */
        private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {
            try {
                for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
                    String imageUrl = Images.imageThumbUrls[i];
                    Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
                    if (bitmap == null) {
                        //BitmapWorkerTask task = new BitmapWorkerTask();
                        //taskCollection.add(task);
                        //task.execute(imageUrl);

                        //new method , use volley

                        useVolley(imageUrl);
                    } else {
                        ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);
                        if (imageView != null && bitmap != null) {
                            imageView.setImageBitmap(bitmap);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        /**
         * 取消所有正在下载或等待下载的任务。
         */
        public void cancelAllTasks() {
            if (taskCollection != null) {
                for (BitmapWorkerTask task : taskCollection) {
                    task.cancel(false);
                }
            }
        }

        /**
         * 异步下载图片的任务。
         * 
         * @author guolin
         */
        class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {

            /**
             * 图片的URL地址
             */
            private String imageUrl;

            @Override
            protected Bitmap doInBackground(String... params) {
                imageUrl = params[0];
                // 在后台开始下载图片
                Bitmap bitmap = downloadBitmap(params[0]);
                if (bitmap != null) {
                    // 图片下载完成后缓存到LrcCache中
                    addBitmapToMemoryCache(params[0], bitmap);
                }
                return bitmap;
            }

            @Override
            protected void onPostExecute(Bitmap bitmap) {
                super.onPostExecute(bitmap);
                // 根据Tag找到相应的ImageView控件,将下载好的图片显示出来。
                ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);
                if (imageView != null && bitmap != null) {
                    imageView.setImageBitmap(bitmap);
                }
                taskCollection.remove(this);
            }

            /**
             * 建立HTTP请求,并获取Bitmap对象。
             * 
             * @param imageUrl
             *            图片的URL地址
             * @return 解析后的Bitmap对象
             */
            private Bitmap downloadBitmap(String imageUrl) {
                Bitmap bitmap = null;
                HttpURLConnection con = null;
                try {
                    URL url = new URL(imageUrl);
                    con = (HttpURLConnection) url.openConnection();
                    con.setConnectTimeout(5 * 1000);
                    con.setReadTimeout(10 * 1000);
                    con.setDoInput(true);
                    con.setDoOutput(true);
                    bitmap = BitmapFactory.decodeStream(con.getInputStream());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (con != null) {
                        con.disconnect();
                    }
                }
                return bitmap;
            }

        }



        private void useVolley(String url){

            final String tmpurl=url;

           // ImageView tmpImageView=(ImageView)mPhotoWall.findViewWithTag(tmpurl);
            ImageRequest imageRequest=new ImageRequest(url,new Response.Listener<Bitmap>() {
                @Override
                public void onResponse(Bitmap response) {

                    ImageView tmpImageView=(ImageView)mPhotoWall.findViewWithTag(tmpurl);

                    tmpImageView.setImageBitmap(response);

                    mMemoryCache.put(tmpurl,response);
                }
            },0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {

                    ImageView tmpImageView=(ImageView)mPhotoWall.findViewWithTag(tmpurl);
                    //imageView.setImageResource(R.drawable.default_image);
                    tmpImageView.setImageResource(R.drawable.empty_photo);
                }
            });



            this.queue.add(imageRequest);

        }

    }

      

Main Activity Code:

public class MainActivity extends Activity {

    /**
     * 用于展示照片墙的GridView
     */
    private GridView mPhotoWall;

    /**
     * GridView的适配器
     */
    private PhotoWallAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mPhotoWall = (GridView) findViewById(R.id.photo_wall);
        adapter = new PhotoWallAdapter(this, 0, Images.imageThumbUrls, mPhotoWall);
        mPhotoWall.setAdapter(adapter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 退出程序时结束所有的下载任务
        adapter.cancelAllTasks();
    }

}

      

+3


source to share


1 answer


The way you inflate the view if it is invalid and reuse it if it is not null is correct. This is the way a subclass AdapterView

ensures that its views are reused.

At first I was puzzled, since you were about list item 0 having a non-zero form; but then I remembered what the AdapterView

adapter is calling to get the list view of 0 multiple times during the measure and shim phase before even displaying the list item. It sounds overkill, but obviously the Google developer had reasons for this. This also explains why you see multiple logs for the list item.

As far as uneven reuse goes, I can't remember if the views were revised by FIFO or LIFO, but the point is, it doesn't matter. If you've pointed out (via getItemViewType()

that the layouts are the same, then it doesn't matter which one AdapterView

serves you up with recycling. If all you need is an aluminum can, do you really care if it's Coke or Pepsi?



Also consider that views at the very top AdapterView

tend to go to and on screen much more often than views further down, and I think this explains the one-way distribution of view recycling that you see.


EDIT: per your exception: you call bitmap.recycle()

and then install bitmap = null

. But keep in mind, even if you have installed bitmap = null

, Drawable

still contains a reference to a bitmap that is now marked as processed! So when it ImageView

tries to do this, it notices that the bitmap has been marked as reprocessed, so an exception is thrown. If you call tmp1.setImageDrawable(null)

after working with a bitmap, the exception should go away.

+2


source







All Articles