缓存机制的作用
1. 服务器异常,用户无法联网看到的内容
2. 节省用户的流量(例如:json 数据和图片数据,无需经常向服务器端请求数据)
3. 提高本地显示的响应速度
json 数据的缓存
我们常常从服务器端,获取 json 数据。可以把它存放在本地文件中,当再次需要时,先从本地判断是否存放;若有则直接使用,若无,则再次从服务器端获取。(json 数据占用内存不大,且不怎么耗费流量,一般不进行系统内存的存储)
json 数据的存储注意点
1. 存储的系统文件夹:cache 目录下(系统容易删除)或 SDCard 目录下(SD 卡损坏且数据丢失,不会有太大干扰)
2. 存放于 Andrid 的 SharedPreferences 中,通过键值对存取,需要使用时,再取出
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);
}
};