ListView异步加载图片分析

来源:互联网 发布:mac emoji 快捷键 编辑:程序博客网 时间:2024/06/04 17:57

一.概述

在ListView中,如果是同步加载图片还好,但是如果异步加载图片就会出现问题,下面我们来分析一下。

先准备一些图片作为数据源,我们新建一个Images类保存图片:

public class Images {     public final static String[] imageUrls = new String[] {              "http://img.my.csdn.net/uploads/201508/05/1438760758_3497.jpg",                "http://img.my.csdn.net/uploads/201508/05/1438760758_6667.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760757_3588.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760756_3304.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760755_6715.jpeg",              "http://img.my.csdn.net/uploads/201508/05/1438760726_5120.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760726_8364.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760725_4031.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760724_9463.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760724_2371.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760707_4653.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760706_6864.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760706_9279.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760704_2341.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760704_5707.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760685_5091.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760685_4444.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760684_8827.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760683_3691.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760683_7315.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760663_7318.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760662_3454.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760662_5113.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760661_3305.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760661_7416.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760589_2946.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760589_1100.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760588_8297.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760587_2575.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760587_8906.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760550_2875.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760550_9517.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760549_7093.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760549_1352.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760548_2780.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760531_1776.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760531_1380.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760530_4944.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760530_5750.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760529_3289.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760500_7871.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760500_6063.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760499_6304.jpeg",              "http://img.my.csdn.net/uploads/201508/05/1438760499_5081.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760498_7007.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760478_3128.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760478_6766.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760477_1358.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760477_3540.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760476_1240.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760446_7993.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760446_3641.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760445_3283.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760444_8623.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760444_6822.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760422_2224.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760421_2824.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760420_2660.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760420_7188.jpg",              "http://img.my.csdn.net/uploads/201508/05/1438760419_4123.jpg",          };  }

然后是我们的重点,适配器:

