android listview 异步加载图片并防止错位

网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作.

如果不重用 convertView 不会出现错位现象, 重用 convertView 但没有异步操作也不会有问题。

我简单分析一下:

当重用 convertView 时,最初一屏显示 7 条记录, getView 被调用 7 次,创建了 7 个 convertView.

当 Item1 划出屏幕, Item8 进入屏幕时,这时没有为 Item8 创建新的 view 实例, Item8 复用的是

Item1 的 view 如果没有异步不会有任何问题,虽然 Item8 和 Item1 指向的是同一个 view,但滑到

Item8 时刷上了 Item8 的数据,这时 Item1 的数据和 Item8 是一样的,因为它们指向的是同一块内存,

但 Item1 已滚出了屏幕你看不见。当 Item1 再次可见时这块 view 又涮上了 Item1 的数据。

但当有异步下载时就有问题了,假设 Item1 的图片下载的比较慢,Item8 的图片下载的比较快,你滚上去

使 Item8 可见,这时 Item8 先显示它自己下载的图片没错,但等到 Item1 的图片也下载完时你发现

Item8 的图片也变成了 Item1 的图片,因为它们复用的是同一个 view。 如果 Item1 的图片下载的比

Item8 的图片快, Item1 先刷上自己下载的图片,这时你滑下去,Item8 的图片还没下载完, Item8

会先显示 Item1 的图片,因为它们是同一快内存,当 Item8 自己的图片下载完后 Item8 的图片又刷成

了自己的,你再滑上去使 Item1 可见, Item1 的图片也会和 Item8 的图片是一样的,

因为它们指向的是同一块内存。

最简单的解决方法就是网上说的,给 ImageView 设置一个 tag, 并预设一个图片。

当 Item1 比 Item8 图片下载的快时, 你滚下去使 Item8 可见,这时 ImageView 的 tag 被设成了

Item8 的 URL, 当 Item1 下载完时,由于 Item1 不可见现在的 tag 是 Item8 的 URL,所以不满足条件,

虽然下载下来了但不会设置到 ImageView 上, tag 标识的永远是可见 view 中图片的 URL。

关键代码如下:

// 给 ImageView 设置一个 tag
holder.img.setTag(imgUrl);
// 预设一个图片
holder.img.setImageResource(R.drawable.ic_launcher);

// 通过 tag 来防止图片错位
if (imageView.getTag() != null && imageView.getTag().equals(imageUrl)) {
    imageView.setImageBitmap(result);
}

我参考网上资料写了一个 listview 异步加载图片的 DEMO:

(1) AsyncTask 下载图片

(2) 实现内存、文件二级缓存

内存缓存使用 LruCache,文件缓存使用 DiskLruCache

/**
 * 图片异步加载类
 *
 * @author Leslie.Fang
 *
 */
public class AsyncImageLoader {
    private Context context;
    // 内存缓存默认 5M
    static final int MEM_CACHE_DEFAULT_SIZE = 5 * 1024 * 1024;
    // 文件缓存默认 10M
    static final int DISK_CACHE_DEFAULT_SIZE = 10 * 1024 * 1024;
    // 一级内存缓存基于 LruCache
    private LruCache<String, Bitmap> memCache;
    // 二级文件缓存基于 DiskLruCache
    private DiskLruCache diskCache;

    public AsyncImageLoader(Context context) {
        this.context = context;
        initMemCache();
        initDiskLruCache();
    }

