预备知识

Glide 基本概念

Model:数据来源,url、本地文件等 Data:原始数据inputStream,ByteBuffer等 Resource:原始数据解码后的资源 TransfromedResource:转换后的resource TranscodeResource:转码后的resouce Target:显示的目标的封装

时序图

流程分析

Glide.with

with(Context) 为例,其他方法类似。

public static RequestManager with(Context context) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(context);
}

此方法返回了一个 RequestManagerRequestManager 是负责管理和开启请求的类。

RequestManagerRetriever 是负责创建和缓存 RequestManager 的, 是单例模式的。

Glide.with 传入的不同的类的实例 ,对应着不同的 RequestManager每个实例对应一个 RequestManager 实例

Glide.with(Application)

private RequestManager getApplicationManager(Context context) {
    // Either an application context or we're on a background thread.
    if (applicationManager == null) {
        synchronized (this) {
            if (applicationManager == null) {
                // Normally pause/resume is taken care of by the fragment we add to the fragment or activity.
                // However, in this case since the manager attached to the application will not receive lifecycle
                // events, we must force the manager to start resumed using ApplicationLifecycle.
                applicationManager = new RequestManager(context.getApplicationContext(),
                        new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
            }
        }
    }

    return applicationManager;
}

ApplicationLifecycle 只做了一件事,在添加监听时调用监听的 onStart() 方法

Glide.with(Activity) 与 Glide.with(android.app.Fragment)

最终都会执行下边的代码

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
    RequestManagerFragment current = getRequestManagerFragment(fm);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
        requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
        current.setRequestManager(requestManager);
    }
    return requestManager;
}

创建了一个不可见的 android.app.Fragment 实例 RequestManagerFragment 来管理生命周期。

RequestManagerFragment 内有一个名为 lifecycle 的成员变量,它是一个 ActivityFragmentLifecycle 类型。

ActivityFragmentLifecycle 负责在 Fragment 执行不同的声明周期方法时,通知它的监听者 LifecycleListener。其中 RequestManager 就是 LifecycleListener 的实现类之一。那么它是在什么时候设置的监听呢?

我们看上边的代码,在创建 RequestManager 时,会传入 current.getLifecycle(),我们进入 RequestManager 的构造方法,其中就有设置监听的代码lifecycle.addListener(this),这样 RequestManager 就能够监听 Fragment 不同的声明周期方法了,然后在不同的声明周期方法中执行相应的操作。

/**
 * Lifecycle callback that registers for connectivity events (if the android.permission.ACCESS_NETWORK_STATE
 * permission is present) and restarts failed or paused requests.
	 */
@Override
public void onStart() {
    // onStart might not be called because this object may be created after the fragment/activity's onStart method.
    resumeRequests();
}

/**
 * Lifecycle callback that unregisters for connectivity events (if the android.permission.ACCESS_NETWORK_STATE
 * permission is present) and pauses in progress loads.
 */
@Override
public void onStop() {
    pauseRequests();
}

/**
 * Lifecycle callback that cancels all in progress requests and clears and recycles resources for all completed
 * requests.
 */
@Override
public void onDestroy() {
    requestTracker.clearRequests();
}

onStart 时唤醒请求,在 onStop 时暂停请求,在 onDestroy 时清除所有关联请求。

我们发现这些操作都是通过 RequestTracker 完成的。

RequestTracker 是实际对请求进行管理的类。

Glide.with(FragmentActivity)

创建了一个不可见的 android.support.v4.app.Fragment 实例 SupportRequestManagerFragment 来管理生命周期。

SupportRequestManagerFragment 的实现逻辑与上边的 RequestManagerFragment 相同。

传入 android.support.v4.app.Fragment

与传入 FragmentActivity 逻辑相同。

注:以上非 Application 的情况,都需要检查是否在主线程,如果非主线程,则最终都会使用 Application 创建。

if (Util.isOnBackgroundThread()) {
  return get(getApplicationContext());
}

那么如何判断是否在主线程的呢?

Looper.myLooper() == Looper.getMainLooper()

RequestManager#load

RequestManager 的 load 方法有几个不同的重载方法,分别是:

load(File) 从文件加载

load(Integer) 从资源文件中加载

load(String)

load(T)

load(Uri)

load(URL)

load(byte[])

load(byte[] , String)

以上方法最终都会调用 loadGeneric 方法,返回一个 DrawableTypeRequest 对象,只是对应的泛型参数不同。

private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
    ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
    ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
            Glide.buildFileDescriptorModelLoader(modelClass, context);
    if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
        throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"
                + " which there is a registered ModelLoader, if you are using a custom model, you must first call"
                + " Glide#register with a ModelLoaderFactory for your custom model class");
    }

    return optionsApplier.apply(
            new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
                    glide, requestTracker, lifecycle, optionsApplier));
}

ModelLoader,用于把 Model 转换成 Data

方法最后返回一个 DrawableTypeRequest 对象。

DrawableTypeRequest

DrawableTypeRequest 用于创建加载 GIFBitmap 的请求的类,还有添加ResourceTranscoder 来将数据转码为非 Drawable 资源类型的作用。

继承关系

GenericRequestBuilderDrawableRequestBuilderDrawableTypeRequest

最终我们会调用 into() 方法开始图片的加载,它有几个重载的方法,我们先分析下 into(ImageView) 方法

public Target<TranscodeType> into(ImageView view) {
		// 1
    Util.assertMainThread();
    if (view == null) {
        throw new IllegalArgumentException("You must pass in a non null View");
    }
		// 2
    if (!isTransformationSet && view.getScaleType() != null) {
        switch (view.getScaleType()) {
            case CENTER_CROP:
                applyCenterCrop();
                break;
            case FIT_CENTER:
            case FIT_START:
            case FIT_END:
                applyFitCenter();
                break;
            //$CASES-OMITTED$
            default:
                // Do nothing.
        }
    }
		// 3
    return into(glide.buildImageViewTarget(view, transcodeClass));
}

1、需要在主线程中执行该方法,否则抛出异常

2、未通过 transform() 方法设置 Transformation,且 getScaleType 不为null,则设置

scaleType。可以看出 transform() 优先级高于 ImageViewscaleType

applyCenterCrop() 方法实现在 DrawableRequestBuilder 中,

@Override
void applyFitCenter() {
    fitCenter();
}

public DrawableRequestBuilder<ModelType> fitCenter() {
    return transform(glide.getDrawableFitCenter());
}

@Override
void applyCenterCrop() {
    centerCrop();
}

public DrawableRequestBuilder<ModelType> centerCrop() {
    return transform(glide.getDrawableCenterCrop());
}

我们看到这两个特性的实现,也是通过设置 transform 实现的。从这可以看出,这几个方法不能出现在一次链式调用中,哪个方法最后被调用哪个方法才有效。

我们继续追踪,看看 centerCrop 和 fitCenter 的 Transformation 分别是怎么实现的。

Glide(...) {
		...
		bitmapCenterCrop = new CenterCrop(bitmapPool);
    drawableCenterCrop = new GifBitmapWrapperTransformation(bitmapPool, bitmapCenterCrop);

    bitmapFitCenter = new FitCenter(bitmapPool);
    drawableFitCenter = new GifBitmapWrapperTransformation(bitmapPool, bitmapFitCenter);
}

GifBitmapWrapperTransformation getDrawableCenterCrop() {
    return drawableCenterCrop;
}

GifBitmapWrapperTransformation getDrawableFitCenter() {
    return drawableFitCenter;
}

可以看到返回的都是 GifBitmapWrapperTransformation。它是Transformation 的包装类,具体功能还是由被包装者实现的。也就分别是 CenterCropFitCenter 这两个被包装的类。这两个类的实现也非常简单,就是分别调用了 TransformationUtilscenterCropfitCenter 方法完成的转换,具体实现就不贴出来了,可以看下源码。

3、根据 transcodeClassImageView 封装成对应的 Target 对象,

<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}

public class ImageViewTargetFactory {

    @SuppressWarnings("unchecked")
    public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
        if (GlideDrawable.class.isAssignableFrom(clazz)) {
            return (Target<Z>) new GlideDrawableImageViewTarget(view);
        } else if (Bitmap.class.equals(clazz)) {
            return (Target<Z>) new BitmapImageViewTarget(view);
        } else if (Drawable.class.isAssignableFrom(clazz)) {
            return (Target<Z>) new DrawableImageViewTarget(view);
        } else {
            throw new IllegalArgumentException("Unhandled class: " + clazz
                    + ", try .as*(Class).transcode(ResourceTranscoder)");
        }
    }
}

Class.isAssignableFrom(cls) 判定此 Class 对象所表示的类或接口与指定的参数cls所表示的类或接口是否相同,或是否是其超类或(超)接口

返回哪个 Target 是由 transcodedClass 决定的。这就要看调用 into 方法的具体对象了。

如果这么调用:Glide.with().load().into()

则实际调用 into() 的是 DrawableTypeRequesttranscodeClass是在构造函数中赋值的。

DrawableRequestBuilder(Context context, Class<ModelType> modelClass,
        LoadProvider<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable> loadProvider, Glide glide,
        RequestTracker requestTracker, Lifecycle lifecycle) {
    super(context, modelClass, loadProvider, GlideDrawable.class, glide, requestTracker, lifecycle);
    // Default to animating.
    crossFade();
}

DrawableTypeRequesttranscodedClassGlideDrawable.class ,那么 Target 就是 GlideDrawableImageViewTarget

如果是 :Glide.with().load().asBitmap().into()

则 Request 类为 BitmapTypeRequesttranscodedClassBitmap.class

TargetBitmapImageViewTarget

如果是 :Glide.with().load().asGif().into()

Request 类为 GifTypeRequesttranscodedClassGifDrawable.class

GifDrawable 继承自 GlideDrawableTargetGlideDrawableImageViewTarget

创建完 Target 对象后,最终传入 into(Y target) 方法中。

public <Y extends Target<TranscodeType>> Y into(Y target) {
    Util.assertMainThread();
    if (target == null) {
        throw new IllegalArgumentException("You must pass in a non null Target");
    }
    if (!isModelSet) {
        throw new IllegalArgumentException("You must first set a model (try #load())");
    }
		// 1
    Request previous = target.getRequest();

    if (previous != null) {
        previous.clear();
        requestTracker.removeRequest(previous);
        previous.recycle();
    }
		// 2
    Request request = buildRequest(target);
    target.setRequest(request);
    lifecycle.addListener(target);
    requestTracker.runRequest(request);

    return target;
}

1、获取此 target 之前的发起的请求

那么问题来了,每次 into 都会创建一个新的 target,那么Request 是如何保存的呢?

如果是 View 对应的 target(ViewTarget),则会通过 View 的 setTag() 方法来保存。

ViewTarget 源码:

/**
 * Stores the request using {@link View#setTag(Object)}.
 *
 * @param request {@inheritDoc}
 */
@Override
public void setRequest(Request request) {
    setTag(request);
}

/**
 * Returns any stored request using {@link android.view.View#getTag()}.
 *
 * <p>
 *     For Glide to function correctly, Glide must be the only thing that calls {@link View#setTag(Object)}. If the
 *     tag is cleared or set to another object type, Glide will not be able to retrieve and cancel previous loads
 *     which will not only prevent Glide from reusing resource, but will also result in incorrect images being
 *     loaded and lots of flashing of images in lists. As a result, this will throw an
 *     {@link java.lang.IllegalArgumentException} if {@link android.view.View#getTag()}} returns a non null object
 *     that is not an {@link com.bumptech.glide.request.Request}.
 * </p>
 */
@Override
public Request getRequest() {
    Object tag = getTag();
    Request request = null;
    if (tag != null) {
        if (tag instanceof Request) {
            request = (Request) tag;
        } else {
            throw new IllegalArgumentException("You must not call setTag() on a view Glide is targeting");
        }
    }
    return request;
}

private void setTag(Object tag) {
    if (tagId == null) {
        isTagUsedAtLeastOnce = true;
        view.setTag(tag);
    } else {
        view.setTag(tagId, tag);
    }
}

private Object getTag() {
    if (tagId == null) {
        return view.getTag();
    } else {
        return view.getTag(tagId);
    }
}


/**
 * Sets the android resource id to use in conjunction with {@link View#setTag(int, Object)}
 * to store temporary state allowing loads to be automatically cancelled and resources re-used
 * in scrolling lists.
 *
 * <p>
 *   If no tag id is set, Glide will use {@link View#setTag(Object)}.
 * </p>
 *
 * <p>
 *   Warning: prior to Android 4.0 tags were stored in a static map. Using this method prior
 *   to Android 4.0 may cause memory leaks and isn't recommended. If you do use this method
 *   on older versions, be sure to call {@link com.bumptech.glide.Glide#clear(View)} on any view
 *   you start a load into to ensure that the static state is removed.
 * </p>
 *
 * @param tagId The android resource to use.
 */
