Android 缓存机制的原理解析与运用

缓存机制的作用

1. 服务器异常,用户无法联网看到的内容
2. 节省用户的流量(例如:json 数据和图片数据,无需经常向服务器端请求数据)
3. 提高本地显示的响应速度

json 数据的缓存

我们常常从服务器端,获取 json 数据。可以把它存放在本地文件中,当再次需要时,先从本地判断是否存放;若有则直接使用,若无,则再次从服务器端获取。(json 数据占用内存不大,且不怎么耗费流量,一般不进行系统内存的存储)

json 数据的存储注意点
1. 存储的系统文件夹:cache 目录下(系统容易删除)或 SDCard 目录下(SD 卡损坏且数据丢失,不会有太大干扰)
2. 存放于 Andrid 的 SharedPreferences 中,通过键值对存取,需要使用时,再取出
json 数据的存储图

json 数据的缓存

当首次从服务器端拿到数据,此时数据在 RAM 中,即内存中。此时保留一份到文件系统,存放到本地。

再次需要数据时,先判断存放在文件中的数据是否有,若有,则通过 key 获取。若无,则重新从服务器端获取。

json 数据的存取代码
public class SharedPrefUtil {
    public static void saveJsonToCache(String key, String value, Context ctx) {
        SharedPreferences sp = ctx.getSharedPreferences("Jsoncache",Context.MODE_PRIVATE);
        SharedPreferences.Editor edit = sp.edit();
        edit.putString(key,value);
        edit.commit();
    }
    public static String getJsonFromCache(String key, Context ctx) {
        SharedPreferences sp = ctx.getSharedPreferences("Jsoncache", Context.MODE_PRIVATE);
        String jsonString = sp.getString(key, "");
        return jsonString;
    }
}

存放在SharedPreferences中,手机shared_prefs目录下的Jsoncache.xml文件下,key 为 json 数据的 url。

图片的三级缓存机制

图片数据占用内存,且耗费流量。务必要使用三级缓存机制

三级缓存机制图示

三级缓存机制图示

首先,在手机屏幕上,ListView 在滑动,每个 item 中,有 ImageView(即显示图片的控件)让其显示图片

手机内存,手机本地文件系统和服务器端的数据称为三级缓存,一开始从服务器端拿数据显示时,存一份在内存缓存,也存一份在本地文件系统。下次再拿数据时,先从内存缓存判断,若没有,则从本地文件系统拿取(这里在本地文件中若是能够拿到,则存一份在内存缓存中);若还是没有,则从服务器中获取。

图片的三级缓存机制代码

MyBitmapUtils 类:

public class MyBitmapUtils {
    private static final String TAG = "MyBitmapUtils";
    public Activity mActivity;
    public MyBitmapUtils(Activity mActivity) {
        this.mActivity = mActivity;
    }
    // 从网络上获取图片,并显示在 imageView 上
    public void display(ImageView iv,String imageUrl) {
        // 先看看内存中的集合中是否有数据,若有,直接使用内存缓存
        Bitmap bitmapFromMem = MemoryCacheUtils.getBitmapFromMem(imageUrl);
        if(bitmapFromMem!=null) {
            LogUtil.i(TAG,"使用内存缓存的数据!");
            iv.setImageBitmap(bitmapFromMem);
            return;
        }
        // 先看看文件缓存,有没有数据,如果有,就直接使用文件缓存
        Bitmap bitmapFromFile = FileCacheUtils.getBitmapFromFile(imageUrl, mActivity);
        if(bitmapFromFile!=null) {
            // 有本地缓存,直接从本地获取数据
            iv.setImageBitmap(bitmapFromFile);
            return;
        }
        // 如果没有去访问网络,使用网络缓存
        NetworkCacheUtils.getBitmapFromNet(iv,imageUrl,mActivity);
    }
}

先判断内存缓存,其次再判断是否有文件系统缓存;若没有,则去网络上拿数据。

从云端获取数据

NetworkCacheUtils 类:

public class NetworkCacheUtils {
    private static final String TAG = "NetworkCacheUtils";

