LRUCache(内存)DiskLruCache (硬盘)缓存的

2019-02-18  本文已影响0人  君袅

了解

什么是LruCache?

LruCache是Android在API12上引入的一种缓存机制,这种缓存有个特点,就是当缓存容量达到上限时,会将最近最少使用的对象从cache中清除出去。

那么这种缓存方式跟普通的缓存有什么区别呢?为什我需要使用它?

曾经,在各大缓存图片的框架没流行的时候。有一种很常用的内存缓存技术:SoftReference 和 WeakReference(软引用和弱引用),但是走到了 Android 2.3(Level 9)时代,垃圾回收机制更倾向于回收 SoftReference 或 WeakReference 的对象。后来,又来到了Android3.0,图片缓存在native中,因为不知道要在是什么时候释放内存,没有策略,没用一种可以预见的场合去将其释放,这就造成了内存溢出。
所以LruCache的出现就是为了解决上述问题,它使用强引用来缓存,消除了垃圾回收机制的影响,同时通过设定缓存上限以及著名的LRU算法来管理内存,使得内存的管理变得更加高效和灵活。

如何使用LruCache?

LruCache是通过LinkedHashMap来实现LRU内存管理的,所以我们可以将其当作一个map来使用,如put,get,remove等。但是要注意,LruCache是不允许key和value为空的。
接下来是针对LruCache这个知识点的一些延伸

Lru是什么?

LRU是操作系统内存管理中比较经典的一种缓存淘汰算法,相信学过操作系统的童鞋应该都有耳闻。操作系统里面还存在一些其他的缓存淘汰算法,如LIFO,FIFO,LFU等,它们都是从某一个维度来评估缓存中条目的重要性,从而当缓存不足时淘汰掉其中最不重要的条目。可见当我们在做App开发时,操作系统里面很多的技术思想其实也可以拿来做参考。
LinkedHashMap为什么能实现LruCache中的Lru?
LinkedHashMap跟HashMap一个重要区别就是其内部的entry是有序的,它有两种排序规则:插入排序和访问排序,其实也就是按put调用时间排序还是按get调用时间来排序。LinkedHashMap通过设置访问排序来是实现Lru的。

LRUCache缓存一张图片

1.建立一个类(图片通过这个类进行缓存)

public class MemCache {
    private LruCache<String, Bitmap> mImageCache;
    // 初始化 - 构造
    public MemCache() {
        initCache();
    }
    private void initCache() {
        // 计算可使用的最大内存
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // 取4分之一的可用内存作为缓存
        final int cacheSize = maxMemory / 4;
        mImageCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
    }
    /**
     * 保存数据--bitmap
     * @param url  图片的name
     * @param bitmap
     */
    public void putBitmap(String url, Bitmap bitmap) {
        if (mImageCache != null) {
            mImageCache.put(url, bitmap);
        }
    }
    //获取缓存资源
    public Bitmap getBitmap(String url){
        return mImageCache.get(url);
    }
}

2.在activity中实现缓存

public class MainActivity extends AppCompatActivity {

    private String url = "http://cms-bucket.ws.126.net/2019/02/15/97392c98fe22485a8a2458ba9dd907d9.jpeg";
    private ImageView img;
    private MemCache memCache=new MemCache();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }
    @Override
    protected void onStart() {
        super.onStart();
                /*
                * 1.在activity开始就要实现
                * 2.首先获取缓存资源判断是否为空
                * 3.如果为空就请求网络缓存
                * 4.如果不为空就用缓存的数据
                * */
                Bitmap bitmap = memCache.getBitmap(url);
                if(bitmap==null){
                    initData();
                }else {
                    img.setImageBitmap(bitmap);
                }
    }

    private void initData() {
        //1.创建OkHttpClient对象
        OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
        //2.创建Request请求对象
        Request build = new Request.Builder()
                .get()
                .url(url)
                .build();
        //3.创建Call对象
        Call call = okHttpClient.newCall(build);
        //4.异步
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                //通过BitmapFactory把加载成bitmap
                final Bitmap bitmap = BitmapFactory.decodeStream(response.body().byteStream());
                if(memCache.getBitmap(url)!=null){
                    memCache.putBitmap(url,bitmap);
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        img.setImageBitmap(bitmap);
                    }
                });
            }
        });
    }
    private void initView() {
        img = (ImageView) findViewById(R.id.img);
    }
}

DiskLruCache 是缓存在硬盘中的

依赖

    implementation 'com.jakewharton:disklrucache:2.0.2'

给字符加密一些准备措施的类

public class Utils {
    private static final String TAG = "Utils";
    public File getDiskCacheDir;

