Android 开发框架 Glide 原理解析
一、复用内存块
复用内存块只能在3.0以后使用。2.3上,bitmap的数据是存储在native的内存区域,并不是在Dalvik的内存堆上。复用内存块,不需要在重新给这个bitmap申请一块新的内存,避免了一次内存的分配和回收,从而改善了运行效率。
二、引用&引用队列
java.lang.ref.Reference 为 软(soft)引用、弱(weak)引用、虚(phantom)引用的父类。
因为Reference对象和垃圾回收密切配合实现,该类可能不能被直接子类化。可以理解为Reference的直接子类都是由jvm定制化处理的,因此在代码中直接继承于Reference类型没有任何作用。但可以继承jvm定制的Reference的子类。其内部提供2个构造函数,一个带queue,一个不带queue。其中queue的意义在于,我们可以在外部对这个queue进行监控。即如果有对象即将被回收,那么相应的reference对象就会被放到这个queue里。我们拿到reference,就可以再作一些事务。
而如果不带的话,就只有不断地轮询reference对象,通过判断里面的get是否返回null( phantomReference对象不能这样作,其get始终返回null,因此它只有带queue的构造函数 )。这两种方法均有相应的使用场景,取决于实际的应用。如weakHashMap中就选择去查询queue的数据,来判定是否有对象将被回收。而ThreadLocalMap,则采用判断get()是否为null来作处理。
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.LinkedList;
public class WeakReferenceDemo {
private static final ReferenceQueue<VeryBig> referenceQueue = new ReferenceQueue<VeryBig>();
public static void main(String args[]) {
int size = 10;
LinkedList<WeakReference<VeryBig>> weakList = new LinkedList<WeakReference<VeryBig>>();
for (int i = 0; i < size; i++) {
weakList.add(new VeryBigWeakReference(new VeryBig("Weak Reference " + i), referenceQueue));
System.out.println("Just created weak: " + weakList.getLast().get().name);
}
System.gc();
try { // 等待上面的垃圾回收线程运行完成
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
checkQueue();
System.out.println("--------------------------------------------------");
checkQueue();
}
public static void checkQueue() {
Reference<? extends VeryBig> ref = null;
while ((ref = referenceQueue.poll()) != null) {
if (ref != null) {
System.out.println("In queue: " + ((VeryBigWeakReference) (ref)).name + "....Class Name = " + ref.getClass().getName());
}
}
}
static class VeryBig {
public String name;
// 占用空间,让线程进行回收
byte[] b = new byte[2 * 1024];
public VeryBig(String name) {
this.name = name;
}
protected void finalize() {
System.out.println("Finalizing VeryBig -> " + name);
}
}
static class VeryBigWeakReference extends WeakReference<VeryBig> {
public String name;
public VeryBigWeakReference(VeryBig big, ReferenceQueue<VeryBig> referenceQueue) {
super(big, referenceQueue);
this.name = big.name;
}
protected void finalize() {
System.out.println("Finalizing VeryBigWeakReference " + name);
}
}
}
三、Glide 内存缓存机制
一般的图片加载库,都是通过内存缓存LruCache、磁盘缓存DiskLruCache中去拿数据,那么Glide也是这样么?
Glide的缓存可以分为两种,第一种是内存缓存,第二种是硬盘缓存。
其中内存缓存又包括活动缓存(WeakReference Cache)和 内存缓存(LruCache)。硬盘缓存就是DiskLruCache。
Glide 通过内存缓存获取数据的流程图如下:
其中Engine加载活动缓存和弱引用缓存的核心逻辑代码如下:
public synchronized <R> LoadStatus load( // 获取资源的 key ,参数有8个 EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options); // 活动缓存 EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); if (active != null) { cb.onResourceReady(active, DataSource.MEMORY_CACHE); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } // 通过 LruCache EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); if (cached != null) { cb.onResourceReady(cached, DataSource.MEMORY_CACHE); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return null; } // 网络加载机制......
其中,Glide从活动缓存(弱引用)里面获取的代码如下:
@Nullable private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } EngineResource<?> active = activeResources.get(key); //如果能拿到资源,则计数器 +1 if (active != null) { active.acquire(); } return active; } #ActiveResources#get() @Nullable synchronized EngineResource<?> get(Key key) { ResourceWeakReference activeRef = activeEngineResources.get(key); if (activeRef == null) { return null; } EngineResource<?> active = activeRef.get(); if (active == null) { cleanupActiveReference(activeRef); } return active; } #EngineResource#acquire() synchronized void acquire() { if (isRecycled) { throw new IllegalStateException("Cannot acquire a recycled resource"); } ++acquired; }
从上面的代码可以看出来,activeEngineResources 为实现了弱引用的 hasmap,通过 key 拿到弱引用的对象,如果获取不到,则可能是因为GC导致对象被回收了,则从 map 中移除;如果拿到对象,则引用计数 acquired +1。
这里说明一下为什么要使用弱引用来缓存当前活跃的资源,而不是像我们之前理解的图片加载框架一般从LruCache获取。这样是为了通过弱引用缓存正在使用的强引用资源,又不阻碍系统需要回收的无引用资源。当前资源正在使用的时候,不会被LruCache算法回收。
Glide从LRU内存缓存里面获取的代码如下:
// 从 lrucache 获取对象 EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); #Engine#loadFromCache() private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } EngineResource<?> cached = getEngineResourceFromCache(key); if (cached != null) { cached.acquire(); activeResources.activate(key, cached); } return cached; }
可以看到其逻辑为:从LRUCache中获取对象,如果对象不为空,则通过activeResources.activate(key, cached); 把它加入弱引用中,且从 LruCache 删除。且 调用 acquire() 让计数器 +1.
综合上述的逻辑,可以看出:Glide 的内存缓存的流程是这样的,先从弱引用中取对象,如果存在,引用计数+1,如果不存在,从 LruCache 取,如果存在,则引用计数+1,并把它存到弱引用中,且自身从 LruCache 移除。
四、Glide 硬盘缓存机制
Glide 的硬盘策略可以分为如下几种:
- DiskCacheStrategy.RESOURCE :只缓存解码过的图片
- DiskCacheStrategy.DATA :只缓存原始图片
- DiskCacheStrategy.ALL : 即缓存原始图片,也缓存解码过的图片啊, 对于远程图片,缓存 DATA 和 RESOURCE;对本地使用 只缓存 RESOURCE。
- DiskCacheStrategy.NONE :不使用硬盘缓存
- DiskCacheStrategy.AUTOMATIC :默认策略,会对本地和和远程图片使用最佳的策略;对下载网络图片,使用 DATA,对于本地图片,使用 RESOURCE
硬盘缓存时通过在 EngineJob 中的 DecodeJob 中完成的,先通过ResourcesCacheGenerator、DataCacheGenerator 看是否能从 DiskLruCache 拿到数据,如果不能,从SourceGenerator去解析数据,并把数据存储到 DiskLruCache 中,后面通过 DataCacheGenerator 的 startNext() 去分发 fetcher 。
最后会回调 EngineJob 的 onResourceReady() 方法了,该方法会加载图片,并把数据存到弱引用中。
五、Glide 生命周期管理机制
生命周期管理主要涉及两个类:Glide.class和RequestManagerRetriever.class,主要用来获得RequestManager。
//with返回一个RequestManager public static RequestManager with(Activity activity) { return getRetriever(activity).get(activity); } //无论调用的是哪个with重载方法,最后都会到这里 public RequestManager get(Activity activity) { if (Util.isOnBackgroundThread()) { return get(activity.getApplicationContext()); } else { assertNotDestroyed(activity); android.app.FragmentManager fm = activity.getFragmentManager(); return fragmentGet(activity, fm, null); } } //这里新建了一个没有视图的RequestManagerFragment private RequestManager fragmentGet(Context context, android.app.FragmentManager fm, android.app.Fragment parentHint) { RequestManagerFragment current = getRequestManagerFragment(fm, parentHint); RequestManager requestManager = current.getRequestManager(); if (requestManager == null) { Glide glide = Glide.get(context); //绑定requestManager和Fragment的Lifecycle requestManager = factory.build( glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); current.setRequestManager(requestManager); } return requestManager; }
RequestManagerFragment.class中持有一个lifecycle,在Fragment进入关键生命周期时会主动通知lifecycle执行相关方法
public class RequestManagerFragment extends Fragment { ... private final ActivityFragmentLifecycle lifecycle; ... @Override public void onStart() { super.onStart(); lifecycle.onStart(); } @Override public void onStop() { super.onStop(); lifecycle.onStop(); } @Override public void onDestroy() { super.onDestroy(); lifecycle.onDestroy(); } }
ActivityFragmentLifecycle.class中持有一个lifecycleListeners,在Fragment进入关键生命周期时Lifecycle会通知他的所有Listener
class ActivityFragmentLifecycle implements Lifecycle { ... private final Set<LifecycleListener> lifecycleListeners;void onStart() { isStarted = true; for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onStart(); } } void onStop() { isStarted = false; for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onStop(); } } void onDestroy() { isDestroyed = true; for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onDestroy(); } } ... }
RequestManger.class关键生命周期中处理加载任务
@Override public void onStart() { resumeRequests(); targetTracker.onStart(); } @Override public void onStop() { pauseRequests(); targetTracker.onStop(); } @Override public void onDestroy() { targetTracker.onDestroy(); for (Target<?> target : targetTracker.getAll()) { clear(target); } targetTracker.clear(); requestTracker.clearRequests(); }
Glide在加载绑定了Activity的生命周期。
在Activity内新建一个无UI的Fragment,这个特殊的Fragment持有一个Lifecycle。通过Lifecycle在Fragment关键生命周期通知RequestManger进行相关的操作。
在生命周期onStart时继续加载,onStop时暂停加载,onDestory是停止加载任务和清除操作。
六、如何自己写一个图片加载框架
异步加载数据,需要考虑使用 线程池 加载。同时需要在展示的时候进行线程切换,那么 Handler 的使用也是必要的。
为了优化数据加载,需要考虑使用 缓存机制, 如使用 LruCache、DiskLruCache。
为了防止加载图片出现OOM,可以考虑使用 弱引用,图片压缩,内存复用 等方式。
为了防止内存泄露,需要注意ImageView的正确使用和生命周期管理。
为了保证App的正常进行,避免因为图片加载导致OOM,需要通过实现 ComponentCallbacks2 进行各个步骤的内存管理。
参考文章: