zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    python 保存两位小数
    python 临时文件
    Windows 10 右键 在此处打开 CMD
    windows 10 右键菜单注册表位置
    GoLand 设置与配置
    docker 相关指令
    ubuntu下python版本间切换
    gitLab本地推送到远程仓库的命令
    CSS的性能优化
    Web前端:11个让你代码整洁的原则
  • 原文地址:https://www.cnblogs.com/lesliefang/p/3619223.html
Copyright © 2011-2022 走看看