public static void setTagId(int tagId) {
    if (ViewTarget.tagId != null || isTagUsedAtLeastOnce) {
        throw new IllegalArgumentException("You cannot set the tag id more than once or change"
            + " the tag id after the first request has been made");
    }
    ViewTarget.tagId = tagId;
}

所以就解释了为什么默认情况下,加载图片的 View 不能在其它地方调用 setTag(Object) 方法,因为会转型错误。

从代码里可以看出,我们还是有方法解决这个问题的,ViewTarget 为我们提供了一个静态方法 setTagId ,这样我们就可以指定保存 Request 时的 tagId 了,从而我们可以在其他地方使用 setTag(Object) 方法了。

如果我们直接调用 into(Y target) 方法,每次都传入一个新的 target,有必要的话,我们就需要自己来取消之前的请求了。

2、创建请求,发起请求。

private Request buildRequest(Target<TranscodeType> target) {
    if (priority == null) {
        priority = Priority.NORMAL;
    }
    return buildRequestRecursive(target, null);
}

private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
    if (thumbnailRequestBuilder != null) {
				ThumbnailRequestCoordinator coordinator = 
        ...
				创建 coordinator 过程
				...
        return coordinator;
    } else if (thumbSizeMultiplier != null) {
        ThumbnailRequestCoordinator coordinator = 
        ...
				创建 coordinator 过程
				...
        return coordinator;
        return coordinator;
    } else {
        // Base case: no thumbnail.
        return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);
    }
}

buildRequestRecursive 方法决定要不要创建缩略图的请求。缩略图通过 thumbnail 方法设置,它有两个重载方法,

  • thumbnail(float)传入一个 0 到 1 的浮点数,根据此值创建请求。
  • thumbnail(GenericRequestBuilder)。传入缩略图请求构建器

ThumbnailRequestCoordinator 包含两个请求,一个是原图请求,一个是缩略图请求。

我们只分析下没有缩略图的情况,即 obtainRequest

此方法使用 GenericRequest.obtain 方法返回了一个 GenericRequest

GenericRequest

发起请求的方法为 begin()

@Override
public void begin() {
    startTime = LogTime.getLogTime();
    if (model == null) {
        onException(null);
        return;
    }

    status = Status.WAITING_FOR_SIZE;
		// 1
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
    } else {
        target.getSize(this);
    }

    if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
    }
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }
}

1、如果overrideWidthoverrideHeight (这两个值就是GenericRequestBuilder#override()方法传入的值)大于0,或者等于 Target#SIZE_ORIGINAL(值为 Integer.MIN_VALUE,表示原始尺寸),则调用 onSizeReady()

如果 overrideWidthoverrideHeight 是无效值,则调用 target.getSize(this) 方法传入当前对象作为监听器, target 获取到尺寸后,就会触发 onSizeReady() 方法。

那么为什么不能同步获取尺寸,而是异步的呢?因为此时尺寸可能还未确定,比如一个ImageView,它有可能还没有测量完,那么就需要测量完,尺寸确定后才能发起请求。那么这个回调又是在什么时机触发的?

ViewTarget#getSize

@Override
public void getSize(SizeReadyCallback cb) {
    sizeDeterminer.getSize(cb);
}

private static class SizeDeterminer {
		...
		public void getSize(SizeReadyCallback cb) {
        int currentWidth = getViewWidthOrParam();
        int currentHeight = getViewHeightOrParam();
        if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
            cb.onSizeReady(currentWidth, currentHeight);
        } else {
            // We want to notify callbacks in the order they were added and we only expect one or two callbacks to
            // be added a time, so a List is a reasonable choice.
            if (!cbs.contains(cb)) {
                cbs.add(cb);
            }
            if (layoutListener == null) {
                final ViewTreeObserver observer = view.getViewTreeObserver();
                layoutListener = new SizeDeterminerLayoutListener(this);
                observer.addOnPreDrawListener(layoutListener);
            }
        }
    }
		...
}