public class ImageAdapter extends ArrayAdapter<String>{    LruCache<String, BitmapDrawable> memoryCache ;    public ImageAdapter(Context context, int resource,            String[] objects) {        super(context, resource, objects);        //返回系统可用的最大堆内存,单位是byte,我的手机算下来是256MB        int maxMemory = (int) Runtime.getRuntime().maxMemory();        //手机:268435456B = 256MB        //模拟器:16777216B = 16MB        int cacheSize = maxMemory/8;//指定缓存大小为内存的八分之一        memoryCache = new LruCache<String,BitmapDrawable>(cacheSize){            //重写此方法,返回每张图片的大小            protected int sizeOf(String key, BitmapDrawable value) {                //每张图片大小:108000B = 105KB                return value.getBitmap().getByteCount();};        };    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        //获取对应位置图片的url地址        String url = getItem(position);        View view;        if(convertView == null){            view = View.inflate(getContext(), R.layout.image_item, null);        }else{            view = convertView;        }        ImageView imageView = (ImageView) view.findViewById(R.id.imageitem);        imageView.setImageResource(R.drawable.ic_launcher);        //根据url从内存中取        BitmapDrawable drawable = getBitmapFromMemory(url);        if(drawable!=null){            //内存中有,直接设置给imageview            imageView.setImageDrawable(drawable);        }else{            //内存没有,开启异步任务去下载            BitmapWorkerTask task = new BitmapWorkerTask(imageView);            task.execute(url);        }        return view;    }    /**     * 获取对应位置图片的url地址     */    @Override    public String getItem(int position) {        return Images.imageUrls[position];    }    /**     * 将图片添加到lrucache中     * @param key     * @param drawable     */    public void addBitmapToMemory(String key,BitmapDrawable drawable){        if(getBitmapFromMemory(key)==null){            memoryCache.put(key, drawable);        }    }    /**     * 根据url从内存中获取图片     * @param key     * @return     */    public BitmapDrawable getBitmapFromMemory(String key){        return memoryCache.get(key);    }    /**     * String:启动任务执行的输入参数的类型     *Void:后台任务执行的百分比     *BitmapDrawable:后台执行任务最终的返回结果     */    public class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable>{        private ImageView imageView;        private String imageUrl;        public BitmapWorkerTask(ImageView imageView) {            this.imageView = imageView;        }        @Override        protected BitmapDrawable doInBackground(String... params) {            imageUrl = params[0];//获取当前图片的地址            Bitmap bitmap = downloadBitmap(imageUrl);            BitmapDrawable bitmapDrawable = new BitmapDrawable(getContext().getResources(),bitmap);            //将下载好的图片添加到缓存中            addBitmapToMemory(imageUrl, bitmapDrawable);            return bitmapDrawable;        }        @Override        protected void onPostExecute(BitmapDrawable result) {            super.onPostExecute(result);            if(imageView!=null&&result!=null){                imageView.setImageDrawable(result);            }        }    }    /**     * 下载图片     * @param imageUrl     * @return     */    public Bitmap downloadBitmap(String imageUrl){        Bitmap bitmap = null;        HttpURLConnection connection = null;        try {            URL url = new URL(imageUrl);            connection = (HttpURLConnection) url.openConnection();            connection.setConnectTimeout(5*000);            connection.setReadTimeout(3*1000);            bitmap = BitmapFactory.decodeStream(connection.getInputStream());        } catch (Exception e) {            e.printStackTrace();        }finally{            if(connection!=null){                connection.disconnect();            }        }        return bitmap;    }}

最后在代码中我们使用这个适配器,给ListView设置数据

public class MainActivity extends Activity {    private ListView listView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        listView = (ListView) findViewById(R.id.listview);        listView.setAdapter(new ImageAdapter(this, 0, Images.imageUrls));    }}

以上就简单实现了ListView图片异步加载,我们看看效果吧:

这里写图片描述

可以看到图片出现了错位和乱序的情况,为什么会这样呢?这就和ListView的工作原理有关了。

那么这里我们就可以思考一下了,目前数据源当中大概有60个图片的URL地址,而根据ListView的工作原理,显然不可能为每张图片都单独分配一个ImageView控件,ImageView控件的个数其实就比一屏能显示的图片数量稍微多一点而已,移出屏幕的ImageView控件会进入到RecycleBin当中,而新进入屏幕的元素则会从RecycleBin中获取ImageView控件。
那么,每当有新的元素进入界面时就会回调getView()方法,而在getView()方法中会开启异步请求从网络上获取图片,注意网络操作都是比较耗时的,也就是说当我们快速滑动ListView的时候就很有可能出现这样一种情况,某一个位置上的元素进入屏幕后开始从网络上请求图片,但是还没等图片下载完成,它就又被移出了屏幕。这种情况下会产生什么样的现象呢?根据ListView的工作原理,被移出屏幕的控件将会很快被新进入屏幕的元素重新利用起来,而如果在这个时候刚好前面发起的图片请求有了响应,就会将刚才位置上的图片显示到当前位置上,因为虽然它们位置不同,但都是共用的同一个ImageView实例,这样就出现了图片乱序的情况。
但是还没完,新进入屏幕的元素它也会发起一条网络请求来获取当前位置的图片,等到图片下载完的时候会设置到同样的ImageView上面,因此就会出现先显示一张图片,然后又变成了另外一张图片的情况,那么刚才我们看到的图片会自动变来变去的情况也就得到了解释。

那么如何来解决这个问题呢,下面介绍一种比较简单的方法,当然还有其他的方法,这里就不一一介绍了。

public class ImageAdapter extends ArrayAdapter<String>{    LruCache<String, BitmapDrawable> memoryCache ;    private ListView mListView;    public ImageAdapter(Context context, int resource,            String[] objects) {        super(context, resource, objects);        //返回系统可用的最大堆内存,单位是byte,我的手机算下来是256MB        int maxMemory = (int) Runtime.getRuntime().maxMemory();        //手机:268435456B = 256MB        //模拟器:16777216B = 16MB        int cacheSize = maxMemory/8;//指定缓存大小为内存的八分之一        memoryCache = new LruCache<String,BitmapDrawable>(cacheSize){            //重写此方法,返回每张图片的大小            protected int sizeOf(String key, BitmapDrawable value) {                //每张图片大小:108000B = 105KB                return value.getBitmap().getByteCount();};        };    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        //获取对应位置图片的url地址        String url = getItem(position);        if(mListView == null){            mListView = (ListView) parent;        }        View view;        if(convertView == null){            view = View.inflate(getContext(), R.layout.image_item, null);        }else{            view = convertView;        }        ImageView imageView = (ImageView) view.findViewById(R.id.imageitem);        imageView.setTag(url);        imageView.setImageResource(R.drawable.ic_launcher);        //根据url从内存中取        BitmapDrawable drawable = getBitmapFromMemory(url);        if(drawable!=null){            //内存中有,直接设置给imageview            imageView.setImageDrawable(drawable);        }else{            //内存没有,开启异步任务去下载            BitmapWorkerTask task = new BitmapWorkerTask();            task.execute(url);        }        return view;    }    /**     * 获取对应位置图片的url地址     */    @Override    public String getItem(int position) {        return Images.imageUrls[position];    }    /**     * 将图片添加到lrucache中     * @param key     * @param drawable     */    public void addBitmapToMemory(String key,BitmapDrawable drawable){        if(getBitmapFromMemory(key)==null){            memoryCache.put(key, drawable);        }    }    /**     * 根据url从内存中获取图片     * @param key     * @return     */    public BitmapDrawable getBitmapFromMemory(String key){        return memoryCache.get(key);    }    /**     * String:启动任务执行的输入参数的类型     *Void:后台任务执行的百分比     *BitmapDrawable:后台执行任务最终的返回结果     */    public class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable>{        private ImageView imageView;        private String imageUrl;//      public BitmapWorkerTask(ImageView imageView) {//          this.imageView = imageView;//      }        @Override        protected BitmapDrawable doInBackground(String... params) {            imageUrl = params[0];//获取当前图片的地址            Bitmap bitmap = downloadBitmap(imageUrl);            BitmapDrawable bitmapDrawable = new BitmapDrawable(getContext().getResources(),bitmap);            //将下载好的图片添加到缓存中            addBitmapToMemory(imageUrl, bitmapDrawable);            return bitmapDrawable;        }        @Override        protected void onPostExecute(BitmapDrawable result) {            imageView = (ImageView) mListView.findViewWithTag(imageUrl);            super.onPostExecute(result);            if(imageView!=null&&result!=null){                imageView.setImageDrawable(result);            }        }    }    /**     * 下载图片     * @param imageUrl     * @return     */    public Bitmap downloadBitmap(String imageUrl){        Bitmap bitmap = null;        HttpURLConnection connection = null;        try {            URL url = new URL(imageUrl);            connection = (HttpURLConnection) url.openConnection();            connection.setConnectTimeout(5*000);            connection.setReadTimeout(3*1000);            bitmap = BitmapFactory.decodeStream(connection.getInputStream());        } catch (Exception e) {            e.printStackTrace();        }finally{            if(connection!=null){                connection.disconnect();            }        }        return bitmap;    }}

这里我们使用findViewWithTag来解决这个问题,这里我们可以尝试分析一下findViewWithTag的工作原理,其实顾名思义,这个方法就是通过Tag的名字来获取具备该Tag名的控件,我们先要调用控件的setTag()方法来给控件设置一个Tag,然后再调用ListView的findViewWithTag()方法使用相同的Tag名来找回控件。
那么为什么用了findViewWithTag()方法之后,图片就不会再出现乱序情况了呢?其实原因很简单,由于ListView中的ImageView控件都是重用的,移出屏幕的控件很快会被进入屏幕的图片重新利用起来,那么getView()方法就会再次得到执行,而在getView()方法中会为这个ImageView控件设置新的Tag,这样老的Tag就会被覆盖掉,于是这时再调用findVIewWithTag()方法并传入老的Tag,就只能得到null了,而我们判断只有ImageView不等于null的时候才会设置图片,这样图片乱序的问题也就不存在了

0 0
原创粉丝点击