    public static void getBitmapFromNet(ImageView iv, String imageUrl, Activity mActivity) {
        final Activity activity = mActivity;
        new AsyncTask<Object, Integer, Bitmap>() {
            ImageView imageView;
            String urlString;

            @Override
            protected Bitmap doInBackground(Object... params) {
                LogUtil.i(TAG,"从网络缓存中获取!");
                Bitmap bitmap = null;
                imageView = (ImageView) params[0];
                urlString = (String) params[1];
                try {
                    URL url = new URL(urlString);
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();

                    connection.setRequestMethod("GET");
                    connection.setReadTimeout(5000);
                    connection.setConnectTimeout(5000);
                    connection.connect();
                    int responseCode = connection.getResponseCode();
                    if (responseCode == 200) {
                        InputStream inputStream = connection.getInputStream();
                        bitmap = BitmapFactory.decodeStream(inputStream);
                    }
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return bitmap;
            }

            @Override
            protected void onPostExecute(Bitmap bitmap) {
                if (bitmap != null) {
                    // LogUtil.i(TAG, "MyBitmapUtils");
                    imageView.setImageBitmap(bitmap);

                    // 把 bitmap 图片的数据放到本地,生成二级缓存
                    FileCacheUtils.saveBitmapToFile(bitmap, urlString, activity);
                }
            }
        }.execute(iv, imageUrl);
    }
}

开启 AsyncTask,子线程开始下载数据,在 doInBackground() 方法中,使用 HttpURLConnection 类,通过 url 下载 inputStream,并解析成 bitmap 返回给主线程。主线程显示图片数据。最后,记得存一份 bitmap 数据到本地文件系统。

本地文件系统缓存

FileCacheUtils 类:

public class FileCacheUtils {
    private static final String TAG = "FileCacheUtils";

    public static void saveBitmapToFile(Bitmap bitmap, String url, Activity mActivity) {
        // 以他作为文件名 MD5
        String md5FileName = MD5Utils.getMD5(url);

        File file = new File(mActivity.getCacheDir(), md5FileName);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            bitmap.compress(Bitmap.CompressFormat.PNG,100,fos);
            fos.close();

            // 在这里,可以顺便放到内存保留起来
            MemoryCacheUtils.saveBitmapToMem(bitmap,url);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static Bitmap getBitmapFromFile(String url,Activity mActivity) {
        Bitmap bitmap = null;
        String md5FileName = MD5Utils.getMD5(url);
        File file = new File(mActivity.getCacheDir(), md5FileName);
        if(file.exists()) {
            LogUtil.i(TAG, "有文件缓存,直接从本地缓存获取数据");
            bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
            MemoryCacheUtils.saveBitmapToMem(bitmap,url);
            return bitmap;
        } else{
            LogUtil.i(TAG,"没有文件缓存");
            return bitmap;
        }
    }
}

存放在手机系统的 cache 目录下,文件名是用 MD5 命名的,PNG 格式。拿的话,使用BitmapFactory.decodeFile(file.getAbsolutePath());,解析出 bitmap,并返回。记得,在这里存一份到内存缓存。

内存缓存

MemoryCacheUtils 类:

public class MemoryCacheUtils {
    private static final String TAG = "MemoryCacheUtils";
    // static HashMap<String,Bitmap> memCache = new HashMap<String,Bitmap>();
    // 改进版(防止 OOM)
    // static HashMap<String,SoftReference<Bitmap>> memCache = new HashMap<String,SoftReference<Bitmap>>();
    // 改进第二版
    // Lru:Leaset Recently Used
    // size 为虚拟机内存大小的:1/4 或者 1/8
    /**
     * 内存缓存:
     * 因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,
     * 这让软引用和弱引用变得不再可靠。  Google建议使用 LruCache
     */
    static LruCache<String, Bitmap> memCache = new LruCache<String, Bitmap>((int) (MemoryComputingUtils.getMaxMemory() / 4)) {
        @Override
        protected int sizeOf(String key, Bitmap value) {
            int byteCount = value.getByteCount();
            LogUtil.i(TAG, "byteCount = " + byteCount);
            return byteCount;//super.sizeOf(key, value);
        }
    };
    // 使用这种写法,我们所有的 bitmap 都维护在 memCache 这个集合里,GC 的时候,任何 bitmap 都无法释放
    // 随着加载的图片越来越多,该集合中维护的 bitmap,可能会造成 OOM

    // 强引用,java 中的引用,默认都是强引用。(我们之前讲的垃圾回收机制,是基于强引用)
    // 对于 GC 回收来说,如果该对象有另一个对象的到它的强引用,则该对象无法回收
    // 软引用,当 GC 回收内存的时候,一般不会去回收软引用,但是当内存不够用的时候,就可以去回收软引用的内存
    // 弱引用,GC 回收的时候,即便是内存够的时候,也有可能回收
    // 虚引用(一般用不上),对于引用对象之间的关系,没有任何影响,配合引用队列使用。每次要回收这种对象,会
    // 把这个对象加入到一个引用队列。程序可以通过遍历这个引用队列,来看队列中是否有对应的虚引用。如果有,说明马上要被回收了。

