Glide的缓存机制原理分析

一、Glide请求图片的流程

当执行 Glide.with(this).load("").into(imageview)的into方法时候,该方法接收一个Target或者一个ImageView,Target是一个接口,默认的实现类包括SimpleTarget,ViewTarget,ImageViewTarget等,如果传递的是ImageView接下来回执行以下几步:

  1. 调用RequestBuilder的into方法,并且传递Imageview;
  2. 判断ImageView是否含有tag;
  3. 如果有tag则从tag中拿出来Request对象,调用begin方法;
  4. 如果没有tag则给setTag,tag的值为本次请求的request对象;
  5. 将请求加入到一个Map集合中;
  6. 调用该的request.begin()方法;
  7. 在begin方法中设置加载的生命周期,调用onSizeReady方法
  8. 执行engine.load方法得到加载的status状态。

into - RequestBuilder -  target/iamgeview - buildRequest - obtainRequest - SingleRequest - begin - onSizeReady - Engine

Glide的缓存机制原理分析_第1张图片

 

二、Engine请求图片的流程

 public  LoadStatus load(…………………………省略参数…………………………) {
    //开始加载的时间毫秒值
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
    //图片缓存的key,从这里可以看出影响key的参数非常多
    EngineKey key =
        keyFactory.buildKey(
            model,
            signature,
            width,
            height,
            transformations,
            resourceClass,
            transcodeClass,
            options);

    EngineResource memoryResource;
    synchronized (this) {
      //读取内存缓存和磁盘缓存
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

      if (memoryResource == null) {
        //没有缓存,则进行网络请求
        return waitForExistingOrStartNewJob(
            glideContext,
            model,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            options,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache,
            cb,
            callbackExecutor,
            key,
            startTime);
      }
    }
    //如果有缓存,则设置缓存
    cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
    return null;
  }

三、glide的缓存

Glide的缓存分为三级缓存:弱引用内存缓存,LurCatch内存缓存,磁盘缓存,从上面可以看出内存缓存的方法是loadFromMemory,磁盘缓存的方法是waitForExistingOrStartNewJob。

   private EngineResource loadFromMemory(EngineKey key, boolean isMemoryCacheable, long startTime) {
        if (!isMemoryCacheable) {
          return null;
        }
        //弱引用内存缓存
        EngineResource active = loadFromActiveResources(key);
        if (active != null) {
          if (VERBOSE_IS_LOGGABLE) {
            logWithTimeAndKey("Loaded resource from active resources", startTime, key);
          }
          return active;
        }
        //LurCatch内存缓存
        EngineResource cached = loadFromCache(key);
        if (cached != null) {
          if (VERBOSE_IS_LOGGABLE) {
            logWithTimeAndKey("Loaded resource from cache", startTime, key);
          }
          return cached;
        }

        return null;
   }

从上面的loadFromMemory方法中,可以发现首先读取的是弱引用内存缓存部分的内容,如果弱引用缓存中没有,然后读取的是LurCatch内存缓存,缓存的读取是根据key进行读取,key是一个接口重写了equals、hashCode、toString方法,key值和图片宽高,资源转化的class,以及其他配置信息相关,也就是只要图片中这些值的任意一个信息改变了,那么key就变了,缓存信息也就变了,虽然原始图片一样,但是展示的大图,小图,圆图是尺寸不一样,缓存的也不是一张图片了。在缓存key中内存缓存的是EngineKey,磁盘缓存的是DataCacheKey。

四、glide的内存缓存

Glide的内存缓存分为弱引用内存缓存和LruCatch内存缓存。

1、弱引用内存缓存

  @Nullable
  private EngineResource loadFromActiveResources(Key key) {
    EngineResource active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }
    return active;
  }

