在本文中,我们将为您详细介绍Androidlrucache实现与使用的相关知识,并且为您解答关于Android内存优化的疑问,此外,我们还会提供一些关于ActiveAndroidandroid.sup
在本文中,我们将为您详细介绍Android lrucache 实现与使用的相关知识,并且为您解答关于Android内存优化的疑问,此外,我们还会提供一些关于ActiveAndroid android.support.v4.util.LruCache、Android 1.6中的Android LruCache、Android DiskLruCache完全解析、Android LruCache : how to put and get user''s data的有用信息。
本文目录一览:- Android lrucache 实现与使用(Android内存优化)(android lrucache原理)
- ActiveAndroid android.support.v4.util.LruCache
- Android 1.6中的Android LruCache
- Android DiskLruCache完全解析
- Android LruCache : how to put and get user''s data
Android lrucache 实现与使用(Android内存优化)(android lrucache原理)
什么是LruCache?
LruCache实现原理是什么?
这两个问题其实可以作为一个问题来回答,知道了什么是 LruCache,就只然而然的知道 LruCache 的实现原理;Lru的全称是Least Recently Used ,近期最少使用的!所以我们可以推断出 LruCache 的实现原理:把近期最少使用的数据从缓存中移除,保留使用最频繁的数据,那具体代码要怎么实现呢,我们进入到源码中看看。
LruCache源码分析
public class LruCache<K, V> {
//缓存 map 集合,为什么要用LinkedHashMap
//因为没错取了缓存值之后,都要进行排序,以确保
//下次移除的是最少使用的值 private final LinkedHashMap<K, V> map; //当前缓存的值 private int size; //最大值 private int maxSize; //添加到缓存中的个数 private int putCount; //创建的个数 private int createCount; //被移除的个数 private int evictionCount; //命中个数 private int hitCount; //丢失个数 private int missCount; //实例化 Lru,需要传入缓存的最大值 //这个最大值可以是个数,比如对象的个数,也可以是内存的大小 //比如,最大内存只能缓存5兆 public LruCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap<K, V>(0, 0.75f, true); } //重置最大缓存的值 public void resize(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } synchronized (this) { this.maxSize = maxSize; } trimToSize(maxSize); } //通过 key 获取缓存值 public final V get(K key) { if (key == null) { throw new NullPointerException("key == null"); } V mapValue; synchronized (this) { mapValue = map.get(key); if (mapValue != null) { hitCount++; return mapValue; } missCount++; } //如果没有,用户可以去创建 V createdValue = create(key); if (createdValue == null) { return null; } synchronized (this) { createCount++; mapValue = map.put(key, createdValue); if (mapValue != null) { // There was a conflict so undo that last put map.put(key, mapValue); } else { //缓存的大小改变 size += safeSizeOf(key, createdValue); } } //这里没有移除,只是改变了位置 if (mapValue != null) { entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { //判断缓存是否越界 trimToSize(maxSize); return createdValue; } } //添加缓存,跟上面这个方法的 create 之后的代码一样的 public final V put(K key, V value) { if (key == null || value == null) { throw new NullPointerException("key == null || value == null"); } V previous; synchronized (this) { putCount++; size += safeSizeOf(key, value); previous = map.put(key, value); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, value); } trimToSize(maxSize); return previous; } //检测缓存是否越界 private void trimToSize(int maxSize) { while (true) { K key; V value; synchronized (this) { if (size < 0 || (map.isEmpty() && size != 0)) { throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!"); } //如果没有,则返回 if (size <= maxSize) { break; } //以下代码表示已经超出了最大范围 Map.Entry<K, V> toEvict = null; for (Map.Entry<K, V> entry : map.entrySet()) { toEvict = entry; } if (toEvict == null) { break; } //移除最后一个,也就是最少使用的缓存 key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } } //手动移除,用户调用 public final V remove(K key) { if (key == null) { throw new NullPointerException("key == null"); } V previous; synchronized (this) { previous = map.remove(key); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, null); } return previous; } //这里用户可以重写它,实现数据和内存回收操作 protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {} protected V create(K key) { return null; } private int safeSizeOf(K key, V value) { int result = sizeOf(key, value); if (result < 0) { throw new IllegalStateException("Negative size: " + key + "=" + value); } return result; } //这个方法要特别注意,跟我们实例化 LruCache 的 maxSize 要呼应,怎么做到呼应呢,比如 maxSize 的大小为缓存的个数,这里就是 return 1就 ok,如果是内存的大小,如果5M,这个就不能是个数 了,这是应该是每个缓存 value 的 size 大小,如果是 Bitmap,这应该是 bitmap.getByteCount(); protected int sizeOf(K key, V value) { return 1; } //清空缓存 public final void evictAll() { trimToSize(-1); // -1 will evict 0-sized elements } public synchronized final int size() { return size; } public synchronized final int maxSize() { return maxSize; } public synchronized final int hitCount() { return hitCount; } public synchronized final int missCount() { return missCount; } public synchronized final int createCount() { return createCount; } public synchronized final int putCount() { return putCount; } public synchronized final int evictionCount() { return evictionCount; } public synchronized final Map<K, V> snapshot() { return new LinkedHashMap<K, V>(map); } }
LruCache 使用
先来看两张内存使用的图
图-1
图-2
以上内存分析图所分析的是同一个应用的数据,唯一不同的是图-1没有使用 LruCache,而图-2使用了 LruCache;可以非常明显的看到,图-1的内存使用明显偏大,基本上都是在30M左右,而图-2的内存使用情况基本上在20M左右。这就足足省了将近10M的内存!
ok,下面把实现代码贴出来
/**
* Created by gyzhong on 15/4/5.
*/
public class LruPageAdapter extends PagerAdapter { private List<String> mData ; private LruCache<String,Bitmap> mLruCache ; private int mTotalSize = (int) Runtime.getRuntime().totalMemory(); private ViewPager mViewPager ; public LruPageAdapter(ViewPager viewPager ,List<String> data){ mData = data ; mViewPager = viewPager ; /*实例化LruCache*/ mLruCache = new LruCache<String,Bitmap>(mTotalSize/5){ /*当缓存大于我们设定的最大值时,会调用这个方法,我们可以用来做内存释放操作*/ @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { super.entryRemoved(evicted, key, oldValue, newValue); if (evicted && oldValue != null){ oldValue.recycle(); } } /*创建 bitmap*/ @Override protected Bitmap create(String key) { final int resId = mViewPager.getResources().getIdentifier(key,"drawable", mViewPager.getContext().getPackageName()) ; return BitmapFactory.decodeResource(mViewPager.getResources(),resId) ; } /*获取每个 value 的大小*/ @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } } ; } @Override public Object instantiateItem(ViewGroup container, int position) { View view = LayoutInflater.from(container.getContext()).inflate(R.layout.view_pager_item, null) ; ImageView imageView = (ImageView) view.findViewById(R.id.id_view_pager_item); Bitmap bitmap = mLruCache.get(mData.get(position)); imageView.setImageBitmap(bitmap); container.addView(view); return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } @Override public int getCount() { return mData.size(); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } }
总结
1、LruCache 是基于 Lru算法实现的一种缓存机制;
2、Lru算法的原理是把近期最少使用的数据给移除掉,当然前提是当前数据的量大于设定的最大值。
3、LruCache 没有真正的释放内存,只是从 Map中移除掉数据,真正释放内存还是要用户手动释放。
ActiveAndroid android.support.v4.util.LruCache
昨天在别人的指导下可以在 Android Studio 中部署 ActiveAndroid 了,但是今天想再测试下,结果又出问题了。
运行的时候,我拿自己的手机真机测试,打开软件的瞬间就崩溃了,跳出了个错误,在 AS 中查看错误提示是这样的:
10-21 14:53:15.369 3095-3095/com.ccsoft.test1 E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.NoClassDefFoundError: android.support.v4.util.LruCache
at com.activeandroid.Cache.initialize(Cache.java:73)
at com.activeandroid.ActiveAndroid.initialize(ActiveAndroid.java:44)
at com.activeandroid.ActiveAndroid.initialize(ActiveAndroid.java:34)
at com.activeandroid.ActiveAndroid.initialize(ActiveAndroid.java:30)
at com.activeandroid.app.Application.onCreate(Application.java:25)
at com.ccsoft.test1.MyApplication.onCreate(MyApplication.java:14)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:999)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4408)
at android.app.ActivityThread.access$1300(ActivityThread.java:147)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1285)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5004)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:558)
at dalvik.system.NativeStart.main(Native Method)
然后第一行错误定位到 Cache.java 中使用 LruCache 有错误。
然后我又回到自己创建的类 MyApplication 测试使用 LruCache,结果只有 android.util 下有个 LruCache
没有包 android.support.v4.util
这是什么情况。望指导
Android 1.6中的Android LruCache
我需要在Android 1.6中使用LruCache(来自Android 3.1的api)
我使用支持LruCache的Android支持库v4(符合本手册http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html#memory-cache)
但问题是我一直得到NoClassDefNotFoundError.我在我的项目中包含support.v4库(在其他活动中我使用ViewPager …)
任何人都知道为什么会这样?
解决方法:
你需要使用android.support.v4.util.LruCache< K,V>不是android.util.LruCache< K,V>因为第二个只存在于具有API> = 12的Android上…检查是否从支持库导入了一个而不是从SDK …
Android DiskLruCache完全解析
1.介绍:其中防止多图OOM的核心解决思路就是使用LruCache技术。但LruCache只是管理了内存中图片的存储与释放,如果图片从内存中被移除的话,那么又需要从网络上重新加载一次图片,这显然非常耗时。对此,Google又提供了一套硬盘缓存的解决方案:DiskLruCache
其实DiskLruCache并没有限制数据的缓存位置,可以自由地进行设定,但是通常情况下多数应用程序都会将缓存的位置选择为 /sdcard/Android/data/<application package>/cache 这个路径。选择在这个位置有两点好处:
第一,这是存储在SD卡上的,因此即使缓存再多的数据也不会对手机的内置存储空间有任何影响,只要SD卡空间足够就行。
第二,这个路径被Android系统认定为应用程序的缓存路径,当程序被卸载的时候,这里的数据也会一起被清除掉,这样就不会出现删除程序之后手机上还有很多残留数据的问题。
使用DiskLruCache进行缓存,一般目录会生成一个journal日志,程序对每张图片的操作记录都存放在这个文件中。
2.打开缓存
DiskLruCache是不能new出实例的,如果我们要创建一个DiskLruCache的实例,则需要调用它的open()方法,接口如下所示:
/**
* Opens the cache in {@code directory}, creating a cache if none exists
* there.
*
* @param directory a writable directory
* @param appVersion
* @param valueCount the number of values per cache entry. Must be positive.
* @param maxSize the maximum number of bytes this cache should use to store
* @throws java.io.IOException if reading or writing the cache directory fails
*/
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
throws IOException {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (valueCount <= 0) {
throw new IllegalArgumentException("valueCount <= 0");
}
// prefer to pick up where we left off
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {
try {
cache.readJournal();
cache.processJournal();
cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),
IO_BUFFER_SIZE);
return cache;
} catch (IOException journalIsCorrupt) {
// System.logW("DiskLruCache " + directory + " is corrupt: "
// + journalIsCorrupt.getMessage() + ", removing");
cache.delete();
}
}
// create a new empty cache
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
open()方法接收四个参数,第一个参数指定的是数据的缓存地址,第二个参数指定当前应用程序的版本号,第三个参数指定同一个key可以对应多少个缓存文件,基本都是传1,第四个参数指定最多可以缓存多少字节的数据。
其中缓存地址前面已经说过了,通常都会存放在 /sdcard/Android/data/<application package>/cache 这个路径下面,但同时我们又需要考虑如果这个手机没有SD卡,或者SD正好被移除了的情况,因此比较优秀的程序都会专门写一个方法来获取缓存地址,如下所示:
public 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();
}
return new File(cachePath + File.separator + uniqueName);
}
可以看到,当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径,否则就调用getCacheDir()方法来获取缓存路径。前者获取到的就是 /sdcard/Android/data/<application package>/cache 这个路径,而后者获取到的是 /data/data/<application package>/cache 这个路径。
接着又将获取到的路径和一个uniqueName进行拼接,作为最终的缓存路径返回。那么这个uniqueName又是什么呢?其实这就是为了对不同类型的数据进行区分而设定的一个唯一值,比如说缓存bitmap、object等文件夹。
接着是应用程序版本号,我们可以使用如下代码简单地获取到当前应用程序的版本号:
public int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
需要注意的是,每当版本号改变,缓存路径下存储的所有数据都会被清除掉,因为DiskLruCache认为当应用程序有版本更新的时候,所有的数据都应该从网上重新获取。
因此,一个非常标准的open()方法就可以这样写:
DiskLruCache mDiskLruCache = null;
try {
File cacheDir = getDiskCacheDir(context, "bitmap");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
首先调用getDiskCacheDir()方法获取到缓存地址的路径,然后判断一下该路径是否存在,如果不存在就创建一下。接着调用DiskLruCache的open()方法来创建实例,并把四个参数传入即可。
有了DiskLruCache的实例之后,我们就可以对缓存的数据进行操作了,操作类型主要包括写入、访问、移除等操作
3.写入缓存
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
out = new BufferedOutputStream(outputStream, 8 * 1024);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return false;
}
这段代码相当基础,相信大家都看得懂,就是访问urlString中传入的网址,并通过outputStream写入到本地。
使用DiskLruCache来进行写入了,写入的操作是借助DiskLruCache.Editor这个类完成的。类似地,这个类也是不能new的,需要调用DiskLruCache的edit()方法来获取实例,接口如下所示:
public Editor edit(String key) throws IOException
可以看到,edit()方法接收一个参数key,这个key将会成为缓存文件的文件名,并且必须要和图片的URL是一一对应的。那么怎样才能让key和图片的URL能够一一对应呢?直接使用URL来作为key?不太合适,因为图片URL中可能包含一些特殊字符,这些字符有可能在命名文件时是不合法的。其实最简单的做法就是将图片的URL进行MD5编码,编码后的字符串肯定是唯一的,并且只会包含0-F这样的字符,完全符合文件的命名规则
/**
* 传入缓存的key值,以得到相应的MD5值
*
* @param key
* @return
*/
public static String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private static 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();
}
代码很简单,现在我们只需要调用一下hashKeyForDisk()方法,并把图片的URL传入到这个方法中,就可以得到对应的key了。
因此,现在就可以这样写来得到一个DiskLruCache.Editor的实例:
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
有了DiskLruCache.Editor的实例之后,我们可以调用它的newOutputStream()方法来创建一个输出流,然后把它传入到downloadUrlToStream()中就能实现下载并写入缓存的功能了。注意newOutputStream()方法接收一个index参数,由于前面在设置valueCount的时候指定的是1,所以这里index传0就可以了。在写入操作执行完之后,我们还需要调用一下commit()方法进行提交才能使写入生效,调用abort()方法的话则表示放弃此次写入。
因此,一次完整写入操作的代码如下所示:
new Thread(new Runnable() {
@Override
public void run() {
try {
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
由于这里调用了downloadUrlToStream()方法来从网络上下载图片,所以一定要确保这段代码是在子线程当中执行的。注意在代码的最后我还调用了一下flush()方法,这个方法并不是每次写入都必须要调用的,但在这里却不可缺少,我会在后面说明它的作用。
4.读取缓存
缓存已经写入成功之后,接下来我们就该学习一下如何读取了。读取的方法要比写入简单一些,主要是借助DiskLruCache的get()方法实现的,接口如下所示:
ublic synchronized Snapshot get(String key) throws IOException
很明显,get()方法要求传入一个key来获取到相应的缓存数据,而这个key毫无疑问就是将图片URL进行MD5编码后的值了,因此读取缓存数据的代码就可以这样写:
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
很奇怪的是,这里获取到的是一个DiskLruCache.Snapshot对象,这个对象我们该怎么利用呢?很简单,只需要调用它的getInputStream()方法就可以得到缓存文件的输入流了。同样地,getInputStream()方法也需要传一个index参数,这里传入0就好。有了文件的输入流之后,想要把缓存图片显示到界面上就轻而易举了。所以,一段完整的读取缓存,并将图片加载到界面上的代码如下所示:
try {
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
InputStream is = snapShot.getInputStream(0);
Bitmap bitmap = BitmapFactory.decodeStream(is);
mImage.setImageBitmap(bitmap);
}
} catch (IOException e) {
e.printStackTrace();
}
5.移除缓存
学习完了写入缓存和读取缓存的方法之后,最难的两个操作你就都已经掌握了,那么接下来要学习的移除缓存对你来说也一定非常轻松了。移除缓存主要是借助DiskLruCache的remove()方法实现的,接口如下所示:
public synchronized boolean remove(String key) throws IOException
try {
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
mDiskLruCache.remove(key);
} catch (IOException e) {
e.printStackTrace();
}
用法虽然简单,但是你要知道,这个方法我们并不应该经常去调用它。因为你完全不需要担心缓存的数据过多从而占用SD卡太多空间的问题,DiskLruCache会根据我们在调用open()方法时设定的缓存最大值来自动删除多余的缓存。只有你确定某个key对应的缓存内容已经过期,需要从网络获取最新数据的时候才应该调用remove()方法来移除缓存。
6. size()
这个方法会返回当前缓存路径下所有缓存数据的总字节数,以byte为单位,如果应用程序中需要在界面上显示当前缓存数据的总大小,就可以通过调用这个方法计算出来。比如网易新闻中就有这样一个功能,如下图所示:
7.flush()
这个方法用于将内存中的操作记录同步到日志文件(也就是journal文件)当中。这个方法非常重要,因为DiskLruCache能够正常工作的前提就是要依赖于journal文件中的内容。前面在讲解写入缓存操作的时候我有调用过一次这个方法,但其实并不是每次写入缓存都要调用一次flush()方法的,频繁地调用并不会带来任何好处,只会额外增加同步journal文件的时间。比较标准的做法就是在Activity的onPause()方法中去调用一次flush()方法就可以了。
8.close()
这个方法用于将DiskLruCache关闭掉,是和open()方法对应的一个方法。关闭掉了之后就不能再调用DiskLruCache中任何操作缓存数据的方法,通常只应该在Activity的onDestroy()方法中去调用close()方法。
9.delete()
这个方法用于将所有的缓存数据全部删除,比如说网易新闻中的那个手动清理缓存功能,其实只需要调用一下DiskLruCache的delete()方法就可以实现了。
10.解读journal
由于现在只缓存了一张图片,所以journal中并没有几行日志,我们一行行进行分析。第一行是个固定的字符串“libcore.io.DiskLruCache”,标志着我们使用的是DiskLruCache技术。第二行是DiskLruCache的版本号,这个值是恒为1的。第三行是应用程序的版本号,我们在open()方法里传入的版本号是什么这里就会显示什么。第四行是valueCount,这个值也是在open()方法中传入的,通常情况下都为1。第五行是一个空行。前五行也被称为journal文件的头,这部分内容还是比较好理解的,但是接下来的部分就要稍微动点脑筋了。
第六行是以一个DIRTY前缀开始的,后面紧跟着缓存图片的key。通常我们看到DIRTY这个字样都不代表着什么好事情,意味着这是一条脏数据。没错,每当我们调用一次DiskLruCache的edit()方法时,都会向journal文件中写入一条DIRTY记录,表示我们正准备写入一条缓存数据,但不知结果如何。然后调用commit()方法表示写入缓存成功,这时会向journal中写入一条CLEAN记录,意味着这条“脏”数据被“洗干净了”,调用abort()方法表示写入缓存失败,这时会向journal中写入一条REMOVE记录。也就是说,每一行DIRTY的key,后面都应该有一行对应的CLEAN或者REMOVE的记录,否则这条数据就是“脏”的,会被自动删除掉。
如果你足够细心的话应该还会注意到,第七行的那条记录,除了CLEAN前缀和key之外,后面还有一个152313,这是什么意思呢?其实,DiskLruCache会在每一行CLEAN记录的最后加上该条缓存数据的大小,以字节为单位。152313也就是我们缓存的那张图片的字节数了,换算出来大概是148.74K,和缓存图片刚刚好一样大
前面我们所学的size()方法可以获取到当前缓存路径下所有缓存数据的总字节数,其实它的工作原理就是把journal文件中所有CLEAN记录的字节数相加,求出的总合再把它返回而已。
除了DIRTY、CLEAN、REMOVE之外,还有一种前缀是READ的记录,这个就非常简单了,每当我们调用get()方法去读取一条缓存数据时,就会向journal文件中写入一条READ记录。因此,像网易新闻这种图片和数据量都非常大的程序,journal文件中就可能会有大量的READ记录。
那么你可能会担心了,如果我不停频繁操作的话,就会不断地向journal文件中写入数据,那这样journal文件岂不是会越来越大?这倒不必担心,DiskLruCache中使用了一个redundantOpCount变量来记录用户操作的次数,每执行一次写入、读取或移除缓存的操作,这个变量值都会加1,当变量值达到2000的时候就会触发重构journal的事件,这时会自动把journal中一些多余的、不必要的记录全部清除掉,保证journal文件的大小始终保持在一个合理的范围内。
Android LruCache : how to put and get user''s data
package zhangphil.cache;
import android.graphics.Bitmap;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.util.LruCache;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int CACHE_SIZE = 8 * 1024 * 1024;
LruCache<String, Bitmap> mLruCache = new LruCache(CACHE_SIZE);
int count = 5;
String key;
Bitmap bmp;
for (int i = 0; i < count; i++) {
key = String.valueOf(i);
bmp = Bitmap.createBitmap(i + 1, i + 2, Bitmap.Config.ARGB_8888);
mLruCache.put(key, bmp);
}
for (int i = 0; i < count; i++) {
key = String.valueOf(i);
bmp = mLruCache.get(key);
Log.d("key:" + key, "value:" + bmp.getWidth() + "," + bmp.getHeight());
}
}
}
关于Android lrucache 实现与使用和Android内存优化的问题我们已经讲解完毕,感谢您的阅读,如果还想了解更多关于ActiveAndroid android.support.v4.util.LruCache、Android 1.6中的Android LruCache、Android DiskLruCache完全解析、Android LruCache : how to put and get user''s data等相关内容,可以在本站寻找。
本文标签: