Bitmap之内存缓存和磁盘缓存详解

Bitmap之内存缓存和磁盘缓存详解,第1张

概述原文首发于微信公众号:躬行之(jzman blog) Android 中缓存的使用比较普遍,使用相应的缓存策略可以减少流量的消耗,也可以在一定程度上提高应用的性能,如加载网络图片的情况,不应该每次都从

原文首发于微信公众号:躬行之(jzman-blog)

@H_301_3@

AndroID 中缓存的使用比较普遍,使用相应的缓存策略可以减少流量的消耗,也可以在一定程度上提高应用的性能,如加载网络图片的情况,不应该每次都从网络上加载图片,应该将其缓存到内存磁盘中,下次直接从内存或磁盘中获取,缓存策略一般使用 LRU(Least Recently Used) 算法,即最近最少使用算法,下面将从内存缓存和磁盘缓存两个方面以图片为例 介绍 AndroID 中如何使用缓存,阅读本文之前,请先阅读上篇文章:

Bitmap之位图采样和内存计算详解内存缓存

LruCache 是 AndroID 3.1 提供的一个缓存类,通过该类可以快速访问缓存的 Bitmap 对象,内部采用一个 linkedHashMap 以强引用的方式存储需要缓存的 Bitmap 对象,当缓存超过指定的大小之前释放最近很少使用的对象所占用的内存。

注意:AndroID 3.1 之前,一个常用的内存缓存是一个 SoftReference 或 WeakReference 的位图缓存,现在已经不推荐使用了。AndroID 3.1 之后,垃圾回收器更加注重回收 SoftWeakference/WeakReference,这使得使用该种方式实现缓存很大程度上无效,使用 support-v4 兼容包中的 LruCache 可以兼容 AndroID 3.1 之前的版本。

LruCache 的使用初始化 LruCache

首先计算需要的缓存大小,具体如下:

//第一种方式:ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);//获取当前硬件条件下应用所占的大致内存大小,单位为Mint memorySize = manager.getMemoryClass();//Mint cacheSize = memorySize/ 8;//第二种方式(比较常用)int memorySize = (int) Runtime.getRuntime().maxMemory();//bytesint cacheSize = memorySize / 8;

然后,初始化 LruCache,具体如下:

//初始化 LruCache 且设置了缓存大小LruCache<String,Bitmap> lruCache = new LruCache<String,Bitmap>(cacheSize){    @OverrIDe    protected int sizeOf(String key,Bitmap value) {        //计算每一个缓存Bitmap的所占内存的大小,内存单位应该和 cacheSize 的单位保持一致        return value.getByteCount();    }};
添加 Bitmap 对象到 LruCache 缓存中
//参数put(String key,Bitmap bitmap)lruCache.put(key,bitmap)
获取缓存中的图片并显示
//参数get(String key)Bitmap bitmap = lruCache.get(key);imageVIEw.setimageBitmap(bitmap);

下面使用 LruCache 加载一张网络图片来演示 LruCache 的简单使用。

加载网络图片

创建一个简单的 ImageLoader,里面封装获取缓存 Bitmap 、添加 Bitmap 到缓存中以及从缓存中移出 Bitmap 的方法,具体如下:

//ImageLoaderpublic class ImageLoader {    private LruCache<String,Bitmap> lruCache;    public ImageLoader() {        int memorySize = (int) Runtime.getRuntime().maxMemory() / 1024;        int cacheSize = memorySize / 8;        lruCache = new LruCache<String,Bitmap>(cacheSize){            @OverrIDe            protected int sizeOf(String key,Bitmap value) {                //计算每一个缓存Bitmap的所占内存的大小                return value.getByteCount()/1024;            }        };    }    /**     * 添加Bitmapd到LruCache中     * @param key     * @param bitmap     */    public voID addBitmapTolruCache(String key,Bitmap bitmap){        if (getBitmapFromLruCache(key)==null){            lruCache.put(key,bitmap);        }    }    /**     * 获取缓存的Bitmap     * @param key     */    public Bitmap getBitmapFromLruCache(String key){        if (key!=null){            return lruCache.get(key);        }        return null;    }    /**     * 移出缓存     * @param key     */    public voID removeBitmapFromLruCache(String key){        if (key!=null){            lruCache.remove(key);        }    }}

