設置Tag防止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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章