ActiveResources为glide的Map内存缓存管理类,提供了弱引用内存缓存的缓存,获取,清除等。如果读的内存缓存不为空,采用引用记数法acquired加1,当释放内存资源的时候acquired又减1。acquired的作用是的保证资源内存释放或者资源回收的正确性。下面通过get方法来看他是怎么获取的:

   @Nullable
   synchronized EngineResource get(Key key) {
      //第一步,通过key拿去Map集合弱引用数据
      ResourceWeakReference activeRef = activeEngineResources.get(key);
      if (activeRef == null) {
        return null;
      }
      //第二步,通过弱引用拿去资源数据
      EngineResource active = activeRef.get();
      if (active == null) {
        cleanupActiveReference(activeRef);
      }
      return active;
   }

可以看出弱引用内存内存缓存是通过两步走的:

第一步:通过key拿去Map集合弱引用数据,key为缓存的键,value为一个资源的弱引用,如果Map中没有,则返回空。

第二步:通过弱引用拿去资源数据,弱引用中没有,则从Map中移除该key,再返回该对象。

2、LruCatch内存缓存

  private EngineResource loadFromCache(Key key) {
    EngineResource cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }

Glide使用的LruCatch缓存不是原生的LruCatch,而是自定义的LruCatch,实现类为LruResourceCache,他管理了LruCatch内存缓存。如果LruCatch内存缓存资源存在则处理和上面的Map内存缓存一样,引用记数法acquired加1,在回收或者是放内存时候-1,同时将该资源放到内存缓存的Map集合中,以便于下次再从Map内存缓存中读取。下面看看具体如何读取LruCatch内存缓存:

  private EngineResource getEngineResourceFromCache(Key key) {、
    //移除之前的LruCatch缓存内存资源(为啥要移除没看明白)
    Resource cached = cache.remove(key);

    final EngineResource result;
    if (cached == null) {
      //LruCatch缓存为空,则表示没有缓存,走网络
      result = null;
    } else if (cached instanceof EngineResource) {
      //如果为EngineResource,则表示有内存缓存
      // Save an object allocation if we've cached an EngineResource (the typical case).
      result = (EngineResource) cached;
    } else {
      //其他情况,这重新创建EngineResource(自定义的EngineResource)
      result =
          new EngineResource<>(
              cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
  }

可以看出LruCatch内存缓存是通过两步走的,流程图如下:

第一步:获取到资源,移除之前的LruCatch缓存的资源。

第二步:如果资源不为空,则返回。

 

Glide的缓存机制原理分析_第2张图片

 

五、glide的磁盘缓存引入

磁盘缓存的默认目录:

String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";

默认大小:

int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;

配置选项:

  • DiskCacheStrategy.NONE// 表示不缓存任何内容
  • DiskCacheStrategy.DATA// 表示只缓存原始图片
  • DiskCacheStrategy.RESOURCE// 表示只缓存转换过后的图片
  • DiskCacheStrategy.ALL // 表示既缓存原始图片,也缓存转换过后的图片

当内存的两级缓存没拿到数据时候,会执行waitForExistingOrStartNewJob方法:

  private  LoadStatus waitForExistingOrStartNewJob(*******省略参数********) {
	//从EngineJob缓存Map集合中获取job任务
	//onlyRetrieveFromCache = true表示只从磁盘中获取,=false表示可以从网络中获取对应两个Map集合,根据key获取value
	EngineJob current = jobs.get(key, onlyRetrieveFromCache);
	if (current != null) {
	  //开启回调线程池,设置回调
	  current.addCallback(cb, callbackExecutor);
	  return new LoadStatus(cb, current);
	}
	//EngineJob:负责请求回调
	EngineJob engineJob =
		engineJobFactory.build(
			key,
			isMemoryCacheable,
			useUnlimitedSourceExecutorPool,
			useAnimationPool,
			onlyRetrieveFromCache);

	//DecodeJob:负责从缓存数据或原始源解码资源的类以及应用转换和转码
	DecodeJob decodeJob =
		decodeJobFactory.build(
			glideContext,
			model,
			key,
			signature,
			width,
			height,
			resourceClass,
			transcodeClass,
			priority,
			diskCacheStrategy,
			transformations,
			isTransformationRequired,
			isScaleOnlyOrNoTransform,
			onlyRetrieveFromCache,
			options,
			engineJob);

	//将请求任务放到Map里面
	jobs.put(key, engineJob);
	//开启缓存线程池
	engineJob.addCallback(cb, callbackExecutor);
	//开启网络请求线程池
	engineJob.start(decodeJob);

	return new LoadStatus(cb, engineJob);
  }

磁盘缓存有两个关键类EngineJob和DecodeJob,其中EngineJob负责回调部分,DecodeJob负责磁盘读取和网络请求的编码和解码部分。二者的创建是采用工厂设计模式从缓存池里面读取的,缓存池pool的最大容量为150,数据结构为数组。步骤如下:

1、从Map>缓存Map集合中根据key获取job任务,如果不为空则开启回调线程线程池设置回调,资源不为空则执行onResourceReady方法。

2、如果EngineJob缓存为空,创建DecodeJob和EngineJob,将EngineJob添加到缓存集合中,一并交给DecodeJob,开启资源加载线程池,进行磁盘缓存加载或者网络请求。流程如下:

engineJob.start(decodeJob) -> DecodeJob.run() -> runWrapped() -> startNext()

六、glide的磁盘缓存过程

首次进来runReason默认为INITIALIZE,调用runWrapped如下:

	private void runWrapped() {
		switch (runReason) {
		  //首次进来执行,默认数据执行完毕之后,status = RESOURCE_CACHE; currentGenerator = ResourceCacheGenerator
		  case INITIALIZE:
			stage = getNextStage(Stage.INITIALIZE);
			currentGenerator = getNextGenerator();
			runGenerators();
			break;

		  case SWITCH_TO_SOURCE_SERVICE:
			runGenerators();
			break;

		  case DECODE_DATA:
			decodeFromRetrievedData();
			break;
		  default:
			throw new IllegalStateException("Unrecognized run reason: " + runReason);
		}
	}

currentGenerator.startNext()这里不在详细深入了,主要是加载数据,对应的接口是DataFetcherGenerator三个实现类分别是:

  • ResourceCacheGenerator:从缓存的显示数据中通过startNext获取
  • DataCacheGenerator:从缓存的原始数据中通过startNext获取
  • SourceGenerator:从网络数据中通过startNext获取

runGenerators是一个while循环,默认首次进执行ResourceCacheGenerator的startNext方法,如果获取失败,通过getNextStage改变status的值为DATA_CACHE,同时currentGenerator = DataCacheGenerator,在执行DataCacheGenerator的startNext,如果再返回失败status的值为SOURCE,currentGenerator = SourceGenerator执行到这里满足条件退出循环。

    private void runGenerators() {
        boolean isStarted = false;
        while (!isCancelled
            && currentGenerator != null
            && !(isStarted = currentGenerator.startNext())) {
          stage = getNextStage(stage);
          currentGenerator = getNextGenerator();

          if (stage == Stage.SOURCE) {
            reschedule();
            return;
          }
        }
    }
   @Override
   public void reschedule() {
     runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
     callback.reschedule(this);
   }

退出循环之前这时候执行reschedule方法,设置任务的RunReason是SWITCH_TO_SOURCE_SERVICE,触发回调Callback的reschedule方法,EngindJob实现此回调接口, 再次调用向线程池派发该DecodeJob任务:

   @Override
   public void reschedule(DecodeJob job) {
     getActiveSourceExecutor().execute(job);
   }

即再次执行runWrapped方法,这时候currentGenerator = SourceGenerator,runReason = SWITCH_TO_SOURCE_SERVICE,会在此执行SourceGenerator的startNext方法,如下:

    @Override
    public boolean startNext() {
        //第二次执行时候进行缓存
        if (dataToCache != null) {
            Object data = dataToCache;
            dataToCache = null;
            cacheData(data);
        }
        //缓存成功后,return不执行网络请求了。
        if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
            return true;
        }
        //网络请求
        sourceCacheGenerator = null;
        loadData = null;
        boolean started = false;
        while (!started && hasNextModelLoader()) {
            loadData = helper.getLoadData().get(loadDataListIndex++);
            if (loadData != null
                    && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
                    || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
                started = true;
                //发起请求
                loadData.fetcher.loadData(helper.getPriority(), this);
            }
        }
        return started;
    }

第一次网络请求时候,在loadData中具体的请求是由XxxFetcher(HttpUrlFetcher等)中的loadData方法进行请求,在4.10.0版本时使用的HttpURLConnection进行获取图片资源Stream的,当获取成功时候会调用callback.onDataReady(result);callback为SourceGenerator中的监听接口,将结果返回给SourceGenerator方法中的onDataReady,同时数据保存在SourceGenerator内部 dataToCache变量中,接着第二次触发reschedule()方法:

  @Override
  public void onDataReady(Object data) {
	DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
	if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
	 //数据保存在SourceGenerator内部
	  dataToCache = data;
	  //再次触发DecodeJob中的reschedule
	  cb.reschedule();
	} else {
	  cb.onDataFetcherReady(
		  loadData.sourceKey,
		  data,
		  loadData.fetcher,
		  loadData.fetcher.getDataSource(),
		  originalKey);
	}
  }

