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

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();
    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏developerHaoz 的安卓之旅

Android 打造一个丝滑的自动轮播控件

现在很多的 App 都有自动轮播的 banner 界面,用于展示广告图片或者显示当前比较热门的一些活动,除了具备比较酷炫的效果之外,通过轮播的方式来减少对界面的...

14420
来自专栏Android干货

项目实战工具类(一):PhoneUtil(手机信息相关)

21150
来自专栏分享达人秀

GridView属性和使用方法

前面一共用了8期来学习ListView列表的相关操作,其实学习的ListView的知识完全适用于AdapterView的其他子类,如GridView、S...

33270
来自专栏刘望舒

打造一个灵活易用的Banner组件

之前做项目时候出于各种考虑,自己开发了Banner组件FBanner,欢迎大家的Star和PR。github上成熟的轮播图库已经有非常多了,比如banner和A...

14050
来自专栏向治洪

gradeview可拖动效果实现

下面先上这次实现功能的效果图:(注:这个效果图没有拖拽的时候移动动画,DEMO里面有,可以下载看看) ? 一、开发心里历程 刚开始接触这个的时候,不知道要如何实...

35280
来自专栏刘望舒

RxBinding使用和源码解析

作者 | juexingzhe 地址 | https://www.jianshu.com/u/ea71bb3770b4 声明 | 本文是 juexingzhe...

466100
来自专栏程序员的诗和远方

看代码学AndroidUI - Tab

最近慢慢学习一点安卓,先看了些基础的,还处于很初级的阶段,平常都是面对弱类型的语言,python,js,现在看java突然有点不适应。 这里推荐郭神的《第一行代...

33790
来自专栏向治洪

SwipeListView实现仿ios的侧滑

github地址:https://github.com/xiangzhihong/SwipeMenuListView 今天介绍一个SwipeMenuListVi...

28280
来自专栏指尖下的Android

Matisse预览图片黑屏,Glide内存溢出

项目中要到图片、视频选择的功能,然后google了一下,找到Matisse,知乎的图片选择框架,用的人还挺多的,果断依赖gradle,然后开始我的踩坑之旅。 ...

27110
来自专栏mukekeheart的iOS之旅

android service 学习(上)

android service 学习(上) Service是android 系统中的一种组件,它跟Activity的级别差不多,但是他不能自己运行,只能后...

26360

扫码关注云+社区

领取腾讯云代金券