Android三级缓存机制工具类的实现

Posted 峥嵘life

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android三级缓存机制工具类的实现相关的知识,希望对你有一定的参考价值。

一.三级缓存概述

(一)三级缓存的三级

第一级是内存,最快,不需要网络

第二级是本地,不需要网络

第三级是网络,需要网络请求

      三级缓存机制的思想:
      如果在内存中获取到数据,就不去本地和网络中获取。
      如果在本地中获取到数据就不去网络中获取,
      如果内存和本地中不存在数据,就要去网络中请求数据

      三级缓存技术能有效节省用户的流量,但是也会增加一些内存负担。

二.使用示例展示三级缓存工具栏类的使用

      程序运行后的页面:
h1
      虽然只用一个按钮和一个图片显示,但是通过测试(联网状态和断网状态对比)能知道图片是从网络中获取还是从本地或者中内存。
      这里用到了几个其他自己编程的小工具类。

(一)添加手机权限,网络权限和SD卡写的权限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

(二)编写布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="start"
        android:text="加载图片" />

    <ImageView
        android:id="@+id/main_iv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

</LinearLayout>

上面是一个非常简单的布局文件。

(三)设计一个方便调试显示个工具类

package com.lwz.threelevelt;

import android.content.Context;
import android.util.Log;
import android.widget.Toast;

/**
 * 本类用于简易的显示信息
 * 比如土司,或Log信息
 */

public class ShowUtils {
    //这里DEBUG的作用是,可以在程序完成后设置DEBUG的值为false,程序以后就不会在显示以前的打印信息
    public static  boolean DEBUG = true;

    //各种Log打印
    public static void e(Object o) {
        if (DEBUG)
            Log.e("TAG", "打印:------      " + o.toString());
    }

    public static void e(int i) {
        if (DEBUG)
            Log.e("TAG", "打印:------      " + i);
    }

    public static void e(float i) {
        if (DEBUG)
            Log.e("TAG", "打印:------      " + i);
    }

    public static void e(boolean b) {
        if (DEBUG)
            Log.e("TAG", "打印:------      " + b);
    }

    //各种土司
    public static void ts(Context context, Object object) {
        if (DEBUG)
            Toast.makeText(context, object + "", Toast.LENGTH_SHORT).show();
    }

    public static void tsl(Context context, Object object) {
        if (DEBUG)
            Toast.makeText(context, object + "", Toast.LENGTH_LONG).show();
    }


}

(四)文件操作的一个工具类

public class FileUtils {

    //判断是否本地有sd卡,确定是否保存在SD卡内
    String path;//文件存储的地方

    /**
     * 通过构造方法传入存储的路径
     */
    public FileUtils(Context context, String dirName) {
        //判断是否本地有sd卡,这里代表的是SD卡在就绪的状态
//这里判断相等状态要使用.equal,使用==会匹配不到???
        if (Environment.getExternalStorageState() .equal( Environment.MEDIA_MOUNTED)) {
            ShowUtils.e("SD卡就绪状态");
            path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + dirName;
        } else {
            ShowUtils.e("SD卡没有就绪状态");
            //保存在内部存储器中
            path = context.getCacheDir().getAbsolutePath() + "/" + dirName;
        }
        //创建文件
        new File(path).mkdirs();
    }

