继续接着上一次https://www.cnblogs.com/webor2006/p/12313876.html的缓存进行编写。
Bitmap复用池:
概念:
关于啥是Bitmap复用,这里还是借用博主的这篇文章https://www.jianshu.com/p/97fd67720b34的说明过一下,先来看两张对比图:
复用前:
![](https://i-blog.csdnimg.cn/blog_migrate/26d7a1f79983b69956db035956132643.png)
可以看到每一张Bitmp在显示时都申请了一块内存,而如果采用复用Bitmap的情况,则会长成这样:
![](https://i-blog.csdnimg.cn/blog_migrate/fb44de0b3efc77d2e483667bf1598462.png)
也就是如果采用了复用之后,如果存在能被复用的图片会重复使用该图片的内存。那复用之后除了减少内存的开销之外,还有另一层意义:
![](https://i-blog.csdnimg.cn/blog_migrate/38c925ea6abf39bcbc86bd0730e73c33.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c1c18b85c37766e21f46dd037be6c2c0.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b183b00af778a6c338064ef5a0fbff75.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a0b3bbbc1863af3b08d62d39ae7491bd.png)
具体实现:
在上一次的LruMemoryCache中还有一块待完善的,就是移出的方法还木有写,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8adb8961764dce70bb5f1b0bd2a04184.png)
先把它完善一下:
![](https://i-blog.csdnimg.cn/blog_migrate/32dc19a858adde7a2c6f35b530816adc.png)
另外关于这里面的移除还分为主动和被动移除,如果是调了上面这个remove2很显示是主动移除,此时就不应该回调这个回调了:
![](https://i-blog.csdnimg.cn/blog_migrate/52badc689ff46681f8350b533d613dbe.png)
为啥?因为如果回调了此方法,是需要将这个资源往复用池中添加的,如果是主动要移除的资源就没必要往复用池中放了;而如果是被动移除的像LruCache中已经达到了最大上限了再添加则就会将最少使用的给移除掉,此时移除掉的就需要放到复用池当中,所以这里需要加个标识来判断一下,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/453b074c470a2edaf409ea160eb245a9.png)
好,接下来则来开始定义咱们的复用池了,先定义相关的接口:
![](https://i-blog.csdnimg.cn/blog_migrate/eafc0ecfcbd68c882aa428f0400d3386.png)
然后定义具体类:
![](https://i-blog.csdnimg.cn/blog_migrate/afff0dc10619e40a64bf589c3048aeba.png)
package com.android.glidearcstudy.glide.recycle;
import android.graphics.Bitmap;
import androidx.collection.LruCache;
public class LruBitmapPool extends LruCache<Integer, Bitmap> implements BitmapPool {
private final static int MAX_OVER_SIZE_MULTIPLE = 2;
public LruBitmapPool(int maxSize) {
super(maxSize);
}
/**
* 将Bitmap放入复用池
*/
@Override
public void put(Bitmap bitmap) {
//TODO
}
/**
* 获得一个可复用的Bitmap
*/
@Override
public Bitmap get(int width, int height, Bitmap.Config config) {
return null;
}
@Override
protected int sizeOf(Integer key, Bitmap value) {
return value.getAllocationByteCount();
}
@Override
protected void entryRemoved(boolean evicted, Integer key, Bitmap oldValue, Bitmap newValue) {
//TODO
}
}
它也是一个LruCache,接下来具体来实现一下,先实现sizeOf(),跟上次的内存缓存一样:
![](https://i-blog.csdnimg.cn/blog_migrate/0d54b3ac29950bc263812b5ff1f463ee.png)
然后再来实现一下存放方法:
![](https://i-blog.csdnimg.cn/blog_migrate/0e5f4ba625c09003ef639bd8ccf13711.png)
接下来实现get方法,这里从复用池中的所有可复用的bitmap中要找到一个近视差不多大小的图才行,如果要显示的图的大小在复用池中找不到,那此时就不能从复用池来拿了,实现如下:
![](https://i-blog.csdnimg.cn/blog_migrate/5c8575734fd90bdab7c398e8517d6bd0.png)
图中打问号的则是我们接下来要处理的,如何从复用池中来获取跟我们要申请图片大小近视大的呢?当然我们可以用遍历的方法来实现它,但是这里可以借助于不怎么常用的一个Map比较方便的实现,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/2d6869aba5f71da6972ae1c8cbe3c1ab.png)
其中NavigableMap是个啥东东,看一下官方的解释:
![](https://i-blog.csdnimg.cn/blog_migrate/937f11f13df9884a4c79967fedd2c4ba.png)
而我们调用的是这个方法:
![](https://i-blog.csdnimg.cn/blog_migrate/bb50f41ba7d28b3b3dc401b836d60a4d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/16ce14f2213d31e05471a4e9577c62bd.png)
好,接下来继续完善这个get()方法:
![](https://i-blog.csdnimg.cn/blog_migrate/c6e076c20cf3b6ed8f8d6a73bce0f85f.png)
好,接下来咱们来应用一下复用池:
public class CacheTest implements Resource.OnResourceListener, MemoryCache.ResourceRemoveListener {
LruMemoryCache lruMemoryCache;
ActiveResources activeResource;
BitmapPool bitmapPool;
public Resource test(Key key) {
bitmapPool = new LruBitmapPool(10);
//内存缓存
lruMemoryCache = new LruMemoryCache(10);
lruMemoryCache.setResourceRemoveListener(this);
//活动资源缓存
activeResource = new ActiveResources(this);
/**
* 第一步 从活动资源中查找是否有正在使用的图片
*/
Resource resource = activeResource.get(key);
if (null != resource) {
//当不使用的时候 release
resource.acquire();
return resource;
}
/**
* 第二步 从内存缓存中查找
*/
resource = lruMemoryCache.get(key);
if (null != resource) {
//1.为什么从内存缓存移除?
// 因为lru可能移除此图片 我们也可能recycle掉此图片
// 如果不移除,则下次使用此图片从活动资源中能找到,但是这个图片可能被recycle掉了
lruMemoryCache.remove2(key);//从内存缓存中移除
resource.acquire();
activeResource.activate(key, resource);//再加入到活动资源缓存中
return resource;
}
return null;
}
/**
* 这个资源没有正在使用了
* 将其从活动资源移除
* 重新加入到内存缓存中
*/
@Override
public void onResourceReleased(Resource resource) {
//TODO
}
/**
* 从内存缓存被动移除
* 此时得放入复用池
*/
@Override
public void onResourceRemoved(Resource resource) {
bitmapPool.put(resource.getBitmap());
}
}
最后onResourceReleased()代表该资源没有在使用了,此时需要将它加入到内存缓存中,这里的回调还是增加一个key参数,所以修改一下回调方法:
![](https://i-blog.csdnimg.cn/blog_migrate/4250d1f167701c89475694bcd0328682.png)
![](https://i-blog.csdnimg.cn/blog_migrate/28a2b1ce348c89009d446d89a9f5ec55.png)
至此,关于内存缓存和活动资源缓存的代码就写完了。
磁盘缓存:
当内存缓存木有找到我们想要的图片之后,接下来则需要从磁盘缓存开找了,对于glide官方而言,磁盘缓存也是使用三方开源的,看一下:
![](https://i-blog.csdnimg.cn/blog_migrate/baf982b251bf29091c6682d810924d3e.png)
这里就不详细看了,代码量也有点大,先将其拷进工程来直接当工具类来使用既可:
![](https://i-blog.csdnimg.cn/blog_migrate/3ae3329d1782fd4364677689274e77d3.png)
大致瞅一下,其实它也是一个LRU算法,只是说是自己实现了一个,而不是继承至LruCache来实现的:
![](https://i-blog.csdnimg.cn/blog_migrate/718e1bb709ac04e38a4d1174648a5a79.png)
![](https://i-blog.csdnimg.cn/blog_migrate/dd1d1e4835b8410018edfec3d5b8485b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c728d2529bc8107016d8362f7137b570.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c59fb1c6af2613c472b626f8bce524c1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/5ebd4e51f1cb555a3695281108b31fee.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ef98aae79d45cc90d466de9956750a6c.png)
也有像LruCache的trimToSize()的实现:
![](https://i-blog.csdnimg.cn/blog_migrate/53114b5e64fc5eba30ae1844e2250d81.png)
接下来咱们利用这个三方开源的磁盘缓存类来使用一下,先新建一个接口:
![](https://i-blog.csdnimg.cn/blog_migrate/cf8c18acaa5dba0321abdb9cd53f5a6a.png)
接下来具体实现一下,也是大致的过一下,跟缓存的实现差不多:
![](https://i-blog.csdnimg.cn/blog_migrate/b3aed2dcc52d6775edc7b13009aa60e8.png)
package com.android.glidearcstudy.glide.cache;
import android.content.Context;
import androidx.annotation.Nullable;
import com.android.glidearcstudy.glide.Utils;
import com.android.glidearcstudy.glide.disklrucache.DiskLruCache;
import java.io.File;
import java.io.IOException;
import java.security.MessageDigest;
public class DiskLruCacheWrapper implements DiskCache {
final static int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
final static String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";
private MessageDigest MD;
private DiskLruCache diskLruCache;
public DiskLruCacheWrapper(Context context) {
this(new File(context.getCacheDir(), DEFAULT_DISK_CACHE_DIR), DEFAULT_DISK_CACHE_SIZE);
}
protected DiskLruCacheWrapper(File directory, long maxSize) {
try {
MD = MessageDigest.getInstance("SHA-256");
//打开一个缓存目录,如果没有则首先创建它,
// directory:指定数据缓存地址
// appVersion:APP版本号,当版本号改变时,缓存数据会被清除
// valueCount:同一个key可以对应多少文件
// maxSize:最大可以缓存的数据量
diskLruCache = DiskLruCache.open(directory, 1, 1, maxSize);
} catch (Exception e) {
e.printStackTrace();
}
}
public String getKey(Key key) {
key.updateDiskCacheKey(MD);
return new String(Utils.sha256BytesToHex(MD.digest()));
}
@Nullable
@Override
public File get(Key key) {
String k = getKey(key);
File result = null;
try {
DiskLruCache.Value value = diskLruCache.get(k);
if (value != null) {
result = value.getFile(0);
}
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
@Override
public void put(Key key, Writer writer) {
String k = getKey(key);
try {
DiskLruCache.Value current = diskLruCache.get(k);
if (current != null) {
return;
}
DiskLruCache.Editor editor = diskLruCache.edit(k);
try {
File file = editor.getFile(0);
if (writer.write(file)) {
editor.commit();
}
} finally {
editor.abortUnlessCommitted();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void delete(Key key) {
String k = getKey(key);
try {
diskLruCache.remove(k);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void clear() {
try {
diskLruCache.delete();
} catch (IOException e) {
e.printStackTrace();
} finally {
diskLruCache = null;
}
}
}
其中用到了一个加密:
![](https://i-blog.csdnimg.cn/blog_migrate/b3fc4adc6739a1391acddd985351f2b2.png)
其中我们的Key接口需要定义一下:
![](https://i-blog.csdnimg.cn/blog_migrate/7388a61e9d9ebb19e3344c1488c2ae7a.png)
然后它具体的实现类有两个:
![](https://i-blog.csdnimg.cn/blog_migrate/b999a84def957547d60d8e788c20a1be.png)
实现也比较简单:
package com.android.glidearcstudy.glide.load;
import com.android.glidearcstudy.glide.cache.Key;
import java.security.MessageDigest;
public class EngineKey implements Key {
private final Object model;
private final int width;
private final int height;
public EngineKey(Object model, int width, int height) {
this.model = model;
this.width = width;
this.height = height;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update(getKeyBytes());
}
@Override
public byte[] getKeyBytes() {
return toString().getBytes();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EngineKey engineKey = (EngineKey) o;
if (width != engineKey.width) return false;
if (height != engineKey.height) return false;
return model != null ? model.equals(engineKey.model) : engineKey.model == null;
}
@Override
public int hashCode() {
int result = model != null ? model.hashCode() : 0;
result = 31 * result + width;
result = 31 * result + height;
return result;
}
@Override
public String toString() {
return "EngineKey{" +
"model=" + model +
", width=" + width +
", height=" + height +
'}';
}
}
package com.android.glidearcstudy.glide.load;
import com.android.glidearcstudy.glide.cache.Key;
import java.security.MessageDigest;
public class ObjectKey implements Key {
private final Object object;
public ObjectKey(Object object) {
this.object = object;
}
/**
* 当磁盘缓存的时候 key只能是字符串
* ObjectKey变成一个字符串
* 序列化:json
* <p>
* 将ObjectKey转变成一个字符串的手段
*
* @param md md5/sha1
*/
@Override
public void updateDiskCacheKey(MessageDigest md) {
md.update(getKeyBytes());
}
@Override
public byte[] getKeyBytes() {
return object.toString().getBytes();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ObjectKey objectKey = (ObjectKey) o;
return object != null ? object.equals(objectKey.object) : objectKey.object == null;
}
@Override
public int hashCode() {
return object != null ? object.hashCode() : 0;
}
}
关于磁盘缓存先大致过到这。
图片资源加载:
接下来则是图片加载的处理了,图片加载有N种方式:
![](https://i-blog.csdnimg.cn/blog_migrate/4860aa9477b7bbe058a684c5942bc3b2.png)
所以咱们需要将其抽像一下,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/1a4c7677d0c62a96086b6cd2d9099f1c.png)
package com.android.glidearcstudy.glide.load.model;
import com.android.glidearcstudy.glide.cache.Key;
import com.android.glidearcstudy.glide.load.model.data.DataFetcher;
/**
* @param <Model> 此泛型表示的是数据的来源
* @param <Data> 此泛型加载成功后的数据类型,比如InputStream、byte[]
*/
public interface ModelLoader<Model, Data> {
class LoadData<Data> {
//缓存的key
public final Key key;
//加载数据
public final DataFetcher<Data> fetcher;
public LoadData(Key key, DataFetcher<Data> fetcher) {
this.key = key;
this.fetcher = fetcher;
}
}
/**
* 此Loader是否能够处理对应Model的数据
*/
boolean handles(Model model);
/**
* 创建加载数据
*/
LoadData<Data> buildData(Model model);
}
其中DataFetcher也是一个接口,不同的形式加载是不一样的:
![](https://i-blog.csdnimg.cn/blog_migrate/7065148893ab7e6d966d2ac1ee44280f.png)
package com.android.glidearcstudy.glide.load.model.data;
/**
* 负责数据获取
*/
public interface DataFetcher<Data> {
interface DataFetcherCallback<Data> {
/**
* 数据加载完成
*/
void onFetcherReady(Data data);
/**
* 加载失败
*/
void onLoadFaled(Exception e);
}
void loadData(DataFetcherCallback<? super Data> callback);
void cancel();
Class<?> getDataClass();
}
好,下面来实现一下具体的数据获取的方式,首先是从网络上获取,最终返回是一个InputStream,所以此时实现如下:
![](https://i-blog.csdnimg.cn/blog_migrate/d0231cbbeb50600fbc1eeafb1187855e.png)
package com.android.glidearcstudy.glide.load.model.data;
import android.net.Uri;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 网络数据加载
*/
public class HttpUriFetcher implements DataFetcher<InputStream> {
private final Uri uri;
private boolean isCanceled;
public HttpUriFetcher(Uri uri) {
this.uri = uri;
}
@Override
public void loadData(DataFetcherCallback<? super InputStream> callback) {
HttpURLConnection conn = null;
InputStream is = null;
try {
URL url = new URL(uri.toString());
conn = (HttpURLConnection) url.openConnection();
conn.connect();
is = conn.getInputStream();
int responseCode = conn.getResponseCode();
if (isCanceled) {
return;
}
if (responseCode == HttpURLConnection.HTTP_OK) {
callback.onFetcherReady(is);
} else {
callback.onLoadFaled(new RuntimeException(conn.getResponseMessage()));
}
} catch (Exception e) {
callback.onLoadFaled(e);
} finally {
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != conn) {
conn.disconnect();
}
}
}
@Override
public void cancel() {
isCanceled = true;
}
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
}
而文件数据加载则是:
![](https://i-blog.csdnimg.cn/blog_migrate/78416c4939ae7b611b688ed32d864e1d.png)
package com.android.glidearcstudy.glide.load.model.data;
import android.content.ContentResolver;
import android.net.Uri;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
* 文件数据加载
*/
public class FileUriFetcher implements DataFetcher<InputStream> {
private final Uri uri;
private final ContentResolver cr;
public FileUriFetcher(Uri uri, ContentResolver cr) {
this.uri = uri;
this.cr = cr;
}
@Override
public void loadData(DataFetcherCallback<? super InputStream> callback) {
InputStream is = null;
try {
is = cr.openInputStream(uri);
callback.onFetcherReady(is);
} catch (FileNotFoundException e) {
callback.onLoadFaled(e);
} finally {
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public void cancel() {
}
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
}
好,对于之前定义的ModelLoader定义了接口还木有定义实现类,下面针对HTTP的来实现一下:
![](https://i-blog.csdnimg.cn/blog_migrate/fd77519763cdd817a1436dcc44067b3f.png)
package com.android.glidearcstudy.glide.load.model;
import android.net.Uri;
import com.android.glidearcstudy.glide.load.ObjectKey;
import com.android.glidearcstudy.glide.load.model.data.HttpUriFetcher;
import java.io.InputStream;
public class HttpUriLoader implements ModelLoader<Uri, InputStream> {
/**
* http类型的uri此loader才支持,只支持http和https
*/
@Override
public boolean handles(Uri uri) {
String scheme = uri.getScheme();
return scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https");
}
@Override
public LoadData<InputStream> buildData(Uri uri) {
return new LoadData<InputStream>(new ObjectKey(uri), new HttpUriFetcher(uri));
}
}
可以看到这个ModelLoader就已经将要加载的所有东东都组装好,那怎么用呢?下面用测试类来试一下:
![](https://i-blog.csdnimg.cn/blog_migrate/869019932414e9ae8a856e8d9fc66d17.png)
可见使用非常之灵活,目前只实现了一个Http的加载器,还有其它的加载类型只要对ModelLoader进行拓展既可,基实可以看一下glide官方其实拓展了N多个Loader,可以大致瞅一下:
![](https://i-blog.csdnimg.cn/blog_migrate/8841ddd9995a7234294576461b8e8842.png)
重点是要学会这种基础灵活的框架搭建的思路,目前先只创建了一个http的加载器,关于其它的Loader的创建下次再继续。