Android通过两级缓存加载图片

来源:互联网 发布:word vba编程代码大全 编辑:程序博客网 时间:2024/06/05 10:55

Android通过两级缓存加载图片

前一段时间刚好学习用两级缓存(LruCache和DiskLruCache)加载图片问题,在书上看到一个解决方案思路很清晰易懂,在这里记录一下。

整体思路:
当程序从网络加载图片后,为了节省流量,将其缓存到存储设备上,为了提高用户体验通常还会缓存一份到内存,这样当程序再次请求图片时,首先从内存获取,如果内存中没有就从存储设备中获取,如果存储设备中也没有再从网络上获取。

一、采样率

首先在加载图片的时候涉及到一个问题,就是图片尺寸问题,很多时候ImageView并没有图片原始的尺寸那么大,所以把整个图片加载进来再给ImageView是没有必要的,通过BitmapFactory.Options可以按一定的采样率来加载缩小后的图片,这样就会降低内存的占用,从而在一定程度上避免OOM。

下面是计算采样率的代码,相关解释已经都写在了注释中了:

public class ImageResizer {    private static final String TAG = "ImageResizer";    /**     * 该方法主要用到了BitmapFactory.Options中的采样率inSampleSize参数     * inSampleSize = 1时,采样后图片大小为图片的原始大小;     * inSampleSize = 2时,采样后的图片宽高均为原图的1/2,像素数为原图的1/4,占用内存也是原来的1/4;     * inSampleSize < 1时,作用相当于1,即无缩放效果;     * inSampleSize的取值总是2的指数,如果传入的值不是2的指数,     * 系统会向下取整并选择一个接近2的指数代替,但不是所有的android版本都成立;     * @param res   资源值     * @param resId 需要加载的图片id     * @param reqWidth  需要加载的图片宽度     * @param reqHeight 需要加载的图片高度     * @return  根据实际需要缩放后的图片     */    public static Bitmap decodeSampledBitmapFromResource(            Resources res, int resId, int reqWidth, int reqHeight) {        //通过BitmapFactory.Options来加载所需尺寸的图片        final BitmapFactory.Options options = new BitmapFactory.Options();        /**         *当inJustDecodeBounds=true时,         * BitmapFactory只会解析图片的原始宽/高信息,         * 不会真正加载图片         */        options.inJustDecodeBounds = true;        BitmapFactory.decodeResource(res, resId, options);        //计算inSampleSize        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);        options.inJustDecodeBounds = false;        return BitmapFactory.decodeResource(res, resId, options);    }    public Bitmap decodeSampleBitmapFromFielDescriptor(            FileDescriptor fd, int reqWidth, int reqHeight){        final BitmapFactory.Options options = new BitmapFactory.Options();        options.inJustDecodeBounds = true;        BitmapFactory.decodeFileDescriptor(fd, null, options);        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);        options.inJustDecodeBounds = false;        return BitmapFactory.decodeFileDescriptor(fd, null, options);    }    /**     * 根据原始尺寸和需要尺寸计算一个合适的采样率     * @param options     * @param reqWidth     * @param reqHeight     * @return     */    public static int calculateInSampleSize(            BitmapFactory.Options options, int reqWidth, int reqHeight) {        //图片的尺寸        final int height = options.outHeight;        final int width = options.outWidth;        int inSampleSize = 1;        /*if (height > reqHeight || width > reqWidth) {            final int halfHeight = height / 2;            final int halfWidth = width / 2;            while ((halfHeight / inSampleSize) >= reqHeight                    && (halfWidth / inSampleSize) >= reqWidth) {                inSampleSize *= 2;            }        }*/        while (height/inSampleSize > reqHeight && width/inSampleSize > reqWidth){            inSampleSize *= 2;        }        return inSampleSize;    }}

二、两级缓存的使用

1、LruCache

LruCache是Android3.1提供的一个缓存类,通过support-v4兼容包可以兼容到早期版本,不过现在Android3.1以下的版本好像几乎没有了。。。。,LruCache是泛型类,其内部采用LinkedHashMap以强引用的方式存储外界缓存对象,如果不了解强引用、软引用、弱引用、虚引用的请自行google/百度。

/* LruCache初始化*///当前进程可用内存,单位KBint maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);//设置缓存大小为当前进程可用内存的1/8int cacheSize = maxMemory / 8;mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {    @Override    protected int sizeOf(String key, Bitmap value) {        return value.getRowBytes() * value.getHeight() / 1024;    }};

2、DiskLruCache

DiskLruCache不属于android sdk的一部分,所以需要自行下载源码并稍微做一下改动就可使用,后面我会附上我使用的源码。

/*DiskLruCache初始化*/File diskCacheDir = getDiskCacheDir(mContext, "bitmap");//如果目录不存在则创建目录if (!diskCacheDir.exists()) {    diskCacheDir.mkdirs();}if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {    try {        mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);        mIsDiskLruCacheCreated = true;    } catch (IOException e) {        e.printStackTrace();    }}

完整代码如下:

public class ImageLoader {    private static final String TAG = "ImageLoader";    public static final int MESSAGE_POST_RESULT = 1;    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;    private static final long KEEP_ALIVE = 10L;    /*指定的密钥应该是在应用程序的资源中声明的id,    以确保它是唯一的(请参阅ID资源类型)。    标识为属于Android框架的键或与任何程序包无关的键将导致IllegalArgumentException被抛出*/    private static final int TAG_KEY_URI = R.id.imageloader_uri;    private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;    private static final int IO_BUFFER_SIZE = 8 * 1024;    private static final int DISK_CACHE_INDEX = 0;    private boolean mIsDiskLruCacheCreated = false;    private static final ThreadFactory sThreadFactory = new ThreadFactory() {        private final AtomicInteger mCount = new AtomicInteger(1);        @Override        public Thread newThread(@NonNull Runnable r) {            return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());        }    };    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS,            new LinkedBlockingQueue<Runnable>(), sThreadFactory);    private Context mContext;    private ImageResizer mImageResizer = new ImageResizer();    private LruCache<String, Bitmap> mMemoryCache;            //内部实现是LinkedHashMap    private DiskLruCache mDiskLruCache;    private Handler mMainHandler = new Handler(Looper.getMainLooper()) {        @Override        public void handleMessage(Message msg) {            LoaderResult result = (LoaderResult) msg.obj;            ImageView imageView = result.imageView;            String uri = (String) imageView.getTag(TAG_KEY_URI);            if (result.uri.equals(uri)) {                imageView.setImageBitmap(result.bitmap);            } else {                Log.w(TAG, "bitmap的uri被修改");            }        }    };    private ImageLoader(Context context) {        mContext = context.getApplicationContext();        /*         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 value) {                return value.getRowBytes() * value.getHeight() / 1024;            }        };        /*        DiskLruCache初始化        */        File diskCacheDir = getDiskCacheDir(mContext, "bitmap");        //如果目录不存在则创建目录        if (!diskCacheDir.exists()) {            diskCacheDir.mkdirs();        }        if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {            try {                mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);                mIsDiskLruCacheCreated = true;            } catch (IOException e) {                e.printStackTrace();            }        }    }    /**     * 创建一个ImageLoader实例     *     * @param context     * @return     */    public static ImageLoader build(Context context) {        return new ImageLoader(context);    }    public void bindBitmap(final String uri, final ImageView imageView) {        bindBitmap(uri, imageView, 0, 0);    }    /**     * 给ImageView添加相应的图片     *     * @param uri     * @param imageView     * @param reqWidth     * @param reqHeight     */    public void bindBitmap(            final String uri, final ImageView imageView,            final int reqWidth, final int reqHeight) {        imageView.setTag(TAG_KEY_URI, uri);        Bitmap bitmap = loadBitmapFromMemCache(uri);        if (bitmap != null) {            imageView.setImageBitmap(bitmap);            return;        }        Runnable loadBitmapTask = new Runnable() {            @Override            public void run() {                Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);                if (bitmap != null) {                    LoaderResult result = new LoaderResult(imageView, uri, bitmap);                    mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();                }            }        };        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);    }    /**     * 从内存、硬盘或者网络中加载图片     * 如果内存中能找到,返回bitmap,     * 如果内存中找不到,从硬盘中加载;     * 如果硬盘中能找到,返回bitmap,     * 如果硬盘中找不到,从网络中加载;     * 如果创建了磁盘缓存,则将图片下载到硬盘中,     * 如果没有创建磁盘缓存,则直接将图片加载到内存中     *     * @param uri     * @param reqWidth     * @param reqHeight     * @return     */    public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {        Bitmap bitmap = loadBitmapFromMemCache(uri);        if (bitmap != null) {            return bitmap;        }        try {            bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);            if (bitmap != null) {                return bitmap;            }            bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);        } catch (IOException e) {            e.printStackTrace();        }        if (bitmap == null && !mIsDiskLruCacheCreated) {            downloadBitmapFromUrl(uri);        }        return bitmap;    }    /**     * 从根据图片的哈希码内存缓存中取出图片     *     * @param key     * @return     */    private Bitmap getBitmapFromMemCache(String key) {        return mMemoryCache.get(key);    }    /**     * 如果内存缓存中没有图片,向其中加入该图片     *     * @param key     * @param bitmap     */    private void addBitmapToMemCache(String key, Bitmap bitmap) {        if (getBitmapFromMemCache(key) == null) {            mMemoryCache.put(key, bitmap);        }    }    /**     * 从内存缓存中加载图片     *     * @param url     * @return     */    private Bitmap loadBitmapFromMemCache(String url) {        final String key = hashKeyFormString(url);        return getBitmapFromMemCache(key);    }    /**     * 从磁盘缓存中加载图片     *     * @param url     * @param reqWidth     * @param reqHeight     * @return     * @throws IOException     */    private Bitmap loadBitmapFromDiskCache(            String url, int reqWidth, int reqHeight) throws IOException {        if (Looper.myLooper() == Looper.getMainLooper()) {            Log.w(TAG, "不推荐使用UI线程加载图片");        }        if (mDiskLruCache == null) {            return null;        }        Bitmap bitmap = null;        String key = hashKeyFormString(url);        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);        if (snapShot != null) {            FileInputStream fileInputStream =                    (FileInputStream) snapShot.getInputStream(DISK_CACHE_INDEX);            FileDescriptor fileDescriptor = fileInputStream.getFD();            bitmap = mImageResizer.decodeSampleBitmapFromFielDescriptor(                    fileDescriptor, reqWidth, reqHeight);            if (bitmap != null) {                addBitmapToMemCache(key, bitmap);            }        }        return bitmap;    }    /**     * 从网络中加载图片     *     * @param url       图片地址     * @param reqWidth  所需宽度     * @param reqHeight 所需高度     * @return 所需的图片     * @throws IOException     */    private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight) throws IOException {        if (Looper.myLooper() == Looper.getMainLooper()) {            throw new RuntimeException("不能在主线程中进行联网操作!");        }        if (mDiskLruCache == null) {            return null;        }        String key = hashKeyFormString(url);        DiskLruCache.Editor editor = mDiskLruCache.edit(key);        if (editor != null) {            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);            if (downloadUrlToStream(url, outputStream)) {                editor.commit();            } else {                editor.abort();            }            mDiskLruCache.flush();        }        return loadBitmapFromDiskCache(url, reqWidth, reqHeight);    }    /**     * 将网络图片通过输出流输出     *     * @param urlString    需要下载图片的地址     * @param outputStream 输出流     * @return 下载成功返回真,否则返回假     */    public 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(), IO_BUFFER_SIZE);            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);            int n;            while ((n = in.read()) != -1) {                out.write(n);            }            return true;        } catch (IOException e) {            e.printStackTrace();        } finally {            if (urlConnection != null) {                urlConnection.disconnect();            }            try {                if (in != null) {                    in.close();                }                if (out != null) {                    out.close();                }            } catch (IOException e) {                e.printStackTrace();            }        }        return false;    }    /**     * 直接将网络中的图片加载到内存中,赋值给bitmap变量     *     * @param urlString 网络图片的地址     * @return 返回下载的图片     */    private Bitmap downloadBitmapFromUrl(String urlString) {        Bitmap bitmap = null;        HttpURLConnection urlConnection = null;        BufferedInputStream in = null;        try {            final URL url = new URL(urlString);            urlConnection = (HttpURLConnection) url.openConnection();            in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);            bitmap = BitmapFactory.decodeStream(in);        } catch (IOException e) {            e.printStackTrace();        } finally {            if (urlConnection != null) {                urlConnection.disconnect();            }            try {                if (in != null) {                    in.close();                }            } catch (IOException e) {                e.printStackTrace();            }        }        return bitmap;    }    /**     * 获取储存图片的路径,如果挂载了sd卡则目录为sd卡的路径,否则用缓存     *     * @param context    上下文     * @param uniqueName 文件名     * @return 返回文件     */    public File getDiskCacheDir(Context context, String uniqueName) {        //判断是否挂载sd卡        boolean externalStorageAvailable =                Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);        final String cachePath;        if (externalStorageAvailable) {    //如果挂载了sd卡,则使用sd卡的路径            cachePath = context.getExternalCacheDir().getPath();        } else {                            //没有挂载sd卡,使用缓存中的路径            cachePath = context.getCacheDir().getPath();        }        return new File(cachePath + File.separator + uniqueName);    }    /**     * 得到可用空间的大小     *     * @param path 文件     * @return 可用空间大小     */    private long getUsableSpace(File path) {        //当api<9时使用如下方法        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {            final StatFs statFs = new StatFs(path.getPath());            return (long) statFs.getBlockSize() * (long) statFs.getAvailableBlocks();        }        return path.getUsableSpace();    }    /**     * 将字符串转换成MD5码     *     * @param str 待转换的字符串     * @return 转换成功返回MD5码,否则返回哈希码     */    private String hashKeyFormString(String str) {        String cacheKey;        try {            final MessageDigest mDigest = MessageDigest.getInstance("MD5");            mDigest.update(str.getBytes());            cacheKey = bytesToHexString(mDigest.digest());        } catch (NoSuchAlgorithmException e) {            cacheKey = String.valueOf(str.hashCode());        }        return cacheKey;    }    /**     * 将字节数组转换成16进制形式字符串     *     * @param bytes 字节数组     * @return 16进制形式字符串     */    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();    }    private static class LoaderResult {        public ImageView imageView;        public String uri;        public Bitmap bitmap;        public LoaderResult(ImageView imageView, String uri, Bitmap bitmap) {            this.imageView = imageView;            this.uri = uri;            this.bitmap = bitmap;        }    }}

附件下载:

DiskLruCache源码

原创粉丝点击