    /**
     * 初始化内存缓存
     */
    private void initMemCache() {
        memCache = new LruCache<String, Bitmap>(MEM_CACHE_DEFAULT_SIZE) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getByteCount();
            }
        };
    }

    /**
     * 初始化文件缓存
     */
    private void initDiskLruCache() {
        try {
            File cacheDir = getDiskCacheDir(context, "bitmap");
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            diskCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, DISK_CACHE_DEFAULT_SIZE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 从内存缓存中拿
     *
     * @param url
     */
    public Bitmap getBitmapFromMem(String url) {
        return memCache.get(url);
    }

    /**
     * 加入到内存缓存中
     *
     * @param url
     * @param bitmap
     */
    public void putBitmapToMem(String url, Bitmap bitmap) {
        memCache.put(url, bitmap);
    }

    /**
     * 从文件缓存中拿
     *
     * @param url
     */
    public Bitmap getBitmapFromDisk(String url) {
        try {
            String key = hashKeyForDisk(url);
            DiskLruCache.Snapshot snapShot = diskCache.get(key);
            if (snapShot != null) {
                InputStream is = snapShot.getInputStream(0);
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                return bitmap;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 从 url 加载图片
     *
     * @param imageView
     * @param imageUrl
     */
    public Bitmap loadImage(ImageView imageView, String imageUrl) {
        // 先从内存中拿
        Bitmap bitmap = getBitmapFromMem(imageUrl);

        if (bitmap != null) {
            Log.i("leslie", "image exists in memory");
            return bitmap;
        }

        // 再从文件中找
        bitmap = getBitmapFromDisk(imageUrl);
        if (bitmap != null) {
            Log.i("leslie", "image exists in file");
            // 重新缓存到内存中
            putBitmapToMem(imageUrl, bitmap);
            return bitmap;
        }

        // 内存和文件中都没有再从网络下载
        if (!TextUtils.isEmpty(imageUrl)) {
            new ImageDownloadTask(imageView).execute(imageUrl);
        }

        return null;
    }

    class ImageDownloadTask extends AsyncTask<String, Integer, Bitmap> {
        private String imageUrl;
        private ImageView imageView;

        public ImageDownloadTask(ImageView imageView) {
            this.imageView = imageView;
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            try {
                imageUrl = params[0];
                String key = hashKeyForDisk(imageUrl);
                // 下载成功后直接将图片流写入文件缓存
                DiskLruCache.Editor editor = diskCache.edit(key);
                if (editor != null) {
                    OutputStream outputStream = editor.newOutputStream(0);
                    if (downloadUrlToStream(imageUrl, outputStream)) {
                        editor.commit();
                    } else {
                        editor.abort();
                    }
                }
                diskCache.flush();

                Bitmap bitmap = getBitmapFromDisk(imageUrl);
                if (bitmap != null) {
                    // 将图片加入到内存缓存中
                    putBitmapToMem(imageUrl, bitmap);
                }

                return bitmap;
            } catch (IOException e) {
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Bitmap result) {
            super.onPostExecute(result);
            if (result != null) {
                // 通过 tag 来防止图片错位
                if (imageView.getTag() != null && imageView.getTag().equals(imageUrl)) {
                    imageView.setImageBitmap(result);
                }
            }
        }

        private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
            HttpURLConnection urlConnection = null;
            BufferedOutputStream out = null;
            BufferedInputStream in = null;
            try {
                final URL url = new URL(urlString);
                urlConnection = (HttpURLConnection) url.openConnection();
                in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
                out = new BufferedOutputStream(outputStream, 8 * 1024);
                int b;
                while ((b = in.read()) != -1) {
                    out.write(b);
                }
                return true;
            } catch (final IOException e) {
                e.printStackTrace();
            } finally {
                if (urlConnection != null) {
                    urlConnection.disconnect();
                }
                try {
                    if (out != null) {
                        out.close();
                    }
                    if (in != null) {
                        in.close();
                    }
                } catch (final IOException e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    }

    private File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

    private int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

    private String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }
}

listview异步加载图片并防止错位的更多相关文章

  1. android listview 异步加载图片并防止错位

    网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 convertView 不会出现错位现象, 重用 convertVie ...

  2. Android的ListView异步加载图片时,错位、重复、闪烁问题的分析及解决方法

    Android ListView异步加载图片错位.重复.闪烁分析以及解决方案,具体问题分析以及解决方案请看下文. 我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图 ...

  3. listview 异步加载图片并防止错位

    1.图片错位原理: 如果我们只是简单显示list中数据,而没用convertview的复用机制和异步操作,就不会产生图片错位:重用convertview但没用异步,也不会有错位现象.但我们的项目中li ...

  4. Android中ListView异步加载图片错位、重复、闪烁问题分析及解决方案

    我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图片错位.重复.闪烁等问题,其实这些问题总结起来就是一个问题,我们需要对这些问题进行ListView的优化. 比如L ...

  5. ListView异步加载图片,完美实现图文混排

    昨天参加一个面试,面试官让当场写一个类似于新闻列表的页面,文本数据和图片都从网络上获取,想起我还没写过ListView异步加载图片并实现图文混排效果的文章,so,今天就来写一下,介绍一下经验. Lis ...

  6. Android 实现ListView异步加载图片

    ListView异步加载图片是非常实用的方法,凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,下面就说实现方法,先贴上主方法的代码: package cn.wangmeng.test; ...

  7. 又优化了一下 Android ListView 异步加载图片

    写这篇文章并不是教大家怎么样用listview异步加载图片,因为这样的文章在网上已经有很多了,比如这位仁兄写的就很好: http://www.iteye.com/topic/685986 我也是因为看 ...

  8. ListView异步加载图片

    ListView异步加载图片是非常实用的方法,凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,下面就说实现方法,先贴上主方法的代码: package cn.wangmeng.test; ...

  9. Listview 异步加载图片之优化篇(有图有码有解释)

    在APP应用中,listview的异步加载图片方式能够带来很好的用户体验,同时也是考量程序性能的一个重要指标.关于listview的异步加载,网上其实很多示例了,中心思想都差不多,不过很多版本或是有b ...

随机推荐

  1. 百钱买百鸡问题Java

    //百钱买百鸡public class baiqianbaiji { static void BQBJ(int m,int n)//m为钱的总数,n为鸡数 { int z; for(int x = 0 ...

  2. Docker内核能力机制

    能力机制(Capability)是 Linux 内核一个强大的特性,可以提供细粒度的权限访问控制. Linux 内核自 2.2 版本起就支持能力机制,它将权限划分为更加细粒度的操作能力,既可以作用在进 ...

  3. 衣带渐宽终不悔,为伊消得人憔悴--DbHelper增强版

    核心理念 如何使用 测试实例 数据库内详细数据信息 测试代码 数据库连接池测试 测试集 延伸 相关下载链接 前几日,写了一篇关于一个 轻量级数据持久化的框架的博客(点击浏览: http://blog. ...

  4. ROS(indigo) 用于机器人控制的图形化编程工具--code_it robot_blockly

    0 简介: 编程语言有汇编,高级语言,解释语言等,现在图形化编程也越来越流行.图形化编程简单易学.8年前,微软推出了VPL用于机器人程序设计,如Python和JavaScript都可以用图形化框图实现 ...

  5. Redis集群教程(Redis cluster tutorial)

    本博文翻译自Redis官网:http://redis.io/topics/cluster-tutorial        本文档以温和的方式介绍Redis集群,不使用复杂的方式来理解分布式系统的概念. ...

  6. SpriteKit中的共享动作(Sharing Actions)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 在SpriteKit中某些动作需要一些额外的延时,如果每次都重 ...

  7. mysql进阶(二十七)数据库索引原理

    mysql进阶(二十七)数据库索引原理 前言   本文主要是阐述MySQL索引机制,主要是说明存储引擎Innodb.   第一部分主要从数据结构及算法理论层面讨论MySQL数据库索引的数理基础.    ...

  8. x264源代码简单分析:x264_slice_write()

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  9. 阻尼回弹效果的ScrollView嵌套GridView

    以前写过一篇带阻尼回弹效果的ScrollView,但是有些小问题,于是又重新整理了一下,这篇文章一是一个带阻尼的Scrollview,再个就是Scrollview嵌套GridView实现,而GridV ...

  10. 带你深入理解STL之迭代器和Traits技法

    在开始讲迭代器之前,先列举几个例子,由浅入深的来理解一下为什么要设计迭代器. //对于int类的求和函数 int sum(int *a , int n) { int sum = 0 ; for (in ...