然后创建一个线程类用于加载图片,具体如下:

//加载图片的线程public class LoadImageThread extends Thread {    private Activity mActivity;    private String mImageUrl;    private ImageLoader mImageLoader;    private ImageVIEw mImageVIEw;    public LoadImageThread(Activity activity,ImageLoader imageLoader,ImageVIEw imageVIEw,String imageUrl) {        this.mActivity = activity;        this.mImageLoader = imageLoader;        this.mImageVIEw = imageVIEw;        this.mImageUrl = imageUrl;    }    @OverrIDe    public voID run() {        httpURLConnection connection = null;        inputStream is = null;        try {            URL url = new URL(mImageUrl);            connection = (httpURLConnection) url.openConnection();            is = connection.getinputStream();            if (connection.getResponseCode() == httpURLConnection.http_OK){                final Bitmap bitmap = BitmapFactory.decodeStream(is);                mImageLoader.addBitmapTolruCache("bitmap",bitmap);                mActivity.runOnUiThread(new Runnable() {                    @OverrIDe                    public voID run() {                        mImageVIEw.setimageBitmap(bitmap);                    }                });            }        } catch (IOException e) {            e.printstacktrace();        } finally {            if (connection!=null){                connection.disconnect();            }            if (is!=null){                try {                    is.close();                } catch (IOException e) {                    e.printstacktrace();                }            }        }    }}

然后,在 MainActivity 中使用 ImageLoader 加载并缓存网络图片到内存中, 先从内存中获取,如果缓存中没有需要的 Bitmap ,则从网络上获取图片并添加到缓存中,使用过程中一旦退出应用,系统将会释放内存,关键方法如下:

//获取图片private voID loadImage(){    Bitmap bitmap = imageLoader.getBitmapFromLruCache("bitmap");   if (bitmap==null){       Log.i(TAG,"从网络获取图片");       new LoadImageThread(this,imageLoader,imageVIEw,url).start();   }else{       Log.i(TAG,"从缓存中获取图片");       imageVIEw.setimageBitmap(bitmap);   }}// 移出缓存private voID removeBitmapFromL(String key){    imageLoader.removeBitmapFromLruCache(key);}

然后在相应的事件里调用上述获取图片、移出缓存的方法,具体如下:

@OverrIDepublic voID onClick(VIEw v) {    switch (v.getID()){        case R.ID.btnLoadLruCache:            loadImage();            break;        case R.ID.btnRemoveBitmapL:            removeBitmapFromL("bitmap");            break;    }}

下面来一张日志截图说明执行情况:

磁盘缓存

磁盘缓存就是指将缓存对象写入文件系统,使用磁盘缓存可有助于在内存缓存不可用时缩短加载时间,从磁盘缓存中获取图片相较从缓存中获取较慢,如果可以应该在后台线程中处理;磁盘缓存使用到一个 diskLruCache 类来实现磁盘缓存,diskLruCache 收到了 Google 官方的推荐使用,diskLruCache 不属于 AndroID SDK 中的一部分,首先贴一个 diskLruCache 的源码链接
DiskLruCache 源码地址 。

diskLruCache 的创建

diskLruCache 的构造方法是私有的,故不能用来创建 diskLruCache,它提供一个 open 方法用于创建自身,方法如下:

/** * 返回相应目录中的缓存,如果不存在则创建 * @param directory 缓存目录 * @param appVersion 表示应用的版本号,一般设为1 * @param valueCount 每个Key所对应的Value的数量,一般设为1 * @param maxSize 缓存大小 * @throws IOException if reading or writing the cache directory fails */public static diskLruCache open(file directory,int appVersion,int valueCount,long maxSize)        throws IOException {    ...    // 创建diskLruCache    diskLruCache cache = new diskLruCache(directory,appVersion,valueCount,maxSize);    if (cache.journalfile.exists()) {        ...        return cache;    }    //如果缓存目录不存在,创建缓存目录以及diskLruCache    directory.mkdirs();    cache = new diskLruCache(directory,maxSize);    ...    return cache;}

注意:缓存目录可以选择 SD 卡上的缓存目录,及 /sdcard/AndroID/data/应用包名/cache 目录,也可以选择当前应用程序 data 下的缓存目录,当然可以指定其他目录,如果应用卸载后希望删除缓存文件,就选择 SD 卡上的缓存目录,如果希望保留数据请选择其他目录,还有一点,如果是内存缓存,退出应用之后缓存将会被清除。

diskLruCache 缓存的添加

diskLruCache 缓存的添加是通过 Editor 完成的,Editor 表示一个缓存对象的编辑对象,可以通过其 edit(String key) 方法来获取对应的 Editor 对象,如果 Editor 正在使用 edit(String key) 方法将会返回 null,即 diskLruCache 不允许同时 *** 作同一个缓存对象。当然缓存的添加都是通过唯一的 key 来进行添加 *** 作的,那么什么作为 key 比较方便吗,以图片为例,一般讲 url 的 MD5 值作为 key ,计算方式如下:

//计算url的MD5值作为keyprivate String hashKeyFordisk(String url) {    String cacheKey;    try {        final MessageDigest mDigest = MessageDigest.getInstance("MD5");        mDigest.update(url.getBytes());        cacheKey = bytesToHexString(mDigest.digest());    } catch (NoSuchAlgorithmException e) {        cacheKey = String.valueOf(url.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();}

通过 url 的 MD5 的值获取到 key 之后,就可以通过 diskLruCache 对象的 edit(String key) 方法获取 Editor 对象,然后通过 Editor 对象的 commit 方法,大概意思就是释放 Editir 对象,之后就可以通过 key 进行其他 *** 作咯。

当然,获取到 key 之后就可以向 diskLruCache 中添加要缓存的东西咯,要加载一个网络图片到缓存中,显然就是的通过下载的方式将要缓存的东西写入文件系统中,那么就需要一个输出流往里面写东西,主要有两种处理方式:

创建 OutputStream 写入要缓存的数据,通过 diskLruCache 的 edit(String key) 方法获得 Editor 对象,然后通过 OutputStream 转换为 Birmap,将该 Bitmap 写入由 Editor 对象创建的 OutputStream 中,最后调用 Editor 对象的 commit 方法提交;先获得 Editor 对象,根据 Editor 对象创建出 OutputStream 直接写入要缓存的数据,最后调用 Editor 对象的 commit 方法提交;

这里以第一种方式为例,将根据 url 将网络图片添加到磁盘缓存中,同时也添加到内存缓存中,具体如下:

//添加网络图片到内存缓存和磁盘缓存public voID putCache(final String url,final CallBack callBack){    Log.i(TAG,"putCache...");    new AsyncTask<String,VoID,Bitmap>(){        @OverrIDe        protected Bitmap doInBackground(String... params) {            String key = hashKeyFordisk(params[0]);            diskLruCache.Editor editor = null;            Bitmap bitmap = null;            try {                URL url = new URL(params[0]);                httpURLConnection conn = (httpURLConnection) url.openConnection();                conn.setReadTimeout(1000 * 30);                conn.setConnectTimeout(1000 * 30);                ByteArrayOutputStream baos = null;                if(conn.getResponseCode()==httpURLConnection.http_OK){                    BufferedinputStream bis = new BufferedinputStream(conn.getinputStream());                    baos = new ByteArrayOutputStream();                    byte[] bytes = new byte[1024];                    int len = -1;                    while((len=bis.read(bytes))!=-1){                        baos.write(bytes,len);                    }                    bis.close();                    baos.close();                    conn.disconnect();                }                if (baos!=null){                    bitmap = decodeSampledBitmapFromStream(baos.toByteArray(),300,200);                    addBitmapToCache(params[0],bitmap);//添加到内存缓存                    editor = diskLruCache.edit(key);                    //关键                    bitmap.compress(Bitmap.CompressFormat.JPEG,100,editor.newOutputStream(0));                    editor.commit();//提交                }            } catch (IOException e) {                try {                    editor.abort();//放弃写入                } catch (IOException e1) {                    e1.printstacktrace();                }            }            return bitmap;        }        @OverrIDe        protected voID onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            callBack.response(bitmap);        }    }.execute(url);}
diskLruCache 缓存的获取

在 diskLruCache 缓存的添加中了解了如何获取 key,获取到 key 之后,通过 diskLruCache 对象的 get 方法获得 Snapshot 对象,然后根据 Snapshot 对象获得 inputStream,最后通过 inputStream 就可以获得 Bitmap ,当然可以利用 上篇文章 中的对 Bitmap 采样的方式进行适当的调整,也可以在缓存之前先压缩再缓存,获取 inputStream 的方法具体如下:

//获取磁盘缓存public inputStream getdiskCache(String url) {    Log.i(TAG,"getdiskCache...");    String key = hashKeyFordisk(url);    try {        diskLruCache.Snapshot snapshot = diskLruCache.get(key);        if (snapshot!=null){            return snapshot.getinputStream(0);        }    } catch (IOException e) {        e.printstacktrace();    }    return null;}

diskLruCache 的主要部分大致如上,下面实现一个简单的三级缓存来说明 LruCache 和 diskLruCache 的具体使用,MainActivity 代码如下:

//MainActivity.javapublic class MainActivity extends AppCompatActivity {    private static final String TAG = "cache_test";    public static String CACHE_DIR = "diskCache";  //缓存目录    public static int CACHE_SIZE = 1024 * 1024 * 10; //缓存大小    private ImageVIEw imageVIEw;    private LruCache<String,String> lruCache;    private LruCacheUtils cacheUtils;    private String url = "http://img06.tooopen.com/images/20161012/tooopen_sy_181713275376.jpg";    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentVIEw(R.layout.activity_main);        imageVIEw = (ImageVIEw) findVIEwByID(R.ID.imageVIEw);    }        @OverrIDe    protected voID onResume() {        super.onResume();        cacheUtils = LruCacheUtils.getInstance();        //创建内存缓存和磁盘缓存        cacheUtils.createCache(this,CACHE_DIR,CACHE_SIZE);    }        @OverrIDe    protected voID onPause() {        super.onPause();        cacheUtils.flush();    }    @OverrIDe    protected voID onStop() {        super.onStop();        cacheUtils.close();    }        public voID loadImage(VIEw vIEw){        load(url,imageVIEw);    }        public voID removeLruCache(VIEw vIEw){        Log.i(TAG,"移出内存缓存...");        cacheUtils.removeLruCache(url);    }        public voID removediskLruCache(VIEw vIEw){        Log.i(TAG,"移出磁盘缓存...");        cacheUtils.removediskLruCache(url);    }        private voID load(String url,final ImageVIEw imageVIEw){        //从内存中获取图片        Bitmap bitmap = cacheUtils.getBitmapFromCache(url);        if (bitmap == null){            //从磁盘中获取图片            inputStream is = cacheUtils.getdiskCache(url);            if (is == null){                //从网络上获取图片                cacheUtils.putCache(url,new LruCacheUtils.CallBack<Bitmap>() {                    @OverrIDe                    public voID response(Bitmap bitmap1) {                        Log.i(TAG,"从网络中获取图片...");                        Log.i(TAG,"正在从网络中下载图片...");                        imageVIEw.setimageBitmap(bitmap1);                        Log.i(TAG,"从网络中获取图片成功...");                    }                });            }else{                Log.i(TAG,"从磁盘中获取图片...");                bitmap = BitmapFactory.decodeStream(is);                imageVIEw.setimageBitmap(bitmap);            }        }else{            Log.i(TAG,"从内存中获取图片...");            imageVIEw.setimageBitmap(bitmap);        }    }}

布局文件比较简单就不贴代码了,下面是日志运行截图说明执行情况,如下图所示:

这篇文章记录了 LruCache 和 diskLruCache 的基本使用方式,至少应该对这两个缓存辅助类有了一定的了解,它的具体实现请参考源码。
【文中代码】:传送门

可以关注公众号:jzman-blog,一起交流学习。

总结

以上是内存溢出为你收集整理的Bitmap之内存缓存和磁盘缓存详解全部内容,希望文章能够帮你解决Bitmap之内存缓存和磁盘缓存详解所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1121242.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-29
下一篇2022-05-29

发表评论

登录后才能评论

评论列表(0条)

    保存