系列文章
Glide手写实现之资源封装
Glide手写实现之活动缓存
Glide手写实现之内存缓存
Glide手写实现之磁盘缓存
Glide手写实现之生命周期关联
Glide手写实现之网络图片加载实现
Glide手写实现之复用池
图片加载理论分析(网络图片)
前面几篇文章中,我们了解到Glide的三级缓存和生命周期与Activity的绑定。 本文章会把之前的三级缓存融合在一起,成为一套缓存策略。根据生命周期,调度缓存和释放缓存。理论上加载网络图片的时候,先要从缓存中获取,如果缓存中拿不到,再从网络中获取。网络中获取到的图片需要保存到缓存中,以备下次使用。
RequestTargetEngine
加载图片的引擎,先从活动缓存中获取,获取到直接可以设置到ImageView中,否则从内存缓存中获取,获取到设置到ImageView中,否则从磁盘中获取,获取到设置到ImageView中,否则从网络中加载,在回调中设置到ImageView中,并且放到磁盘缓存中。
/**
* 加载图片资源
*/
public class RequestTargetEngine implements LifecycleCallback, ValueCallback, MemoryCacheCallback, ResponseListener {
private final String TAG = RequestTargetEngine.class.getSimpleName();
@Override
public void glideInitAction() {
Log.d(TAG, "glideInitAction: Glide生命周期之 已经开启了 初始化了....");
}
@Override
public void glideStopAction() {
Log.d(TAG, "glideInitAction: Glide生命周期之 已经停止中 ....");
}
@Override
public void glideRecycleAction() {
Log.d(TAG, "glideInitAction: Glide生命周期之 进行释放操作 缓存策略释放操作等 >>>>>> ....");
if (activeCache != null) {
activeCache.closeThread(); // 把活动缓存给释放掉
}
// 把内存缓存移除
}
private ActiveCache activeCache; // 活动缓存
private MemoryCache memoryCache; // 内存缓存
private DiskLruCacheImpl diskLruCache; // 磁盘缓存
// 复用池
private final int MEMORY_MAX_SIZE = 1024 * 1024 * 60;
public RequestTargetEngine() {
if (activeCache == null) {
activeCache = new ActiveCache(this); // 回调告诉外界,Value资源不再使用了 设置监听
}
if (memoryCache == null) {
memoryCache = new MemoryCache(MEMORY_MAX_SIZE); //LRU最少使用的元素会被移除 设置监听
memoryCache.setMemoryCacheCallback(this);
}
// 初始化磁盘缓存
diskLruCache = new DiskLruCacheImpl();
}
private String path;
private Context glideContext;
private String key; // ac037ea49e34257dc5577d1796bb137dbaddc0e42a9dff051beee8ea457a4668
private ImageView imageView; // 显示的目标
/**
* RequestManager传递的值
*/
public void loadValueInitAction(String path, Context glideContext) {
this.path = path;
this.glideContext = glideContext;
key = new Key(path).getKey();
}
public void into(ImageView imageView) {
this.imageView = imageView;
Tool.checkNotEmpty(imageView);
Tool.assertMainThread();
// TODO 加载资源 --》 缓存 ---》网络/SD/ 加载资源 成功后 --》资源 保存到缓存中 >>>
Value value = cacheAction();
if (null != value) {
// 使用完成了 减一
value.nonUseAction();
imageView.setImageBitmap(value.getmBitmap());
}
}
// TODO 加载资源 --》 缓存 ---》网络/SD/ 加载资源 成功后 --》资源 保存到缓存中 >>>
private Value cacheAction() {
// TODO 第一步,判断活动缓存是否有资源,如果有资源 就返回, 否则就继续往下找
Value value = activeCache.get(key);
if (null != value) {
Log.d(TAG, "cacheAction: 本次加载是在(活动缓存)中获取的资源>>>");
// 返回 代表 使用了一次 Value
value.useAction(); // 使用了一次 加一
return value;
}
// TODO 第二步,从内存缓存中去找,如果找到了,内存缓存中的元素 “移动” 到 活动缓存, 然后再返回
value = memoryCache.get(key);
if (null != value) {
// 移动操作
memoryCache.shoudonRemove(key); // 移除内存缓存
activeCache.put(key, value); // 把内存缓存中的元素 加入到活动缓存中
Log.d(TAG, "cacheAction: 本次加载是在(内存缓存)中获取的资源>>>");
// 返回 代表 使用了一次 Value
value.useAction(); // 使用了一次 加一
return value;
}
// TODO 第三步,从磁盘缓存中去找,如果找到了,把磁盘缓存中的元素 加入到 活动缓存中
value = diskLruCache.get(key);
if (null != value) {
// 把磁盘缓存中的元素 --> 加入到活动缓存中
activeCache.put(key, value);
// 把磁盘缓存中的元素 --> 加入到内存缓存中
// memoryCache.put(key, value);
Log.d(TAG, "cacheAction: 本次加载是在(磁盘缓存)中获取的资源>>>");
// 返回 代表 使用了一次 Value
value.useAction(); // 使用了一次 加一
return value;
}
// TODO 第四步,真正的去加载外部资源了, 去网络上加载/去SD本地上加载
value = new LoadDataManager().loadResource(path, this, glideContext);
if (value != null)
return value;
return null;
}
/**
* 活动缓存间接的调用Value所发出的
* 回调告诉外界,Value资源不再使用了
* 监听的方法(Value不再使用了)
* @param key
* @param value
*/
@Override
public void valueNonUseListener(String key, Value value) {
// 把活动缓存操作的Value资源 加入到 内存缓存
if (key != null && value != null) {
memoryCache.put(key, value);
}
}
/**
* 内存缓存发出的
* LRU最少使用的元素会被移除
* @param key
* @param oldValue
*/
@Override
public void entryRemovedMemoryCache(String key, Value oldValue) {
// 添加到复用池 ...... ,空留的功能点
}
// 加载外部资源成功
@Override
public void responseSuccess(Value value) {
if (null != value) {
saveCahce(key, value);
imageView.setImageBitmap(value.getmBitmap());
}
}
// 加载外部资源失败
@Override
public void responseException(Exception e) {
Log.d(TAG, "responseException: 加载外部资源失败 e:" + e.getMessage());
}
/**
* 保存到缓存中
* @param key
* @param value
*/
private void saveCahce(String key, Value value) {
Log.d(TAG, "saveCahce: >>>>>>>>>>>>>>>>>>>>>> 加载外部资源成功后,保存到缓存中 key:" + key + " value:" + value);
value.setKey(key);
if (diskLruCache != null) {
diskLruCache.put(key, value); // 保存到磁盘缓存中
}
}
}
ILoadData
从外部加载资源的统一标准接口
/**
* 加载外部资源 标准
*/
public interface ILoadData {
// 加载外部资源的行为
public Value loadResource(String path, ResponseListener responseListener, Context context);
}
ResponseListener
从外部资源加载的回调接口,成功或者失败都要回调
/**
* 加载外部资源 成功与失败的 回调
*/
public interface ResponseListener {
public void responseSuccess(Value value);
public void responseException(Exception e);
}
LoadDataManager
加载外部资源的实现类,本文只讨论从网络中获取资源。
public class LoadDataManager implements ILoadData, Runnable {
private final String TAG = LoadDataManager.class.getSimpleName();
private String path;
private ResponseListener responseListener;
private Context context;
@Override
public Value loadResource(String path, ResponseListener responseListener, Context context) {
this.path = path;
this.responseListener = responseListener;
this.context = context;
// 加载 网络图片/SD本地图片/..
Uri uri = Uri.parse(path);
// 网络图片
if ("HTTP".equalsIgnoreCase(uri.getScheme()) || "HTTPS".equalsIgnoreCase(uri.getScheme())) {
new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>()).execute(this);
}
// SD本地图片 返回Value
// ....
return null;
}
@Override
public void run() {
InputStream inputStream = null;
HttpURLConnection httpURLConnection = null;
try {
URL url = new URL(path);
URLConnection urlConnection = url.openConnection();
httpURLConnection = (HttpURLConnection) urlConnection;
httpURLConnection.setConnectTimeout(5000);
final int responseCode = httpURLConnection.getResponseCode();
if (HttpURLConnection.HTTP_OK == responseCode) {
inputStream = httpURLConnection.getInputStream();
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
// 成功 切换主线程
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Value value = Value.getInstance();
value.setmBitmap(bitmap);
// 回调成功
responseListener.responseSuccess(value);
}
});
} else {
// 失败 切换主线程
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
responseListener.responseException(new IllegalStateException("请求失败 请求码:" + responseCode));
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "run: 关闭 inputStream.close(); e:" + e.getMessage());
}
}
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
}
}
}
内存缓存插入时机
当活动缓存中的资源不再使用的时候,会将资源缓存到内存缓存
总结
第一次的时候,去网络下载图片,保存到磁盘缓存中(/sd/disk_lru_cache_dir/key)
第二次的时候,从磁盘缓存中获取到,放到活动缓存中,再从活动缓存中,找到了资源
第三次的时候,直接再活动缓存中,找到了资源
第N次的时候,直接再活动缓存中,找到了资源
把Activity退出的时候,生命周期的回调方法,进行释放,活动缓存的释放
又一次加载的时候,从内存缓存中获取了
下一次加载的时候,就是从活动缓存获取了
把App给杀掉
整个活动缓存,整个内存缓存,都没有了
所以启动第一次从磁盘缓存中获取
1.活动缓存,HashMap管理的,用到了弱引用去回收移除 ----> 活动缓存(Value没有被使用了)移除后 交给 内存缓存
2.内存缓存,内存缓存作为第二道防线,LRU算法的
3.磁盘缓存,缓存中最后一道防线,保存手机存储的,LRU算法的
4.生命周期的管理,通过添加Fragment到Activity中实现的。