LRU (Least Recently Used) 的意思就是近期最少使用算法,它的核心思想就是会优先淘汰那些近期最少使用的缓存对象。android
在咱们平常开发中,UI 界面进行网络图片加载是很正常的一件事情,可是当界面上的图片过于多的时候,不可能每次都从网络上进行图片的获取,一方面效率会很低,另外一方面,也会很是耗费用户的流量。算法
Android 为咱们提供了 LruCache 类,使用它咱们能够进行图片的内存缓存,今天咱们就一块儿学习一下吧。缓存
package com.keven.jianshu.part6;
import android.graphics.Bitmap;
import android.util.LruCache;
/**
* Created by keven on 2019/5/28.
*/
public class MyImageLoader {
private LruCache<String, Bitmap> mLruCache;
/**
* 构造函数
*/
public MyImageLoader() {
//设置最大缓存空间为运行时内存的 1/8
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
//计算一个元素的缓存大小
return value.getByteCount();
}
};
}
/**
* 添加图片到 LruCache
*
* @param key
* @param bitmap
*/
public void addBitmap(String key, Bitmap bitmap) {
if (getBitmap(key) == null) {
mLruCache.put(key, bitmap);
}
}
/**
* 从缓存中获取图片
*
* @param key
* @return
*/
public Bitmap getBitmap(String key) {
return mLruCache.get(key);
}
/**
* 从缓存中删除指定的 Bitmap
*
* @param key
*/
public void removeBitmapFromMemory(String key) {
mLruCache.remove(key);
}
}
复制代码
至于代码的具体含义,注释已经进行了诠释。bash
public class Part6ImageActivity extends AppCompatActivity {
private static String imgUrl = "https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1559013549&di=41b6aa8d219f05d44708d296dbf96b5f&src=http://img5.duitang.com/uploads/item/201601/03/20160103233143_4KLWs.jpeg";
private static final int SUCCESS = 0x0001;
private static final int FAIL = 0x0002;
private MyHandler mHandler;
private static ImageView mImageView;
private static MyImageLoader mImageLoader;
private Button mBt_load;
static class MyHandler extends Handler {
//建立一个类继承 Handler
WeakReference<AppCompatActivity> mWeakReference;
public MyHandler(AppCompatActivity activity) {
mWeakReference = new WeakReference<>(activity);
}
//在 handleMessage 方法中对网络下载的图片进行处理
@Override
public void handleMessage(Message msg) {
final AppCompatActivity appCompatActivity = mWeakReference.get();
if (appCompatActivity != null) {
switch (msg.what) {
case SUCCESS://成功
byte[] Picture = (byte[]) msg.obj;
Bitmap bitmap = BitmapFactory.decodeByteArray(Picture, 0, Picture.length);
mImageLoader.addBitmap(ImageUtils.hashKeyForCache(imgUrl), bitmap);
mImageView.setImageBitmap(bitmap);
break;
case FAIL://失败
break;
}
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_part6_image);
//建立 Handler
mHandler = new MyHandler(this);
mImageView = findViewById(R.id.iv_lrucache);
//建立自定义的图片加载类
mImageLoader = new MyImageLoader();
mBt_load = findViewById(R.id.bt_load);
//点击按钮进行图片加载
mBt_load.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bitmap bitmap = getBitmapFromCache();
if (bitmap != null) {//有缓存
LogUtils.e("从缓存中取出图片");
mImageView.setImageBitmap(bitmap);
} else {//没有缓存
LogUtils.e("从网络下载图片");
downLoadBitmap();
}
}
});
}
/**
* 从缓存中获取图片
*
* @return
*/
private Bitmap getBitmapFromCache() {
return mImageLoader.getBitmap(ImageUtils.hashKeyForCache(imgUrl));
}
/**
* 从网络上下载图片
* 使用 OKHttp 进行图片的下载
*/
private void downLoadBitmap() {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(imgUrl)
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
byte[] Picture_bt = response.body().bytes();
Message message = mHandler.obtainMessage();
message.obj = Picture_bt;
message.what = SUCCESS;
mHandler.sendMessage(message);
}
});
}
}
复制代码
其中的布局文件就很简单,一个按钮 + 一个 Imageview网络
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".part6.Part6ImageActivity">
<Button
android:id="@+id/bt_load"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="加载图片"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/iv_lrucache"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
复制代码
代码中还用到了一个工具类,主要用于将图片的 url 转换为 md5 编码后的字符串,用做缓存文件的 key 进行存储,保证其独一性app
public class ImageUtils {
public static String hashKeyForCache(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();
}
}
复制代码
咱们进行加载图片按钮的屡次点击,经过 log 进行查看是否正常缓存ide
com.keven.jianshu E/TAG: 从网络下载图片
com.keven.jianshu E/TAG: 从缓存中取出图片
com.keven.jianshu E/TAG: 从缓存中取出图片
复制代码
能够看出,除了第一次图片是从网络上进行下载,以后都是从缓存中进行获取。函数
A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.
一个包含有限数量强引用的缓存,每次访问一个值,它都会被移动到队列的头部,将一个新的值添加到已经满了的缓存队列时,该队列末尾的值将会被逐出,而且可能会被垃圾回收机制进行回收。工具
建立了一个 LinkedHashMap,三个参数分别为 初始容量、加载因子和访问顺序,当 accessOrder 为 true 时,这个集合的元素顺序就会是访问顺序,也就是访问了以后就会将这个元素放到集合的最后面。布局
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);
}
复制代码
有些人可能会有疑问,初始容量传 0 的话,那岂不是没办法进行存储了,那么建立这个 LinkedHashMap 还有什么意义呢?
其实要解答这个问题并不难,看下源码你就会发现
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);//这里调用父类HashMap的构造方法;
this.accessOrder = accessOrder;
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1; // 默认是1
while (capacity < initialCapacity)//不断翻倍直到大于人为设置的大小
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);//的确如你所言,后面若是须要增大长度,按照capacity*loadFactor取整后增加;
table = new Entry[capacity];
init();
}
复制代码
其中的 trimToSize() 方法用于判断加入元素后是否超过最大缓存数,若是超过就清除掉最少使用的元素。
public final V put(K key, V value) {
// 若是 key 或者 value 为 null,则抛出异常
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized(this) {
// 加入元素的数量,在 putCount() 用到
putCount++;
// 回调用 sizeOf(K key, V value) 方法,这个方法用户本身实现,默认返回 1
size += safeSizeOf(key, value);
// 返回以前关联过这个 key 的值,若是没有关联过则返回 null
previous = map.put(key, value);
if (previous != null) {
// safeSizeOf() 默认返回 1
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
// 该方法默认方法体为空
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
public 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!");
}
// 直到缓存大小 size 小于或等于最大缓存大小 maxSize,则中止循环
if (size <= maxSize) {
break;
}
// 取出 map 中第一个元素
Map.Entry < K, V > toEvict = map.eldest();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
// 删除该元素
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
public Map.Entry<K, V> eldest() {
return head;
}
复制代码
LruCahche 的 get() 方法源码
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
//从 LinkedHashMap 中获取值
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
复制代码
LinkedHashMap 的 get() 方法源码
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
//若是访问顺序设置为 true,则执行 afterNodeAccess(e) 方法
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
复制代码
afterNodeAccess() 方法源码
// 这个方法的做用就是将刚访问过的元素放到集合的最后一位
void afterNodeAccess(Node < K, V > e) {
LinkedHashMap.Entry < K, V > last;
if (accessOrder && (last = tail) != e) {
// 将 e 转换成 LinkedHashMap.Entry
// b 就是这个节点以前的节点
// a 就是这个节点以后的节点
LinkedHashMap.Entry < K, V > p = (LinkedHashMap.Entry < K, V > ) e, b = p.before, a = p.after;
// 将这个节点以后的节点置为 null
p.after = null;
// b 为 null,则表明这个节点是第一个节点,将它后面的节点置为第一个节点
if (b == null) head = a;
// 若是不是,则将 a 上前移动一位
else b.after = a;
// 若是 a 不为 null,则将 a 节点的元素变为 b
if (a != null) a.before = b;
else last = b;
if (last == null) head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
复制代码
从缓存中删除内容,并更新缓存大小
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;
}
复制代码