• 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();
        }
    }
    复制代码

    代码地址: https://github.com/lesliebeijing/AsyncImageLoader.git

    如果使用  Volley 就简单的多了

    同一个 URL 请求的重复发送,退出 activity 后队列中请求的 cancel,(上面的 demo 没有处理这两种情况)

    图片的缓存等 都不用自己处理了,  Volley 都封装好了。

    Volley ListView 异步加载图片  demo:  https://github.com/lesliebeijing/VolleyListViewImageDemo.git

  • 相关阅读:
    Python3爬虫系列:理论+实验+爬取妹子图实战
    虚机安装后无网卡、网卡驱动
    Linux运维工程师面试题整理
    睡眠或者重启windows,无法ssh连接或者pingVMware的虚机
    W10: Warning: Changing a readonly file使用vi/vim报错问题解决
    keyboard-interactive authentication with the ssh2 server failed 的SecureCRT报错解决
    公网访问内网实现(内网穿透)
    Linux内网时钟同步问题(ntp和chrony)
    xshell的快捷复制粘贴设置
    Linux中shell去除空行的几种方法
  • 原文地址:https://www.cnblogs.com/annieBaby/p/4905536.html
Copyright © 2020-2023  润新知