    /**
     * <h3>Avoid Soft References for Caching</h3>
     * In practice, soft references are inefficient for caching. The runtime doesn't
     * have enough information on which references to clear and which to keep. Most
     * fatally, it doesn't know what to do when given the choice between clearing a
     * soft reference and growing the heap.
     * <p>
     * <p>The lack of information on the value to your application of each reference
     * limits the usefulness of soft references. References that are cleared too
     * early cause unnecessary work; those that are cleared too late waste memory.
     * <p>
     * <p>Most applications should use an {@code android.util.LruCache} instead of
     * soft references. LruCache has an effective eviction policy and lets the user
     * tune how much memory is allotted.
     */
    public static void saveBitmapToMem(Bitmap bitmap, String url) {
        // memCache.put(url,new SoftReference<Bitmap>(bitmap));
        Bitmap put = memCache.put(url, bitmap);
        LogUtil.i(TAG, "put = " + put);
    }

    public static Bitmap getBitmapFromMem(String url) {
/*        SoftReference<Bitmap> bitmapSoftReference = memCache.get(url);
        if(bitmapSoftReference!=null) {
            Bitmap bitmap = bitmapSoftReference.get();
            return bitmap;
        }*/
        Bitmap bitmap = memCache.get(url);
        if (bitmap != null) {
            LogUtil.i(TAG, "bitmap = " + bitmap.toString());
        } else {
            LogUtil.i(TAG, "bitmap is null!");
        }
        return bitmap;
    }
}

使用键值对(

static HashMap<String,Bitmap> memCache = new HashMap<String,Bitmap>();

会出现如下错误:

使用这种写法,我们所有的 bitmap 都维护在 memCache 这个集合里,GC 的时候,任何 bitmap 都无法释放

随着加载的图片越来越多,该集合中维护的 bitmap,可能会造成 OOM

然后,修改代码,用软引用包裹 Bitmap,代码如下:

static HashMap<String,SoftReference<Bitmap>> memCache = new HashMap<String,SoftReference<Bitmap>>();

Java 虚拟机对 4 种引用的处理方式:

1. 强引用,java 中的引用,默认都是强引用。(我们之前讲的垃圾回收机制,是基于强引用)对于 GC 回收来说,如果该对象有另一个对象的到它的强引用,则该对象无法回收

2. 软引用,当 GC 回收内存的时候,一般不会去回收软引用,但是当内存不够用的时候,就可以去回收软引用的内存

3. 弱引用,GC 回收的时候,即便是内存够的时候,也有可能回收

4. 虚引用(一般用不上),对于引用对象之间的关系,没有任何影响,配合引用队列使用。每次要回收这种对象,会把这个对象加入到一个引用队列。程序可以通过遍历这个引用队列,来看队列中是否有对应的虚引用。如果有,说明马上要被回收了

有如下问题:

1. 问题一
    /**
     * <h3>Avoid Soft References for Caching</h3>
     * In practice, soft references are inefficient for caching. The runtime doesn't
     * have enough information on which references to clear and which to keep. Most
     * fatally, it doesn't know what to do when given the choice between clearing a
     * soft reference and growing the heap.
     * <p>
     * <p>The lack of information on the value to your application of each reference
     * limits the usefulness of soft references. References that are cleared too
     * early cause unnecessary work; those that are cleared too late waste memory.
     * <p>
     * <p>Most applications should use an {@code android.util.LruCache} instead of
     * soft references. LruCache has an effective eviction policy and lets the user
     * tune how much memory is allotted.
     */

2. 问题二
    /**
     * 内存缓存:
     * 因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,
     * 这让软引用和弱引用变得不再可靠。  Google建议使用 LruCache
     */

最后,听从谷歌的建议,使用LruCache类,不过记得要重写sizeOf()方法哦!

    static LruCache<String, Bitmap> memCache = new LruCache<String, Bitmap>((int) (MemoryComputingUtils.getMaxMemory() / 4)) {
        @Override
        protected int sizeOf(String key, Bitmap value) {
            int byteCount = value.getByteCount();
            LogUtil.i(TAG, "byteCount = " + byteCount);
            return byteCount;//super.sizeOf(key, value);
        }
    };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值