Android 开发框架 Glide 原理解析

一、复用内存块

复用内存块只能在3.0以后使用。2.3上,bitmap的数据是存储在native的内存区域,并不是在Dalvik的内存堆上。复用内存块,不需要在重新给这个bitmap申请一块新的内存,避免了一次内存的分配和回收,从而改善了运行效率。

在4.4之前,只能重用相同大小的bitmap的内存区域,而4.4之后你可以重用任何bitmap的内存区域,只要这块内存比将要分配内存的bitmap大就可以。这里最好的方法就是使用LRUCache来缓存bitmap,后面来了新的bitmap,可以从cache中按照api版本找到最适合重用的bitmap,来重用它的内存区域。
 
复用内存块类似对象池的技术原理,避免内存的频繁的创建和销毁带来性能的损耗。使用inBitmap能高提升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来作处理。

ReferenceQueue名义上是一个队列,但实际内部并非有实际的存储结构,它的存储是依赖于内部节点之间的关系来表达。可以理解为queue是一个类似于链表的结构,这里的节点其实就是reference本身。可以理解为queue为一个链表的容器,其自己仅存储当前的head节点,而后面的节点由每个reference节点自己通过next来保持即可。
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 进行各个步骤的内存管理。

 

参考文章:

Glide 缓存机制解析(为啥使用弱引用)

Android面试题:讲一讲Glide的原理

  

posted @ 2021-03-14 12:10  灰色飘零  阅读(3223)  评论(0编辑  收藏  举报