可以看到,如果View 的宽高已经确定,则会直接触发 onSizeReady,如果未确定,则是通过给视图的 ViewTreeObserver 设置 OnPreDrawListener 监听来实现的触发回调。

然后我们看下 onSizeReady 的实现。

public void onSizeReady(int width, int height) {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
		// 1
    if (status != Status.WAITING_FOR_SIZE) {
        return;
    }
    status = Status.RUNNING;
		// 2
    width = Math.round(sizeMultiplier * width);
    height = Math.round(sizeMultiplier * height);
		// 3
    ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
    final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);

    if (dataFetcher == null) {
        onException(new Exception("Failed to load model: \'" + model + "\'"));
        return;
    }
		// 4
    ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    }
		
    loadedFromMemoryCache = true;
		// 5
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
            priority, isMemoryCacheable, diskCacheStrategy, this);
    loadedFromMemoryCache = resource != null;
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
}

1、应该是为了防止方法多次执行

2、根据设置的 sizeMultiplier,获取需要加载的宽高

3、获取ModelLoader, DataFetche

ModelLoader: 创建 DataFetcher

DataFetcher :从数据源(Model)获取原始数据(Data)

4、获取 ResourceTranscoder :Resource 转码器

5、调用 Engine#load 开始加载

Engine

Engine 在 Glide 初始化时创建,下面是 Engine#load 源码

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
        DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
        Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();
		// 1
    final String id = fetcher.getId();
    EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
            loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
            transcoder, loadProvider.getSourceEncoder());
		// 2
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
        cb.onResourceReady(cached);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from cache", startTime, key);
        }
        return null;
    }
		// 3
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
        cb.onResourceReady(active);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from active resources", startTime, key);
        }
        return null;
    }
		// 4
    EngineJob current = jobs.get(key);
    if (current != null) {
        current.addCallback(cb);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Added to existing load", startTime, key);
        }
        return new LoadStatus(cb, current);
    }
		// 5
    EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
    DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
            transcoder, diskCacheProvider, diskCacheStrategy, priority);
    EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
    jobs.put(key, engineJob);
    engineJob.addCallback(cb);
    engineJob.start(runnable);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
}

1、生成资源的 key

2、根据 key 从 MemoryCache(默认实现类 LruResourceCache)内存缓存中获取资源,

3、如果从 MemoryCache 获取为 null,则从正在使用的内存缓存 activeResources( HashMap 保存资源的弱引用)中获取。

4、如果有与此资源匹配的加载工作正在运行,则将回调添加到这个加载工作中。

5、以上条件都不满足则开启一个新的的加载工作。

engineJob.start(runnable) 触发 EngineRunnable#run 方法

EnginRunnable#run

@Override
public void run() {
    if (isCancelled) {
        return;
    }

    Exception exception = null;
    Resource<?> resource = null;
    try {
        resource = decode();
    } catch (Exception e) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "Exception decoding", e);
        }
        exception = e;
    }

    if (isCancelled) {
        if (resource != null) {
            resource.recycle();
        }
        return;
    }

    if (resource == null) {
        onLoadFailed(exception);
    } else {
        onLoadComplete(resource);
    }
}

流程图

EngineJob#onResourceReady

将操作方在主线程上执行

private void handleResultOnMainThread() {
    if (isCancelled) {
        resource.recycle();
        return;
    } else if (cbs.isEmpty()) {
        throw new IllegalStateException("Received a resource without any callbacks to notify");
    }
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;

    // Hold on to resource for duration of request so we don't recycle it in the middle of notifying if it
    // synchronously released by one of the callbacks.
		// 1
    engineResource.acquire();
		// 2
    listener.onEngineJobComplete(key, engineResource);

    for (ResourceCallback cb : cbs) {
        if (!isInIgnoredCallbacks(cb)) {
            engineResource.acquire();
            cb.onResourceReady(engineResource);
        }
    }
		// 3 
    // Our request is complete, so we can release the resource.
    engineResource.release();
}

1、引用计数加1

2、通知 EngineJob 工作完成,会触发 Engine#onEngineJobComplete,这里会将资源放入 activeResources 中。

3、计数器减1,如果为了,会触发 Engine#onResourceReleased,这里会将资源从 activeResources 缓存中移除,并加入到 MemoryCache 中。

缓存

评论