    /**
     * 文件的写入
     * 传入一个文件的名称和一个Bitmap对象
     * 最后的结果是保存一个图片
     */
    public void saveToSDCard(String key, Bitmap bmp) {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(new File(path, key));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        //保存图片的设置
        bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
        try {
            fos.close();//关闭流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 文件的读取,
     * 根据文件的名字,读取出一个Bitmap的对象,
     * 如果之前保存过就有值,否则是null
     */
    public Bitmap readFromSDCard(String key) {
        return BitmapFactory.decodeFile(new File(path, key).getAbsolutePath());
}
}

(五)最最重要的三级缓存功能的实现的工具类

package com.lwz.threelevelt;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.v4.util.LruCache;

import java.io.InputStream;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static com.lwz.threelevelt.ShowUtils.e;

/**
 * 图片三级缓存机制的实现类
 * <p>
 * 首先是内存,最快
 * 其次是本地,不需要网络
 * 最后是网络,需要网络请求
 * 这如果在内存中获取到数据,就不去本地和网络中获取,同样如果在本地中获取到数据就不去网络中获取,
 * 如果内存和本地中不存在数据,采取网络中请求数据
 * <p>
 * ,这里结合的是另一个fileUtils的工具类来实现
 * <p>
 * 调用方法也是很简单的:
 */

public class ImageLoader {

    //下载使用的线程池对象
    ExecutorService threadLooper;
    //缓存类,能过获取和写入数据到缓存中,短时间的存储!!
    private static LruCache<String, Bitmap> cache;
    //文件操作类对象
    private FileUtils fileUtils;

    /**
     * 构造方法,需要传入一个保存文件的名字
     * 实例化:线程池对象,缓存类,文件操作类对象
     */
    public ImageLoader(Context context, String dirName) {
        //获取系统分配的最大内存
        int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);
        //实例化缓存类的对象
        cache = new LruCache<String, Bitmap>(maxSize) {
            //每一个键所对应的值的大小
            //自动释放低频率的文件
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
        fileUtils = new FileUtils(context, dirName);//实例化文件操作类的对象
        threadLooper = Executors.newFixedThreadPool(5);//实例化线程池,并设置运行的最大线程数
    }


    /**
     * 下载图片的方法,这也是提供给我们调用的方法
     * 需要传入一个URL地址,和一个图片下载成功后的回调方法
     */
    public void loadImage(final String url, @NonNull final ImageLoadListener listener) {
        //去掉所有需要转义的斜杠,把获得的字符串作为Bitmap对象的一个标示符,通过它和它的Bitmap对象一一对应
        final String key = url.replaceAll("[\\\\W]", "");
        //第一级,先判断缓存类中是否有数据
        if (readFromCache(key) != null) {
            //直接拿出来
            e("从缓存中加载");
            listener.loadImage(readFromCache(key));
        } else {
            //第二级,再判断本地中是否存在数据
            final Bitmap bitmap = fileUtils.readFromSDCard(key);//查看是否存在数据
            if (bitmap != null) {//本地中存在数据
                //存储到缓存
                e("从SDCard中加载");
                saveToCache(key, bitmap);//把从SD卡中读取到的数据保存到缓存中,
                //返回
                listener.loadImage(fileUtils.readFromSDCard(key));//返回一个数据给调用者
            } else {
                //第三级,从网络中下载数据
                //要把数据分别存入本地中内存中

                //下载,使用子线程

                //创建一个Handler对象,这里还是主线程,
                // 下载是在子线程,然后调用Handler对象发送数据给主线程
                final Handler handler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        e("从网络下载");
                        listener.loadImage((Bitmap) msg.obj);
                    }
                };

                //线程池的使用,在里面下载数据
                threadLooper.execute(new Runnable() {
                    @Override
                    public void run() {
                        //开始下载
                        try {
                            URL u = new URL(url);
                            InputStream inputStream = u.openStream();
                            Bitmap bitmap1 = BitmapFactory.decodeStream(inputStream);//获取bitmap对象
                            fileUtils.saveToSDCard(key, bitmap1);//保存文件到SD卡
                            saveToCache(key, bitmap1);//保存文件到内存中
                            //使用Handler对象给主线程发送消息
                            Message msg = handler.obtainMessage();
                            msg.obj = bitmap1;
                            handler.sendMessage(msg);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }

    }

    /**
     * 取消子线程的任务
     */
    public void cancelDownLoad() {
        threadLooper.shutdown();
    }

    /**
     * 定义一个接口,里面有一个方法,
     * 这里有一个Bitmap对象参数,作用是让调用这接收这个Bitmap对象,实际这bitmap对象就是缓存中的对象
     */
    public interface ImageLoadListener {
        public void loadImage(Bitmap bmp);
    }


    /**
     * 使用缓存类存储Bitmap对象
     */
    private void saveToCache(String key, Bitmap bmp) {
        cache.put(key, bmp);
    }

    /**
     * 使用缓存类获取Bitmap对象
     */
    private Bitmap readFromCache(String key) {
        return cache.get(key);
    }
}

      上面代码中e(“XXX”) 和ShowUtils.e(“XXX”)效果是一样的,因为导入方式是静态的:import static com.lwz.threelevelt.ShowUtils.e;所以可以省略类名ShowUtils。
      有些简单的方法这样使用是非常方便的。

(六)最后的调用类

package com.lwz.threelevelt;

import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;

/**
 * 三级缓存工具类的调用测试
 * 1.创建三级缓存类的对象,传入上下文和图片名字
 * 2.调用三级缓存类的对象的loadImage方法,
 * 传入两个参数,第一个参数是URL地址,第二个参数是回调接口,在回调接口内可以接收到根据URL地址下载到的Bitmap对象
 */
public class MainActivity extends AppCompatActivity {

    //定义布局的控件
    ImageView imageView;
    //定义三级缓存工具类
    ImageLoader loader;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ShowUtils.DEBUG = false;
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.main_iv);
        loader = new ImageLoader(this, "test3");//创建文件夹
    }

    public void start(View v) {
        loader.loadImage("http://p3.so.qhmsg.com/bdr/326__/t018da60b972e086a1d.jpg", new ImageLoader.ImageLoadListener() {
            @Override
            public void loadImage(Bitmap bmp) {
                imageView.setImageBitmap(bmp);
            }
        });
    }
}

      可以看到实现了三级缓存的工具类后,调用还是非常简单的。
      程序运行后显示的界面:
h2
第一次点击按钮,页面显示:
h3
显示的Log数据:
h4
可以看到数据是从网络中获取到的。因为刚刚开始本地或缓存中都没有数据。

第二次或多次点击按钮,显示的Log数据:
h5
可以看到数据是从缓存中获取到的。因为图片的数据每次打开后,缓存中都会有它的数据。

退出程序后,再进入程序点击按钮,显示的Log数据:
h6
可以看到数据是从本地中获取到的。因为缓存中的数据很容易被回收,本地的数据不会被回收。

卸载程序后,再安装程序点击按钮,显示的Log数据:
h7
      可以看到数据是从SD卡中获取到的。因为程序存放的数据是在SD卡下的,程序卸载后数据依然存在,但是如果数据保存在内部存储器,卸载程序也会删除数据。


      上面就是三级缓存图片工具类的实现和三级缓存图片的一个简单使用。

以上是关于Android三级缓存机制工具类的实现的主要内容,如果未能解决你的问题,请参考以下文章

Android中图片的三级缓存详解

彻底解析Android缓存机制——LruCache

Android图片三级缓存策略

Android 图片缓存机制

图片三级缓存

Android中的缓存机制与实现