    /**
     * 获取缓存文件夹,这里优先选择SD卡下面的android/data/packageName/cache/路径,若没有SD卡,就选择data/data/packageName/cache
     *
     * @param context    上下文环境
     * @param uniqueName 缓存文件夹名称
     * @return 返回缓存文件
     */
    public static 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();
        }
        File file = new File(cachePath + File.separator + uniqueName);
        Log.d(TAG, "getDiskCacheDir: file="+file.getAbsolutePath());
        return file;
    }
    /**
     * 获取本App的版本号
     *
     * @param context context上下文
     * @return 返回版本号
     */
    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;
    }
    /**
     * 给字符串来个md5加密,
     * @param key 需要加密的string
     * @return 返回加密后的string ,或者加密失败,就返回string的哈希值
     */
    public static String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            //md5加密
            MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            //若md5加密失败,就用哈希值
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }
    /**
     * 字节数组转为十六进制字符串
     * @param bytes 字节数组
     * @return 返回十六进制字符串
     */
    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();
    }

}

建立一个操控缓存对象的类

public class DiskCache {
    private static final String TAG = "DiskCache";
    DiskLruCache mDiskLruCache;
    // 初始化

    public DiskCache(File directory, long maxSize) {
        try {
            mDiskLruCache = DiskLruCache.open(directory, 1, 1, maxSize);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 保存bitmap到磁盘
     * 通过图片url 然后md5 加密的内容,作为key  来保存bitmap 对象
     *
     * @param url    图片请求url  --》 md5  去除特殊字符
     * @param bitmap
     */
    public void saveBitmap(String url, Bitmap bitmap) {

        try {
            String key = Utils.hashKeyForDisk(url);
            //editor 操作数据保存逻辑
            DiskLruCache.Editor editor = mDiskLruCache.edit(key);
            OutputStream os = editor.newOutputStream(0);
            //此处存的一个 bitmap 对象因此用 ObjectOutputStream
            ObjectOutputStream outputStream = new ObjectOutputStream(os);
            // 下载图片
            if (downloadImage(url, outputStream)) {
                editor.commit();
            } else {
                editor.abort();
            }
            mDiskLruCache.flush();
            //别忘了关闭流和提交编辑
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 通过url  获取md5加密后的key。然后通过key 获取bitmap 对象
     *
     * @param url
     * @return
     */
    public Bitmap getBitmap(String url) {
        //使用DiskLruCache获取缓存,需要传入key,而key是imageUrl加密后的字符串,
        Bitmap bitmap = null;
        String key = Utils.hashKeyForDisk(url);
        //通过key获取的只是一个快照
        DiskLruCache.Snapshot snapshot = null;
        try {
            snapshot = mDiskLruCache.get(key);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (snapshot != null) {
            InputStream inputStream = snapshot.getInputStream(0);//类似写缓存时候,传入的是缓存的编号
            //可以使用bitmapFactory
            bitmap = BitmapFactory.decodeStream(inputStream);
        }

        return bitmap;
    }


    /**
     * 下载图片
     *
     * @param imgUrl       图片网址链接
     * @param outputStream 输出流对象
     * @return 返回时候完成下载成功
     */
    private boolean downloadImage(String imgUrl, OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            URL url = new URL(imgUrl);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);//Buffer输入流,8M大小的缓存
            out = new BufferedOutputStream(outputStream, 8 * 1024);
            int b;//正在读取的byte
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //关闭资源
        finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }
}

在activity中的使用

public class Main2Activity extends AppCompatActivity {

    private String imgUrl = "http://cms-bucket.ws.126.net/2019/02/15/97392c98fe22485a8a2458ba9dd907d9.jpeg";
    private ImageView mImg;
    DiskCache diskCache;
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        // 初始化硬盘缓存对象
        diskCache = new DiskCache(Utils.getDiskCacheDir(this,"cache"),10*1024);

        /**
         * 动态获取权限,Android 6.0 新特性,一些保护权限,除了要在AndroidManifest中声明权限,还要使用如下代码动态获取
         */
        if (Build.VERSION.SDK_INT >= 23) {
            int REQUEST_CODE_CONTACT = 101;
            String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE,WRITE_SECURE_SETTINGS};
            //验证是否许可权限
            for (String str : permissions) {
                if (this.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
                    //申请权限
                    this.requestPermissions(permissions, REQUEST_CODE_CONTACT);
                    return;
                }
            }
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (diskCache.getBitmap(imgUrl)!=null){
            mImg.setImageBitmap(diskCache.getBitmap(imgUrl));
        }else{
            requestNetBitmap();
        }
    }

    private void initView() {
        mImg = (ImageView) findViewById(R.id.img);
    }
    /**
     * 请求网络的图片对象
     */
    private void requestNetBitmap() {

        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder()
                .get()
                .url(imgUrl)
                .build();
        Call call = okHttpClient.newCall(request);

        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d(TAG, "onFailure: ");
            }
            @Override
            public void onResponse(Call call, final Response response) throws IOException {
//                final byte[] bytes = response.body().bytes();
                final InputStream inputStream = response.body().byteStream();
                final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                if (bitmap != null) {
                    // 保存磁盘数据
                    diskCache.saveBitmap(imgUrl,bitmap);
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            mImg.setImageBitmap(bitmap);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        });
    }
}
上一篇下一篇

猜你喜欢

热点阅读