ListView异步加载图片分析

一.概述

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

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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值