次执行runWrapped方法,和上面的一样,会在此执行SourceGenerator的startNext方法,这时候dataToCache已经有数值了,这时候才会调用cacheData(data);方法进行缓存,这块才是Glide设置缓存的地方。

    private void cacheData(Object dataToCache) {
        long startTime = LogTime.getLogTime();
        try {
          //创建缓存内容
          Encoder encoder = helper.getSourceEncoder(dataToCache);
          DataCacheWriter writer =
              new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
          //获取key
          originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
          //设置缓存
          helper.getDiskCache().put(originalKey, writer);
        } finally {
          //清除发起之前网络请求数据
          loadData.fetcher.cleanup();
       }
       sourceCacheGenerator = new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
    } 
  

最后会创建DataCacheGenerator对象,cacheData方法执行完毕,接着执行SourceGenerator的startNext后面的内容:

   if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
       return true;
   }

sourceCacheGenerator.startNext()这个判断会进入sourceCacheGenerator的startNext()方法,这时候将从Disk缓存中获取一次,并且设置LruCatch缓存,成功返回。DataCacheGenerator的startNext方法。最终也会走到sourceCacheGenerator的loadData.fetcher.loadData(helper.getPriority(), this);这里的loadData方法是ByteBufferFetcher的loadData,ByteBufferFetcher是一个专门读取缓存的类,通过ByteBufferUtil.fromFile(file)拿到缓存,调用callback.onDataReady(result)设置回调,callback为DataCacheGenerator。

    @Override
    public void loadData(
        @NonNull Priority priority, @NonNull DataCallback callback) {
      ByteBuffer result;
      try {
        //读取缓存
        result = ByteBufferUtil.fromFile(file);
      } catch (IOException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Failed to obtain ByteBuffer for file", e);
        }
        callback.onLoadFailed(e);
        return;
      }
      //设置回调,callback为DataCacheGenerator
      callback.onDataReady(result);
    }

    @Override
    public void onDataReady(Object data) {
        cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
    }

最后将结果回传给DecodeJob的onDataFetcherReady,然后调用decodeFromRetrievedData();方法将结果回传。

  @Override
  public void onDataFetcherReady(
	  Key sourceKey, Object data, DataFetcher fetcher, DataSource dataSource, Key attemptedKey) {
	this.currentSourceKey = sourceKey;
	this.currentData = data;
	this.currentFetcher = fetcher;
	this.currentDataSource = dataSource;
	this.currentAttemptingKey = attemptedKey;
	//当前线程是否和runGenerators方法时的线程相等
	if (Thread.currentThread() != currentThread) {
	  runReason = RunReason.DECODE_DATA;
	  callback.reschedule(this);
	} else {
	  GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
	  try {
		decodeFromRetrievedData();
	  } finally {
		GlideTrace.endSection();
	  }
	}
  }

DecodeJob -> onDataFetcherReady -> decodeFromRetrievedData() ->  notifyEncodeAndRelease()->  notifyComplete(result, dataSource) ->  callback.onResourceReady(resource, dataSource)->  engineJob.onResourceReady(resource, dataSource)  -> notifyCallbacksOfResult()

  void notifyCallbacksOfResult() {
	ResourceCallbacksAndExecutors copy;
	Key localKey;
	EngineResource localResource;
	synchronized (this) {
	  stateVerifier.throwIfRecycled();
	  if (isCancelled) {
		//取消请求则回收资源
		resource.recycle();
		release();
		return;
	  } else if (cbs.isEmpty()) {
		throw new IllegalStateException("Received a resource without any callbacks to notify");
	  } else if (hasResource) {
		throw new IllegalStateException("Already have resource");
	  }
	  //创建engineResource
	  engineResource = engineResourceFactory.build(resource, isCacheable, key, resourceListener);
	  hasResource = true;
	  copy = cbs.copy();
	  incrementPendingCallbacks(copy.size() + 1);
	  localKey = key;
	  localResource = engineResource;
	}
	//设置弱引用内存缓存
	engineJobListener.onEngineJobComplete(this, localKey, localResource);

	for (final ResourceCallbackAndExecutor entry : copy) {
	  //执行资源回调线程,执行run方法返回回调
	  entry.executor.execute(new CallResourceReady(entry.cb));
	}
	decrementPendingCallbacks();
  }

