五.Glide

前言

Glide 4.11.0版本代码剖析
主线:
Glide.with(MainActivity.this).load(url).into(imageView);

总体认识这几个方法都实现了哪些功能:

  • with 通过一个空白的Fragment去管理生命周期,最终返回RequestManager对象
  • load 构建出RequestBuilder对象给后面into方法使用
  • into 实现了缓存机制
    • 等待队列和运行队列
    • 活动缓存
    • 内存缓存
    • 磁盘缓存
    • 网络请求

1.with方法

1.1 如何监听Glide的生命周期

Glide利用一个空白的Fragment去监听Activity的生命周期,然后可以根据生命周期的变化,对Glide加载图片的BitMap进行回收处理。
SupportRequestManagerFragment:空白的Fragment,androidX
RequestManagerFragment: 空白的Fragment,android.app
RequestManagerRetriever:通过这个类的get方法返回RequestManager对象
RequestManager:对图片加载进行管理,比如:页面关闭时对图片BitMap进行销毁

1.2 生命周期作用域Application、Activity、Fragment

根据Glide.with(参数)传入的参数来决定作用域,不同的作用域生命周期不同。

  • 如果在子线程使用Glide,不论传入什么参数,作用域是Application,Glide与应用的生命周期相同
    不会创建一个空白的Fragment来监听生命周期
  • 如果在主线程中使用Glide
    会创建一个空白的Fragment来监听生命周期
    • 传入的参数是view,那么作用域是Activity或Fragment
    • 传入的参数是Fragment,作用域是Fragment
    • 传入的参数是Activity,作用域是Activity
    • 传入的参数是ServiceContext或ApplicationContext,作用域是Application,生命周期等同于App的生命周期,同样不会创建一个空白的Fragment来监听生命周期
1.3 生命周期的绑定

将空白的Fragment绑定到RequestMananger上,RequestManagerRetriever.java代码如下:

private RequestManager supportFragmentGet({
    
    
  //省略非核心代码

  //获取到空白的Fragment
  SupportRequestManagerFragment current =
          getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
  //将RequestManager绑定到空白的Fragment上,空白的Fragment监听到Activity生命周期时,
  //空白Frag同时会执行RequestManager的与生命周期同名的方法
  RequestManager requestManager = current.getRequestManager();
  if (requestManager == null) {
    
    
    Glide glide = Glide.get(context);
    //通过工厂,创建RequestManager对象
    requestManager =
        factory.build(
            glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
    //将RequestManager绑定到空白Fragment,空白Fragment生命周期发生改变时,
    //RequestManager中会执行与生命周期同名的方法
    current.setRequestManager(requestManager);
  }
  return requestManager;
}

/**
 * 通过是Tag或者是Map集合获取到空白Fragment  
 **/
private SupportRequestManagerFragment getSupportRequestManagerFragment(
    @NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {
    
    
  //通过Tag获取到空白Fragment
  SupportRequestManagerFragment current =
      (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
  if (current == null) {
    
    
    //从缓存的Map集合中获取空白Fragment
    current = pendingSupportRequestManagerFragments.get(fm);
    if (current == null) {
    
    
      //通过Tag和Map缓存集合都没有获取到,则创建一个空白的Fragment
      current = new SupportRequestManagerFragment();
      //将创建的空白Fragment缓存到Map集合
      pendingSupportRequestManagerFragments.put(fm, current);
      //将空白Fragment添加到我们的Activity或Fragment中去
      fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
      //发送Handler消息,在主线程中移除Map缓存集合中的空白Fragment
      //主要目的是保证主线程中,FragmentManager通过Tag获取到的空白Fragment非空
      handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
    }
  }
  return current;
}

/**
 * 接收发送过来的消息,移除Map缓存集合中的空白Fragment
 **/
public boolean handleMessage(Message message) {
    
    
  switch (message.what) {
    
    
    case ID_REMOVE_FRAGMENT_MANAGER:
      android.app.FragmentManager fm = (android.app.FragmentManager) 
      //从Map缓存集合中移除空白Fragment android.app包下的Fragment
      removed = pendingRequestManagerFragments.remove(fm);
      break;
    case ID_REMOVE_SUPPORT_FRAGMENT_MANAGER:
      FragmentManager supportFm = (FragmentManager) message.obj;
      //从Map缓存集合中移除空白Fragment  AndroidX下的Fragment
      removed = pendingSupportRequestManagerFragments.remove(supportFm);
      break;
  }
  return handled;
}

在绑定空白Fragment的时候,首先需要获取到空白的Fragment,可以通过Tag标签获取到已经添加到Activity上的空白Fragment,也可以通过Map缓存集合中获取到创建的空白Fragment,然后将空白的Fragment绑定到RequestManager身上。

注意:
RequestManager实现了LifeCycleListener接口, 是在构造函数中以Handler发送消息的方式注册到ActivityFragmentLifeCycle中去的。所以,当页面生命周期发生变化的时候,会执行RequestManager中的与生命周期同名的方法。

1.4 生命周期的监听

当添加到Activity中的空白Fragment在创建的时候,会创建一个ActivityFragmentLifecycle作为参数传入,在Activity生命周期发生变化的时候,里面的空白Fragment就会监听到Activity生命周期的变化,同时会执行相同的生命周期方法,此时会执行我们传入的ActivityFragmentLifecycle对象的与生命周期同名的方法,而这些方法在执行的时候,就会遍历一个存储了LifeCycleListener接口的集合,然后执行集合中每一个接口实现类的与生命周期同名的方法。
解释说明:

  • Activity
    • 举例:MainActivity
  • 空白Fragment
    • 举例:继承自androidx的SupportRequstManagerFragment
  • ActivityFragmentLifecycle
    • LifeCycle接口的实现类,这个接口还有另外一个实现类ApplicationLifycycle
    • 这个类中维护了一个LifeCycleListener接口的集合,通过addListener将接口的实现类添加到集合,通过removeListener将接口的实现类移出集合。
      LifeCycleListener接口的集合源码如下:
      private final Set<LifecycleListener> lifecycleListeners = Collections.newSetFromMap(new WeakHashMap<LifecycleListener, Boolean>());
  • LifeCycleListener
    • 该接口的典型的实现类有:RequestManager、ImageViewTarget、BitmapImageViewTarget等;
    • 通过LifeCycle实现类ActivityFragmentLifeCycle的addListener方法,将这些实现类添加到上面的接口集合中去,当Activity发生onStart、onStop和onDestroy生命周期变化的时候,就会执行这些实现类中的与生命周期同名的方法。
1.5生命周期的回调

LifeCycleListener只实现了3个方法,分别是onStart、onStop和onDestroy,当页面执行与以上几个方法同名的生命周期方法时,就会回调到注册进入LifeCycleListener接口集合的实现类的onStart、onStop和onDestroy方法。

这里只用onStart()生命周期方法作为入口举例,生命周期的回调具体流程如下:

  • Activity执行onStart生命周期方法
    • 举例:MainActivity.onStart()
  • ->空白Fragment执行onStart生命周期方法
    • 举例:SupportRequestManagerFragment.onStart()
  • ->传入空白Fragment构造函数中的ActivityFragmentLifeCycle执行onStart方法
    • 举例:ActivityFragmentLifeCycle.onStart()
  • ->ActivityFragmentLifeCycle类中,遍历LifeCycleListener集合,分别执行实现类的onStart方法
    • 举例:RequestManager.onStart()

2.load方法

创建出RequestBuilder对象,供into方法使用。
流程如下:

  • load(“https://img-blog.csdnimg.cn/5c2b98c8029f4ff5ac3e178d2c0a0940.png”) //加载网络图片url,不过这里并没有真正去加载
  • -> asDrawable() @RequestManager.java
  • -> as(Drawable.class) @RequestManager.java
  • -> new RequestBuilder<>(glide, this, resourceClass, context) @RequestManager.java

3.into方法

3.1 into方法的源码中流程走向

into的源码走向,这是一个极其复杂的流程,需要查阅Glide的into方法时序图,才能完整走通整个流程。

尝试走通into方法的整个流程:

  • into(ImageView view) @RequestBuilder
  • into(glideContext.buildImageViewTarget(view, transcodeClass), null, requestOptions, Executors.mainThreadExecutor()) @RequestBuilder
    该方法最终返回BitmapImageViewTarget对象
    • buildImageViewTarget()
      • 返回BitmapImageViewTarget或DrawableImageViewTarget对象,我们这是是返回BitmapImageViewTarget
    • Executors.mainThreadExecutor()
      • 创建了一个Executor(并且这个Executor中包含了一个主线程的handler),这个Executor在把EngineJob当做线程池执行一个DecodeJob任务时会用到,然后就会执行DecodeJob的run方法,用来从从磁盘缓存中取出图片文件
    • Request request = buildRequest(target, targetListener, options, callbackExecutor);
      • 构建Request接口的实现类SingleRequest
    • previous.begin();
      • previous是我们前面构建出的Request接口的实现类SingleRequest,所以这里其实是调用的SingleRequest.begin()
  • SingleRequest.begin() @SingleRequest
    • onResourceReady(resource, DataSource.MEMORY_CACHE);
      • 完成后调用该方法,将资源回调回去
    • onSizeReady(overrideWidth, overrideHeight);
      • 计算好图片控件的宽高后调用该方法,没有计算好则需要通过如下方法去计算,target.getSize(this),计算好继续回调onSizeRead方法
    • target.onLoadStarted(getPlaceholderDrawable());
      • 这里就会开始加载图片了,target是上面的BitmapImageViewTarget,但是是在其父类ImageViewTarget中完成的该方法处理,ImageViewTarget.onLoadStarted()
  • onSizeReady(overrideWidth, overrideHeight); @SingleRequest
    • 图片控件的宽高都计算好了
    • engine.load() @SingleRequest
  • load() @Engine
    • EngineKey key = keyFactory.buildKey()
      • 构建出EngineKey,这个对象中包含了图片的宽、高、签名等信息,通过这些信息可以作为找到存储图片的唯一标识
    • memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);//从缓存中加载图片资源
      • EngineResource<?> active = loadFromActiveResources(key);
        • 从活动缓存中加载图片资源
      • EngineResource<?> cached = loadFromCache(key);
        • 活动缓存中没有找到图片资源,则从内存缓存中加载图片资源
    • waitForExistingOrStartNewJob()
      • 活动缓存和内存缓存中都没有查找到图片资源,则需要通过线程池从磁盘缓存中去查询图片资源
  • waitForExistingOrStartNewJob() @Engine
    • EngineJob engineJob = engineJobFactory.build()
      • 查找有没有正在运行的任务,里面包含了线程池来执行任务
    • DecodeJob decodeJob = decodeJobFactory.build()
      • 这就是一个Runnable任务
    • engineJob.start(decodeJob);
      • (GlideExecutor)executor.execute(decodeJob);
        • //利用线程池来执行任务
  • run() @DecodeJob
    • 执行线程池中任务的run方法
  • runWrapped() @DecodeJob
    • currentGenerator = getNextGenerator();
      • 返回SourceGenerator对象,也就是说
        currentGenerator = SourceGenerator
  • runGenerators() @DecodeJob
  • currentGenerator.startNext()
    • 由于前面将currentGenerator赋值给了SourceGenerator,所以这里其实是调用的
      SourceGenerator.startNext()
  • startNext() @SourceGenerator
  • helper.getLoadData().get(loadDataListIndex++); @SourceGenerator
    • modelLoader.buildLoadData(model, width, height, options); @SourceGenerator
      • 在Glide构造函数中对modelLoader的对象进行了实现,所以这里是调用的HttpGlideUrlLoader.buildLoadData()
      • buildLoadData() @HttpClideUrlLoader
        • new HttpUrlFetcher(url, timeout)
          • 也就是说,前面我们的helper.getLoadData().get(loadDataListIndex++)得到的LoadData<Data>类中的DataFetcher<Data> fetcher参数,最终的实现类是HttpUrlFetcher
  • startNextLoad(loadData); @SourceGenerator
  • loadData.fetcher.loadData() @SourceGenerator
    • 这里的loadData.fetcher就是前面返回的HttpUrlFetcher对象所以最终执行的是HttpUrlFetcher.loadData()
  • loadData() @HttpUrlFetcher
    • InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders()); @@HttpUrlFetcher
      • 这里面就会通过HttpUrlConnection去访问网络,最终返回图片的InputStream流
    • callback.onDataReady(result);
      • 将返回的InputStream流回调回去,这里需要一层层回调上去,解析然后展示在控件上

通过HttpUrlConnection访问网络图片地址,拿到图片的InputStream后一层层往上回调流程如下:

  • callback.onDataReady(result); @HttpUrlFetcher
  • onDataReady(Object data) @ResourceCacheGenerator
    • cb.onDataFetcherReady(
      sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE, currentKey); @ResourceCacheGenerator
  • onDataFetcherReady() @SourceGenerator
    • cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey); @SourceGenerator
  • onDataFetcherReady() @DecodeJob
    • decodeFromRetrievedData(); @DecodeJob
    • decodeFromData(currentFetcher, currentData, currentDataSource); @DecodeJob
    • decodeFromFetcher(data, dataSource); @DecodeJob
    • runLoadPath(data, dataSource, path); @DecodeJob
    • path.load(rewinder, options, width, height, new DecodeCallback(dataSource)); @DecodeJob
  • loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables); @LoadPath
    • result = path.decode(rewinder, width, height, options, decodeCallback); @LoadPath
    • Resource decoded = decodeResource(rewinder, width, height, options);
      通过解码,最终:decoded = Resource<Bitmap>
      • decodeResourceWithList(rewinder, width, height, options, exceptions);
      • result = decoder.decode(data, width, height, options);
        • 这里会调用实现类StringBitmapDecode.decode方法
      • Resource decode(InputStream source, int width, int height, Options options) @StringBitmapDecode
        • 最终返回Bitmap,就将InputStream最终转换成Bitmap了,可以在控件中展示
    • Resource transformed = callback.onResourceDecoded(decoded); @LoadPath
      • 将上面解码出来的Bitmap回调出去,decoded = Resource<Bitmap>
  • Resource onResourceDecoded(Resource decoded) @DecodeJob
    • onResourceDecoded(dataSource, decoded); @DecodeJob
  • callback.onResourceDecode //解释一下,这里有点懵逼了
    • DecodeJob是一个Runnale任务,会执行run方法
  • runWrapped(); @DecodeJob
    • decodeFromRetrievedData();
    • notifyEncodeAndRelease(resource, currentDataSource);
    • notifyComplete(result, dataSource);
    • callback.onResourceReady(resource, dataSource);
  • notifyCallbacksOfResult(); @EngineJob
    • engineJobListener.onEngineJobComplete(this, localKey, localResource); @EngineJob
      将资源添加到活动缓存
      • onEngineJobComplete() @Engine
        • activeResources.activate(key, resource);//将资源添加到活动缓存
    • entry.executor.execute(new CallResourceReady(entry.cb));
      线程池执行任务CallResourceReady,会运行里面的run方法
      • callCallbackOnResourceReady(cb);
  • callCallbackOnResourceReady() @EngineJob
    • cb.onResourceReady(engineResource, dataSource);
  • onResourceReady(Resource<?> resource, DataSource dataSource) @SingleRequest
    • onResourceReady((Resource) resource, ® received, dataSource);
    • target.onResourceReady(result, animation);
      • 这里的target就是BitmapImageViewTarget对象,他的父类ImageViewTarget中包含了这个方法
  • onResourceReady() @ImageViewTarget
    • setResourceInternal(resource);
    • setResource(resource);
      • 由其子类BitmapImageViewTarget实现这个抽象方法
  • setResource(Bitmap resource) @BitmapImageViewTarget
    • 收工!收工!收工!

可以通过下面这张图了解into在整个过程中,做了哪些处理:
图片地址
这张图参考博客地址:
http://www.javashuo.com/article/p-vigwftpx-kg.html

3.2 Glide活动缓存的由来

活动缓存activeResources的存在,是因为如果App中内存缓存LruCacheResources中存储了过多的图片资源,会导致App占用的内存急剧增大,所以LruCacheResources中会利用LRU算法清理最近最不常用的图片资源,这个过程中可能会清理掉当前页面展示的图片的内存资源,从到导致App崩溃无法正常使用。所以,在内存缓存和App中间,会存在一个活动缓存ActiveResources,用来存放当前正在使用的图片的内存资源,这个活动缓存没有大小限制,随着页面的关闭而清空,清空之间将活动缓存中的图片资源添加到LruCacheResources内存缓存中去。

4.Glide缓存机制

4.1 资源封装
  • 当我们的页面需要展示一张图片时,首先会从活动缓存中去查询图片资源是否存在,存在则直接加载活动缓存中的图片资源;
  • 当活动缓存中不存在要加载的图片资源时,则从Lru内存缓存中去查询图片资源是否存在,存在则将Lru内存缓存中的图片资源移动到活动缓存,然后加载活动缓存中的图片资源;
  • 当Lru内存缓存中不存在要加载的图片资源时,则从磁盘缓存中去查询图片资源是否存在,存在则将磁盘缓存中的图片资源复制一份到活动缓存,然后加载活动缓存中的图片资源;
  • 当磁盘缓存中不存在要加载的图片资源时,则从网络去下载图片资源,然后保存到磁盘缓存,然后从磁盘缓存中复制一份到活动缓存,然后从活动缓存中加载图片资源。
  • 当页面销毁时,图片资源就会从活动缓存中移动到Lru内存缓存;当App被杀掉时,活动缓存和Lru内存缓存中的图片资源都将被清除,图片资源最终会存在磁盘缓存中。

知识拓展:
Lru内存缓存和活动缓存都属于内存缓存,App被杀死后,内存缓存都会被清除;磁盘缓存属于硬盘缓存,会通过Lru算法进行资源清理。
磁盘缓存和Lru内存缓存都采用了Lru算法,所以我们来了解一些其中的Lru算法机制。

LruCache是一个Lru算法的类,里面通过使用LinkedHashMap来实现Lru算法机制,LinkedHashMap使用案例如下:

/**
  * 测试Lru算法,最近最少使用原则
  **/
private static void testLruCache() {
    
    
  //int initialCapacity, float loadFactor, boolean accessOrder
  //参数1:集合初始容量	参数2:加载因子,达到容量的多少后会扩容	
  //参数3:是否进行使用后排序,使用过则拍在最后面,排在前面的是最近最少使用的
  LinkedHashMap linkedHashMap = new LinkedHashMap(0, 0.75f, true);
  //依次按照1,2,3的顺序添加到集合;最先添加的元素最先获取到,最后添加的元素最后获取到
  System.out.println("最先添加的元素最先获取到,最后添加的元素最后获取到");
  linkedHashMap.put(1, "1");
  linkedHashMap.put(2, "2");
  linkedHashMap.put(3, "3");
  System.out.println(linkedHashMap.toString());
  //最先添加的元素被使用一次,会放在集合中最后面,就会最后获取到该元素
  System.out.println("最先添加的元素被使用一次,就会最后获取到该元素");
  linkedHashMap.get(1);
  System.out.println(linkedHashMap.toString());
  System.out.println();
}

打印日志:
> Task :javalib:JavaTest.main()
最先添加的元素最先获取到,最后添加的元素最后获取到
{
    
    1=1, 2=2, 3=3}
最先添加的元素被使用一次,就会最后获取到该元素
{
    
    2=2, 3=3, 1=1}

所谓资源封装,就是要封装获取图片资源的key,还有图片资源bitmap本身;当我们需要从活动缓存、Lru内存缓存和磁盘缓存中获取缓存的图片资源时,就需要通过这个唯一的key去查找bitmap图片资源。

4.2 活动缓存

活动缓存是一个Map集合,key就是我们上面资源封装出来的那个key,value就是包含了bitmap的图片资源;需要包含添加和删除操作,根据key、value添加图片资源,根据key从活动缓存中移除图片资源;同时还需要包含一个接口回调函数,因为在空白Fragment监听到页面销毁的生命周期方法时,会将当前页面正在使用的图片资源从活动缓存中删除,然后将图片资源移动到Lru内存缓存中。

4.3 内存缓存

内存缓存只需要直接继承自LruCache,无需实现LruCache实现的容器LinkedHashMap,还有里面包含的put和get方法,但是需要重写sizeOf()方法,重写计算集合中每一个缓存资源bitmap的大小。

4.4 磁盘缓存

DiskLruCache是由Jake Warton开发的,存在与git上的项目。
这个类同样也是继承自LruCache类,只不过数据不保存在缓存,而是通过io流的形式,保存到磁盘。
注意,我们是通过图片的url作为key,并且当做缓存资源文件的名称,但是这个key必须要加密,否则就是带有图片url的那种斜杠和冒号之类的格式,在文件路径中这属于不合格的格式。所以需要转换将这个数据进行转换,然后将bitmap数据存储在磁盘缓存文件中;然后我们就可以根据图片的key获取到磁盘中缓存文件中图片的bitmap数据。

4.5 缓存加载流程

几种情况下,缓存资源的加载过程分析:

  • 情况1
    • 第一次加载图片,是通过网络去下载图片,保存到磁盘缓存中,然后从磁盘缓存中获取到图片资源,再保存一份到活动缓存,然后从活动缓存中获取到图片资源,展示到图片控件上;
    • 第二次加载图片时,直接从活动缓存中获取到图片资源,展示到图片控件上;
    • 第三次加载图片时,同样是从活动缓存中获取到图片资源,展示到图片控件上;
    • 第N次加载图片,都是从活动缓存中获取到图片资源,展示到图片控件上。
  • 情况2
    • 将当前Activity关闭,页面中的活动缓存资源会被释放,缓存的图片资源会被移动到内存缓存中去;
    • 然后再次打开当前Activity,会从内存缓存中获取到图片资源,然后将内存缓存中的图片资源移动到活动缓存中,然后从活动缓存中获取到图片资源,展示到图片控件上;
    • 下一次加载图片资源,会从活动缓存中获取到图片资源,展示到图片控件上;
    • 第二次加载图片时,直接从活动缓存中获取到图片资源,展示到图片控件上;
    • 第三次加载图片时,同样是从活动缓存中获取到图片资源,展示到图片控件上;
    • 第N次加载图片,都是从活动缓存中获取到图片资源,展示到图片控件上。
  • 情况3
    • 将App杀掉,整个活动缓存和内存缓存都被清除掉了,因为他们都是运行时缓存;
    • 首次启动App,打开当前页面,会从磁盘缓存中获取到图片资源,再保存一份到活动缓存,然后从活动缓存中获取到图片资源,展示到图片控件上;
    • 第二次加载图片时,直接从活动缓存中获取到图片资源,展示到图片控件上;
    • 第三次加载图片时,同样是从活动缓存中获取到图片资源,展示到图片控件上;
    • 第N次加载图片,都是从活动缓存中获取到图片资源,展示到图片控件上。

Glide整体流程

Glide整体流程图-简化版
在这里插入图片描述

面试题

1.在使用Glide的过程中,什么情况下会导致内存溢出,说说原因?

在子线程中使用Glide或者with方法传入的参数是Application的上下文时,都会存在内存溢出的风险。因为在这两种情况下,Glide的生命周期与App的生命周期相同,当我们在页面销毁的时候,有图片还没有加载完成,就会存在内存泄露的风险。因为没有创建空白的Fragment监听页面生命周期,对未完成的任务进行处理。

我们平时使用Glide的with方法时,传入的参数应该是非Application作用域的上下文,可以传入当前Activity的上下文。这种情况下,Glide会为我们创建一个空白的Fragment,他可以监听到Activity或者是Fragment的生命周期,然后在页面销毁的时候,会通过这个空白的Fragment,来完成资源的销毁工作,或者是对下载的图片不予处理等。

2.Glide源码中的缓存,为什么要有两个,一个活动缓存,一个内存缓存?

注意:
活动缓存也叫活动资源、活跃缓存、前台缓存;内存缓存也叫LRU缓存。

内存缓存(LRU缓存):
是用来存放我们打开App后,最近最常使用的图片资源的内存缓存集合。为了防止App加载的图片资源过多,导致占用的系统内存资源过大问题,采用了LRU算法,根据缓存最大值对最近最少使用到的资源进行清理。但是在清理资源的过程中,可能会清理到我们当前页面展示过的资源,此时会导致应用崩溃,所以引出了活动缓存。

活动缓存(活动资源、活跃资源、前台缓存):
是用来对正在使用的图片资源进行存储的内存缓存集合,这是一个没有采用过LRU算法的集合,所以不存在容量限制,不会对当前页面展示的资源进行清理。

当我们的页面要展示图片时,先从活动缓存中去查找是否有图片资源,有则直接使用,没有则会去查找内存缓存,当内存缓存中存在我们的图片资源时,则将他移动到活动缓存,并从内存缓存中移除,然后再从活动缓存中取出使用;当我们的页面销毁时,需要将图片资源从活动缓存中移动到内存缓存中,同时删除活动缓存中的图片资源。

猜你喜欢

转载自blog.csdn.net/tangkunTKTK/article/details/130925157