结合LruCache和DiskLruCache高效加载图片

很多android小白在第一次独立写项目的时候,一定会遇到一个头疼的问题,那就是图片。关于大部分的问题,在我的上一篇博客你真的会用ListView吗?中都讲到了,但是再往深处想,我们采用的LruCache缓存技术,是基于运行中的内存,于是在关掉程序后再次打开,图片还是得继续下载。那我们就将缓存的数据放在手机存储中啊,是的,DiskLruCache完美解决了这个问题,并且每一个app都有一个默认的缓存目录,在app卸载的时候回一并删除,这样就不用担心数据残留的问题了。

基于上一篇博客,我们要做的是完善ImageAdapter的代码:

package com.cjt_pc.listviewtest;

/**
 * Created by cjt-pc on 2015/10/13.
 * Email:879309896@qq.com
 */

import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.support.v4.util.LruCache;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import libcore.io.DiskLruCache;

public class ImageAdapter extends ArrayAdapter<String> implements AbsListView.OnScrollListener {

    // 记录异步加载任务的集合
    List<BitmapTask> taskList = new ArrayList<>();
    // item布局id
    private int resource;
    // 绑定的控件ListView
    private ListView mListView;
    // 内存缓存,以键值的方式存储
    private LruCache<String, Bitmap> mMemoryCache;
    // 硬盘缓存,不能实例化
    private DiskLruCache mDiskLruCache;
    // 第一个可见项index
    private int mFirstVisibleItem;
    // 屏幕中可见项的数目count
    private int mVisibleItemCount;
    // 避免第一次进入不加载图片,设置bool参数
    private boolean isFirstEnter = true;

    public ImageAdapter(Context context, int resource, String[] objects) {
        super(context, resource, objects);
        this.resource = resource;
        // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
        // LruCache通过构造函数传入缓存值,以KB为单位。
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // 使用最大可用内存值的1/8作为缓存的大小。
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // 重写此方法来衡量每张图片的大小,默认返回图片数量。
                return bitmap.getByteCount() / 1024;
            }
        };
        try {
            // 获取图片缓存路径
            File cacheDir = SystemUtil.getDiskCacheDir(context, "thumb");
            if (!cacheDir.exists() && cacheDir.mkdirs()) {
                Log.d("cjt-pc", "创建文件夹成功,路劲是:" + cacheDir.getAbsolutePath());
            }
            // 创建DiskLruCache实例,初始化缓存数据
            mDiskLruCache = DiskLruCache
                    .open(cacheDir, SystemUtil.getAppVersion(context), 1, 10 * 1024 * 1024);
            Log.d("cjt-pc", "硬盘缓存大小为:" + mDiskLruCache.size());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 添加bitmap到lruCache缓存中
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    // 从lruCache缓存中获取bitmap
    public Bitmap getBitmapFromMemCache(String key) {
        return mMemoryCache.get(key);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        mListView = (ListView) parent;
        String url = getItem(position);
        ViewHolder viewHolder;
        if (convertView == null) {
            convertView = LayoutInflater.from(getContext()).inflate(resource, null);
            viewHolder = new ViewHolder();
            viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        // 给imageView设置一个唯一标签Tag,避免图片显示混乱
        viewHolder.imageView.setTag(url);
        // 初始化imageView,防止在异步加载时出现上一张图片
        viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);
        loadBitmap(url, viewHolder.imageView);
        return convertView;
    }

    // 通过lruCache缓存技术,快速重新加载和处理图片
    public void loadBitmap(String imageKey, ImageView imageView) {
        Bitmap bitmap = getBitmapFromMemCache(imageKey);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
        }
    }

    @Override
    public void onScrollStateChanged(AbsListView absListView, int i) {
        // 仅当ListView静止时才去下载图片,GridView滑动时取消所有正在下载的任务
        if (i == SCROLL_STATE_IDLE) {
            loadBitmaps(mFirstVisibleItem, mVisibleItemCount);
        } else {
            cancelAllTasks();
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                         int totalItemCount) {
        mFirstVisibleItem = firstVisibleItem;
        mVisibleItemCount = visibleItemCount;
        // 下载的任务应该由onScrollStateChanged里调用,但首次进入程序时onScrollStateChanged并不会调用,
        // 因此在这里为首次进入程序开启下载任务。
        if (isFirstEnter && visibleItemCount > 0) {
            loadBitmaps(firstVisibleItem, visibleItemCount);
            isFirstEnter = false;
        }
    }

    /**
     * 加载Bitmap对象。此方法会在LruCache中检查所有屏幕中可见的ImageView的Bitmap对象,
     * 如果发现任何一个ImageView的Bitmap对象不在缓存中,就会开启异步线程去下载图片。
     *
     * @param firstVisibleItem 第一个可见的ImageView的下标
     * @param visibleItemCount 屏幕中总共可见的元素数
     */
    private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {
        try {
            for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
                String imageUrl = Images.imageUrls[i];
                Bitmap bitmap = getBitmapFromMemCache(imageUrl);
                if (bitmap == null) {
                    BitmapTask task = new BitmapTask();
                    taskList.add(task);
                    task.execute(imageUrl);
                } else {
                    ImageView imageView = (ImageView) mListView.findViewWithTag(imageUrl);
                    if (imageView != null) {
                        imageView.setImageBitmap(bitmap);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 将缓存记录同步到journal文件中。
     */
    public void fluchCache() {
        if (mDiskLruCache != null) {
            try {
                mDiskLruCache.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void cancelAllTasks() {
        if (!taskList.isEmpty()) {
            // 遍历任务集合,取消正在进行的异步任务,然后清空任务列表
            for (BitmapTask task : taskList) {
                task.cancel(true);
            }
            taskList.clear();
        }
    }

    class ViewHolder {
        ImageView imageView;
    }


    /**
     * 异步下载图片的任务。
     *
     * @author cjt-pc
     */
    class BitmapTask extends AsyncTask<String, Void, Bitmap> {

        private String url;

        @Override
        protected Bitmap doInBackground(String... strings) {
            url = strings[0];
            InputStream is = null;
            Bitmap bitmap = null;
            DiskLruCache.Snapshot snapShot;
            try {
                // 生成图片URL对应的key
                String key = SystemUtil.hashKeyForDisk(url);
                // 查找key对应的缓存
                snapShot = mDiskLruCache.get(key);
                if (snapShot == null) {
                    is = new URL(url).openStream();
                    // 如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
                    DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                    // 将InputStream转换成OutputStream,然后提交到缓存中
                    OutputStream os = editor.newOutputStream(0);
                    int length;
                    byte[] buffer = new byte[1024];
                    while ((length = is.read(buffer)) != -1) {
                        os.write(buffer, 0, length);
                    }
                    editor.commit();
                    // 注意这里is在read(buffer)之后会发生改变,所以要重新获取一次
                    is = new URL(url).openStream();
                } else {
                    is = snapShot.getInputStream(0);
                }
                // 注意这里没有直接用decodeStream,是因为由于本身的原因这样极其不稳定
                byte[] bytes = PictureCompressUtil.readStream(is);
                // bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                bitmap = PictureCompressUtil.decodeSampledBitmapFromByteArray(bytes, 1440, 300);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bitmap != null) {
                addBitmapToMemoryCache(url, bitmap);
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            ImageView imageView = (ImageView) mListView.findViewWithTag(url);
            if (imageView != null && bitmap != null) {
                imageView.setImageBitmap(bitmap);
            }
            // 要记得在异步加载过程完成后将本次任务移出
            taskList.remove(this);
        }
    }

}

这里面采用了一个工具类,SystemUtil.class:

package com.cjt_pc.listviewtest;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Environment;

import java.io.File;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Created by cjt-pc on 2015/10/14.
 * Email:879309896@qq.com
 */
public class SystemUtil {

    // 得到当前版本号
    public static int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

    // 通过唯一的名字在指定的缓存目录下创建临时文件
    public static File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && context.getExternalCacheDir() != null) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

    // 传入指定的key,返回类型为MD5的String
    public static 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 static String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xFF & b);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }
}

这样,我们就不用担心在程序退出后下载完成的图片都被释放掉了,用户体验摆在首位。

源码下载:http://download.csdn.net/detail/qq_15002323/9180993

参考资料:
Android DiskLruCache完全解析,硬盘缓存的最佳方案
Android照片墙完整版,完美结合LruCache和DiskLruCache

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值