notifyCallbacksOfResult收到回调的数据,会执行三步:

1、如果发现取消了请求则立即释放资源

2、回调Engine,设置弱引用内存缓存

     @Override
     public synchronized void onEngineJobComplete(
         EngineJob engineJob, Key key, EngineResource resource) {
       if (resource != null && resource.isMemoryCacheable()) {
         //弱引用缓存
         activeResources.activate(key, resource);
       }
       jobs.removeIfCurrent(key, engineJob);
     }

     synchronized void activate(Key key, EngineResource resource) {
       ResourceWeakReference toPut =
           new ResourceWeakReference(
               key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
       //设置缓存数据
       ResourceWeakReference removed = activeEngineResources.put(key, toPut);
       if (removed != null) {
         removed.reset();
       }
     }

3、执行资源回调线程,执行run方法返回回调。

EngineJob : CallResourceReady.run -> callCallbackOnResourceReady(cb) ->  cb.onResourceReady(engineResource, dataSource) ->

SingleRequest : onResourceReady -> onResourceReady  ->

target: onResourceReady(result, animation)

这样又回到起点所说的target上面去了,调用onResourceReady设置图片资源。至此磁盘缓存结束。简化流程图如下:

Glide的缓存机制原理分析_第3张图片

 

七、Bitmap缓存池

在内存缓存中Bitmap是内存消耗的主要元凶,Glide是通过Bitmap的缓存池进行缓存, BitmapPool缓存Bitmap 对象,避免重复创建Bitmap避免资源的过渡消耗。

1、BitmapPool

    public interface BitmapPool {

        long getMaxSize();

        void setSizeMultiplier(float var1);

        void put(Bitmap var1);

        @NonNull
        Bitmap get(int var1, int var2, Config var3);

        @NonNull
        Bitmap getDirty(int var1, int var2, Config var3);

        void clearMemory();

        void trimMemory(int var1);
    }

他是一个接口,定义了缓存的读取、清理,设置,BitmapPool的实现类,在 Glide 中有两个 BitmapPool 的实现类:BitmapPoolAdapter 和 LruBitmapPool。 BitmapPoolAdapter 类是一个空实现类,这里详细分析一下 LruBitmapPool,在分析之前先来了解下LruPoolStrategy、Key 和 KeyPool。

(1)LruPoolStrategy

LruPoolStrategy定义了LruBitmapPool缓存策略接口,包括一些存放、获取、移除Bitmap的方法。在 Glide内部 LruPoolStrategy接口有三个实现类,分别是 AttributeStrategy、SizeConfigStrategy 和 SizeStrategy 类,这三个类是通过不同维度的条件缓存 Bitmap。

  • AttributeStrategy:API 19之前,获取缓存的时候,必须要求图片的size和config必须严格匹配才算是缓存命中成功。
  • SizeConfigStrategy :API 19之后,不严格要求缓存中的获取的图片大小一定要和获取缓存时指定的大小一致。

(2)Key和KeyPool

BitmapPool 和 LruPoolStrategy 都只是接口定义,其底层的实现类其实都是使用了Map 数据结构,Map 是以键值对的形式在K和V之间形成一一映射的,我们的 V 就是 Bitmap 对象了。在 Glide 中具体的 K 是 Key 这样的一个类,而为了避免每次存放、获取 Bitmap对象而新建Key对象,引入了 KeyPool 的概念。因为不同的缓存策略对应不同的Key,不同的Key自然要对应不同的 KeyPool 了,有三种缓存策略,所以也有三种 Key 和 KeyPool 了。

2、缓存原理

LruBitmapPool是BitmapPool的实现类,可以通过Glide的配置项配置builder.setBitmapPool。LruBitmapPool没有做太多的东西主要任务都交给了 LruPoolStrategy缓存策略接口,具体的实现类有 AttributeStrategy、SizeConfigStrategy 和 SizeStrategy,这三个类是通过不同的条件来缓存 Bitmap 的,底层具体的实现都使用了GroupedLinkedMap,这是Glide为了实现LRU算法自定义的一个数据结构,其中包含三种数据结构:哈希表(HashMap)、循环链表以及列表(ArrayList)。这个结构其实类似 Java 里提供的 LinkedHashMap 类。如下图:

Glide的缓存机制原理分析_第4张图片

    class GroupedLinkedMap {
        private final GroupedLinkedMap.LinkedEntry head = new GroupedLinkedMap.LinkedEntry();
        private final Map> keyToEntry = new HashMap();

        GroupedLinkedMap() {
        }

        //设置
        public void put(K key, V value) {
            GroupedLinkedMap.LinkedEntry entry = (GroupedLinkedMap.LinkedEntry)this.keyToEntry.get(key);
            if (entry == null) {
                entry = new GroupedLinkedMap.LinkedEntry(key);
                this.makeTail(entry);
                this.keyToEntry.put(key, entry);
            } else {
                key.offer();
            }
            entry.add(value);
        }

        //读取
        @Nullable
        public V get(K key) {
            GroupedLinkedMap.LinkedEntry entry = (GroupedLinkedMap.LinkedEntry)this.keyToEntry.get(key);
            if (entry == null) {
                entry = new GroupedLinkedMap.LinkedEntry(key);
                this.keyToEntry.put(key, entry);
            } else {
                key.offer();
            }
            this.makeHead(entry);
            return entry.removeLast();
        }
    }

BitmapPool 大小通过 MemorySizeCalculator 设置,使用LRU算法维护BitmapPool,会根据 Bitmap 的大小与 Config 生成一个 Key,Key 也有自己对应的对象池,数据最终存储在GroupedLinkedMap 中,GroupedLinkedMap使用哈希表、循环链表、List 来存储数据。

八、总结

1、先读取内存缓存,内存缓存分为两步第一步根据key读取Map缓存的弱引用缓存,第二步弱引用读取LruCatch缓存。内促资源存在则返回,否则执行磁盘缓存。

2、再去取磁盘缓存,如果没有,则请求网络,结束后依次设置磁盘缓存,内存LruCatch缓存,内存弱引用缓存,再回调给ImageView。

 

 

你可能感兴趣的:(android)