Glide源碼分析——Glide基本用法荧嵌、緩存

我們最常用的gilde加載圖片的時候就用短短的方法就將圖片展示出來了:

Glide.with(this).load(url).into(imageview);

在這簡單的操作后面是很多的代碼,先從這一個個方法開始看起具则。

1.with()

RequestManager with()

public class Glide{
    ·····
    //適用于在正常fragment之外使用的資源或activity生命周期(在服務中或者通知縮略圖)
    //@param context 任何上下文都不會被保留
    //@return 可用于啟動加載的頂級應用程序的RequestManager
    public static RequestManager with(@NonNull Context context) {
        return getRetriever(context).get(context);
  }
  
  //該加載與傳入的activity生命周期相關聯(lián)
  //
  public static RequestManager with(@NonNull Activity activity) {
        return getRetriever(activity).get(activity);
  }
  
  public static RequestManager with(FragmentActivity activity) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(activity);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static RequestManager with(android.app.Fragment fragment) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(fragment);
    }

    public static RequestManager with(Fragment fragment) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(fragment);
    }
}
  • with()方法重載的中類很多即纲,可以傳入context、activity博肋、fragment等
  • RequsetManager調用get方法獲得RequestManagerRetriever對象
  • RequestManagerRetriever調用get方法獲取RequestManager對象

Glide#getRetriever

@NonNull
  private static RequestManagerRetriever getRetriever(@Nullable Context context) {
    Preconditions.checkNotNull(
        context,
        "You cannot start a load on a not yet attached View or a Fragment where getActivity() "
            + "returns null (which usually occurs when getActivity() is called before the Fragment "
            + "is attached or after the Fragment is destroyed).");
    return Glide.get(context).getRequestManagerRetriever();
}

調用Glide.get方法低斋,初始化glide

@NonNull
public static Glide get(@NonNull Context context) {
    if (glide == null) {
      GeneratedAppGlideModule annotationGeneratedModule =
          getAnnotationGeneratedGlideModules(context.getApplicationContext());
      synchronized (Glide.class) {
        if (glide == null) {
          checkAndInitializeGlide(context, annotationGeneratedModule);
        }
      }
    }
    return glide;
}

Glide加載是個靜態(tài)方法,且采用單例模式

RequestManagerRetriever#get()

public class RequestManagerRetriever implements Handler.Callback {
    ```
    private volatile RequestManager applicationManager;
    
    @NonNull
  private RequestManager getApplicationManager(@NonNull 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.

          // TODO(b/27524013): Factor out this Glide.get() call.
          Glide glide = Glide.get(context.getApplicationContext());
          applicationManager =
              factory.build(
                  glide,
                  new ApplicationLifecycle(),
                  new EmptyRequestManagerTreeNode(),
                  context.getApplicationContext());
        }
      }
    }

    return applicationManager;
  }
  
   @NonNull
  public RequestManager get(@NonNull Context context) {
    if (context == null) {
      throw new IllegalArgumentException("You cannot start a load on a null Context");
    } else if (Util.isOnMainThread() && !(context instanceof Application)) {
      if (context instanceof FragmentActivity) {
        return get((FragmentActivity) context);
      } else if (context instanceof Activity) {
        return get((Activity) context);
      } else if (context instanceof ContextWrapper
          // Only unwrap a ContextWrapper if the baseContext has a non-null application context.
          // Context#createPackageContext may return a Context without an Application instance,
          // in which case a ContextWrapper may be used to attach one.
          && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
        return get(((ContextWrapper) context).getBaseContext());
      }
    }

    return getApplicationManager(context);
  }

  @NonNull
  public RequestManager get(@NonNull FragmentActivity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
      assertNotDestroyed(activity);
      FragmentManager fm = activity.getSupportFragmentManager();
      return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }

  @NonNull
  public RequestManager get(@NonNull Fragment fragment) {
    Preconditions.checkNotNull(
        fragment.getContext(),
        "You cannot start a load on a fragment before it is attached or after it is destroyed");
    if (Util.isOnBackgroundThread()) {
      return get(fragment.getContext().getApplicationContext());
    } else {
      FragmentManager fm = fragment.getChildFragmentManager();
      return supportFragmentGet(fragment.getContext(), fm, fragment, fragment.isVisible());
    }
  }

  @SuppressWarnings("deprecation")
  @NonNull
  public RequestManager get(@NonNull Activity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();
      return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }
  ······
}

這里的get方法是根據(jù)傳入的不同的參數(shù)制定的束昵。實際上只分為兩種參數(shù)Application類型的參數(shù)和傳入非Application類型的參數(shù)拔稳。

  • 傳入Application參數(shù)類型的情況:

在Glide.with()方法中傳入的是一個Application對象,就會調用get()方法中參數(shù)為context的方法锹雏。然后會調用getApplicationManager()方法來獲取一個RequestManager對象巴比。

這是最簡單的一種情況。因為Application對象的生命周期就是應用程序的生命周期。Glide的生命周期和應用程序的生命周期就是同步的轻绞。關閉應用程序采记,Glide的加載也會終止。

  • 傳入非Application參數(shù)類型的情況:

這種情況下會像當前傳入的Activity等中添加一個隱藏的fragment政勃。比如說Glide正在加載一張圖片如果此時關閉了當前應用唧龄,那么glide應當停止對當前圖片的加載,但是glide并不知道當前activity的生命周期奸远,但是fragment的生命周期和activity是同步的既棺,若activity被銷毀了,fragment是可以監(jiān)聽到的懒叛,因此添加一個隱藏的fragment丸冕。

RequestManagerRetriever#fragmentGet

添加隱藏的fragment在supportFragmentGet()和fragmentGet()方法中。這兩個方法中有一個類RequestManagerFragment繼承于fragment薛窥,創(chuàng)建了fragment對象胖烛。

private RequestManager fragmentGet(
    @NonNull Context context,
    @NonNull android.app.FragmentManager fm,
    @Nullable android.app.Fragment parentHint,
    boolean isParentVisible) {
    //創(chuàng)建fragment對象
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      // TODO(b/27524013): Factor out this Glide.get() call.
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }

RequestManagerFragment

public class RequestManagerFragment extends Fragment {
    ······
    public RequestManagerFragment() {
        this(new ActivityFragmentLifecycle());
    }
    ······
}

這里面定義了一個ActivityFragmentLifecycle類來監(jiān)聽fragment生命周期的變化

ActivityFragmentLifecycle

LifecycleListener監(jiān)聽fragment生命周期

class ActivityFragmentLifecycle implements Lifecycle {
     @Override
  public void addListener(@NonNull LifecycleListener listener) {
    lifecycleListeners.add(listener);

    if (isDestroyed) {
      listener.onDestroy();
    } else if (isStarted) {
      listener.onStart();
    } else {
      listener.onStop();
    }
  }

  @Override
  public void removeListener(@NonNull LifecycleListener listener) {
    lifecycleListeners.remove(listener);
  }

  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();
    }
  }
}

2. load()

RequestManager

因為with()方法返回的是RequestManager對象,所以調用load方法的類肯定是RequestManager诅迷。load方法可加載的類型有很多佩番,這里只拿加載圖片的url為例。

我看的這一版的gilde已經(jīng)是3.10.0的了罢杉。網(wǎng)上一些博客中的DrawTypeRequest已經(jīng)沒有了趟畏。

public class RequestManager
    implements ComponentCallbacks2, LifecycleListener, ModelTypes<RequestBuilder<Drawable>>{
    ······
    public RequestBuilder<Drawable> asDrawable() {
        return as(Drawable.class);
    }
    
    public RequestBuilder<Drawable> load(@Nullable URL url) {
        return asDrawable().load(url);
    }
    
    //as方法中傳入的是Drawable對象
    @NonNull
    @CheckResult
    public RequestBuilder<Drawable> asDrawable() {
        return as(Drawable.class);
    }
    
    //resourceClass要解碼的資源
    //該方法返回一個新的請求構建器,用于加載給定的資源類屑那,進入RequestBuilder
    @NonNull
    @CheckResult
    public <ResourceType> RequestBuilder<ResourceType> as(
      @NonNull Class<ResourceType> resourceClass) {
        return new RequestBuilder<>(glide, this, resourceClass, context);
    }
    ······
}

看到加載圖片的load方法都返回RequestBuilder<Drawable>對象拱镐,且load方法都先由asDrawable()調用。

上面的源碼是從load()-->asDrawable()-->as()的過程持际,as方法中的resourceClass就是傳進來的Drawable.class這里的drawable指代的是一種資源的類型。下面再看下RequestBuilder這個類哗咆。

RequestManager中的load方法主要就是通過實例化RequestBuilder蜘欲,再調用RequestBuilder中的load方法。

RequestBuilder

public class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>>
    implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> {
    
    //構造方法晌柬,as中調用的方法就是這個
    protected RequestBuilder(
      @NonNull Glide glide,
      RequestManager requestManager,
      Class<TranscodeType> transcodeClass,
      Context context) {
    this.glide = glide;
    this.requestManager = requestManager;
    this.transcodeClass = transcodeClass;
    this.context = context;
    this.transitionOptions = requestManager.getDefaultTransitionOptions(transcodeClass);
    this.glideContext = glide.getGlideContext();

    initRequestListeners(requestManager.getDefaultRequestListeners());
    apply(requestManager.getDefaultRequestOptions());
  }
  
  ······
    //load加載的資源類型有很多種姥份,這里還是只放了url的
    public RequestBuilder<TranscodeType> load(@Nullable Uri uri) {
        return loadGeneric(uri);
    }
    
    @NonNull
    private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
        this.model = model;
        isModelSet = true;
        return this;
    }
}

在這個類可以看見一個自定義的泛型,TranscodeType轉碼類型年碘,他代表可以傳進來的url file bitmp等不同類型的資源澈歉。官方介紹是一個通用類,可以處理通用資源類型的設置選項和啟動負載屿衅。

load方法又調用了loadGeneric方法埃难,這里面設置了兩個變量,mode和isModleSet。

into

從load.into的調用關系可以知道into方法在RequestBuilder類中

@NonNull
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
    return into(target, /*targetListener=*/ null, Executors.mainThreadExecutor());
}

@NonNull
@Synthetic
<Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      Executor callbackExecutor) {
    return into(target, targetListener, /*options=*/ this, callbackExecutor);
}

@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);

    BaseRequestOptions<?> requestOptions = this;
    if (!requestOptions.isTransformationSet()
        && requestOptions.isTransformationAllowed()
        && view.getScaleType() != null) {
      // Clone in this method so that if we use this RequestBuilder to load into a View and then
      // into a different target, we don't retain the transformation applied based on the previous
      // View's scale type.
      switch (view.getScaleType()) {
        case CENTER_CROP:
          requestOptions = requestOptions.clone().optionalCenterCrop();
          break;
        case CENTER_INSIDE:
          requestOptions = requestOptions.clone().optionalCenterInside();
          break;
        case FIT_CENTER:
        case FIT_START:
        case FIT_END:
          requestOptions = requestOptions.clone().optionalFitCenter();
          break;
        case FIT_XY:
          requestOptions = requestOptions.clone().optionalCenterInside();
          break;
        case CENTER:
        case MATRIX:
        default:
          // Do nothing.
      }
    }
    
    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        Executors.mainThreadExecutor());
}

into方法最終會返回一個target對象涡尘。通過glide.buildImageViewTarget(view,transcodeClass)創(chuàng)建一個target對象忍弛,Target對象是最終用來展示圖片的。

buildImageViewTarget

@NonNull
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
      @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
public <Z> ViewTarget<ImageView, Z> buildTarget(
      @NonNull ImageView view, @NonNull Class<Z> clazz) {
    if (Bitmap.class.equals(clazz)) {
      return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
    } else if (Drawable.class.isAssignableFrom(clazz)) {
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
}

創(chuàng)建target對象主要分兩種:

  • 加載圖片時調用了asBitmap()方法考抄,會構建BitmapImageViewTarget對象
  • 其他情況细疚,構建GlideDrawableImageviewTarget對象

GenericRequestBuilder#into(Target target)

private <Y extends Target<TranscodeType>> Y into(
    @NonNull Y target,
    @Nullable RequestListener<TranscodeType> targetListener,
    BaseRequestOptions<?> options,
    Executor callbackExecutor) {
    Preconditions.checkNotNull(target);
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }
    
    //Request加載資源的請求類 Target繼承于LifecyclerListener,通過getRequest檢索到此目標當前的請求
    Request request = buildRequest(target, targetListener, options, callbackExecutor);

    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      if (!Preconditions.checkNotNull(previous).isRunning()) {
            previous.begin();
      }
      return target;
    }

    requestManager.clear(target);
    target.setRequest(request);
    //跟蹤、取消和重新啟動進程中川梅、已完成和失敗請求
    requestManager.track(target, request);

    return target;
    //3.7.0版本
    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())");
        }

        Request previous = target.getRequest();

        if (previous != null) {
            previous.clear();
            requestTracker.removeRequest(previous);
            previous.recycle();
        }

        Request request = buildRequest(target);
        target.setRequest(request);
        lifecycle.addListener(target);
        //執(zhí)行圖片請求疯兼,根據(jù)是不是加載狀態(tài)執(zhí)行
        requestTracker.runRequest(request);

        return target;
}

這里會檢查是不是在主線程,因為更新ui的操作必須在主線程贫途。這里通過target對象獲取request對象镇防,然后清除之前的request對象,回收request對象潮饱,然后重新構建一個request對象来氧,并給這個target對象設置request對象。

下面的分析拿glide3.7.0的版本來看∠憷現(xiàn)在4.10.0版本和之前的版本差別太大了啦扬。

在這之后就開始加載圖片。buildRequest()方法構建出來Request對象凫碌,buildRequest-->buildRequestRecursive()-->obtainRequest-->GenericRequest.obtain()-->REQUEST_POOL.poll()扑毡。調用了請求池的poll方法,獲取request對象盛险。如果這個對象為空就創(chuàng)建一個GenericRequest對象瞄摊,然后用request對象的init方法進行初始化。

runRequest

public void runRequest(Request request) {
       requests.add(request);
       if (!isPaused) {
           //執(zhí)行request
           request.begin();
       } else {
           //將request加入待執(zhí)行隊列
           pendingRequests.add(request);
       }
}

先判斷Glide當前是不是處于暫停狀態(tài)苦掘。
在執(zhí)行圖片的加載過程中有幾個重要的類:

  • ModeLoader:獲取原始數(shù)據(jù)的類
  • ResourceTranscoder:圖片資源轉換的類
  • Engine:負責啟動負載并管理活動和緩存的資源類
  • DataLoadProvider:負責圖片的編碼和解碼
  • DataFetcher:將流資源轉換為glide實際加載圖片需要的數(shù)據(jù)换帜,比如byte[] file uri url等
  • EngineJob:開啟線程,為異步加載圖片做準備

緩存

glide緩存主要分為內(nèi)存緩存和硬盤緩存鹤啡。這里將從這兩個方向來分析惯驼。

緩存key

緩存功能要實現(xiàn)的話肯定是要有進行緩存的key的。拿到這個key我們才能找到對應的圖片递瑰。

public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {

    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();

        final String id = fetcher.getId();
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());

        ...
    }
    ...
}

這里調用fetcher.getId()方法獲得了一個id字符串祟牲,這個字符串是加載圖片的唯一標識。EngineKey就是保存key的類抖部,一個key有10個標識可以區(qū)分说贝。

內(nèi)存緩存

默認情況下,Glide自動就是會開啟內(nèi)存緩存的慎颗。如果不想開啟內(nèi)存緩存乡恕,只需要設置skipMemoryCache(true)就可以了言询。

Glide.with(this)
    .load(url)
    .skipMemoryCache(true)
    .into(imageView);

Glide緩存實現(xiàn)了LruCache算法,還結合了一種弱引用的機制几颜,共同完成了內(nèi)存緩存的功能倍试。

Glide獲取緩存資源對象

loadGeneric()-->Glide.buildStreamModelLoader()-->buildModelLoader()-->Glide.get()-->createGlide()

public class GlideBuilder {
    ...

    Glide createGlide() {
        if (sourceService == null) {
            final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
            sourceService = new FifoPriorityThreadPoolExecutor(cores);
        }
        if (diskCacheService == null) {
            diskCacheService = new FifoPriorityThreadPoolExecutor(1);
        }
        MemorySizeCalculator calculator = new MemorySizeCalculator(context);
        if (bitmapPool == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                int size = calculator.getBitmapPoolSize();
                bitmapPool = new LruBitmapPool(size);
            } else {
                bitmapPool = new BitmapPoolAdapter();
            }
        }
        if (memoryCache == null) {
            //Glide實現(xiàn)內(nèi)存緩存使用的LruCache對象
            memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
        }
        if (diskCacheFactory == null) {
            diskCacheFactory = new InternalCacheDiskCacheFactory(context);
        }
        if (engine == null) {
            engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);
        }
        if (decodeFormat == null) {
            decodeFormat = DecodeFormat.DEFAULT;
        }
        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
    }
}

LruCache算法的實現(xiàn)主要的點就在于它用了LindedHashMap來保存數(shù)據(jù),其中的一個特性accessOrder = true是基于訪問順序的蛋哭,再加上對LindkedHashMap的數(shù)據(jù)操作上鎖實現(xiàn)的緩存策略县习。調用get方法訪問緩存對象時,就會調用LinkedHashMap的get()方法獲取對應集合的元素谆趾,同時會更新該元素到隊列躁愿。當調用put()方法時,就會在集合中添加元素沪蓬,并調用trimToSize()判斷緩存是否以滿彤钟,如果滿了就用LinkedHashMap的迭代器刪除對首元素,也就是近期最少訪問的元素跷叉。

Engine#load()

內(nèi)存緩存的代碼在這里實現(xiàn)

public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {
    ...    

    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();

        final String id = fetcher.getId();
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());

        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;
        }

        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;
        }

        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);
        }

        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);
    }

    ...
}

在這個方法里其實可以把加載圖片的過程分為三步:

  • loadFromCache():從緩存中獲取
  • loadFromActivityResources():獲取緩存圖片
  • engineJob.start(runnable):開啟線程從網(wǎng)絡加載圖片

Glide加載過程中會調用loadFromCache()和loadFromActivityResources()這兩個方法來獲取內(nèi)存緩存逸雹。一個使用的是LruCache算法,另一個使用的是弱引用云挟。

loadFromCache/loadFromActivityResources

public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {

    private final MemoryCache cache;
    private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
    ...

    private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }
        EngineResource<?> cached = getEngineResourceFromCache(key);
        if (cached != null) {
            cached.acquire();
            activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
        }
        return cached;
    }

    private EngineResource<?> getEngineResourceFromCache(Key key) {
        Resource<?> cached = cache.remove(key);
        final EngineResource result;
        if (cached == null) {
            result = null;
        } else if (cached instanceof EngineResource) {
            result = (EngineResource) cached;
        } else {
            result = new EngineResource(cached, true /*isCacheable*/);
        }
        return result;
    }

    private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }
        EngineResource<?> active = null;
        WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
        if (activeRef != null) {
            active = activeRef.get();
            if (active != null) {
                active.acquire();
            } else {
                activeResources.remove(key);
            }
        }
        return active;
    }

    ...
}

loadFromCache()方法先會判斷是否開啟了內(nèi)存緩存梆砸,開啟了則會調用getEngineResourceFromCache()方法從cache對象也就是LruResourceCache中獲取圖片資源,但是卻把這個資源從緩存中removele园欣。獲取到這個資源后把這個資源又加入到了activeResources中帖世。這很令我困惑,為什么這樣做沸枯,按理來說Lru會根據(jù)獲取到的這個資源會把它重新加入到隊列后面日矫,為了就是會被刪除。

activeResources就是一個弱引用的HashMap,用來緩存正在使用中的圖片绑榴。loadFromActiveResources()方法就是從activeResources這個HashMap當中取值的哪轿。使用activeResources來緩存正在使用中的圖片可以保護這些圖片不會被LruCache算法回收掉。

內(nèi)存緩存的寫入

當圖片加載完成后彭沼,會在EngineJob當中通過Handler發(fā)送一條消息將執(zhí)行邏輯切回到主線程當中缔逛,從而執(zhí)行了handleResultOnMainThread()方法。

class EngineJob implements EngineRunnable.EngineRunnableManager {

    private final EngineResourceFactory engineResourceFactory;
    ...

    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;
        engineResource.acquire();
        listener.onEngineJobComplete(key, engineResource);
        for (ResourceCallback cb : cbs) {
            if (!isInIgnoredCallbacks(cb)) {
                engineResource.acquire();
                cb.onResourceReady(engineResource);
            }
        }
        engineResource.release();
    }

    static class EngineResourceFactory {
        public <R> EngineResource<R> build(Resource<R> resource, boolean isMemoryCacheable) {
            return new EngineResource<R>(resource, isMemoryCacheable);
        }
    }
    ...
}

EngineJob這個類就是主要就是來執(zhí)行添加(addCallBack)和刪除(rmoveCallBack)回調姓惑,并在回調時通知回調來管理負載的類加載完成。

handleResultOnMainThread()方法中通過EngineResourceFactory構建出了一個包含圖片資源的EngineResource對象按脚,將這個對象傳入Engine的onEngineJobComplete方法中于毙。

onEngineJobComplete

@Override
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
    Util.assertMainThread();
    // A null resource indicates that the load failed, usually due to an exception.
    if (resource != null) {
        resource.setResourceListener(key, this);

        if (resource.isCacheable()) {
            activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
        }
    }
    // TODO: should this check that the engine job is still current?
    jobs.remove(key);
}

可看到回調過來的EngineResource被put到了activeResources當中,在這里將圖片寫入了緩存辅搬。但這里只是弱引用緩存唯沮,還有一種LruCache在哪里呢脖旱?

acquire()和release()

class EngineResource<Z> implements Resource<Z> {

    private int acquired;
    ...

    void acquire() {
        if (isRecycled) {
            throw new IllegalStateException("Cannot acquire a recycled resource");
        }
        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
            throw new IllegalThreadStateException("Must call acquire on the main thread");
        }
        ++acquired;
    }

    void release() {
        if (acquired <= 0) {
            throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
        }
        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
            throw new IllegalThreadStateException("Must call release on the main thread");
        }
        if (--acquired == 0) {
            listener.onResourceReleased(key, this);
        }
    }
}

這兩個方法handleResultOnMainThread()方法中出現(xiàn)過,engineResource調用了acquire()方法后介蛉,才執(zhí)行了onEngineJobComplete()方法中萌庆,也就是將圖片加入了activeResources弱引用集合中。將圖片加入弱引用map達到將當前正在使用的圖片加入的效果币旧,就出現(xiàn)了acquire和release方法践险。

當acquired變量大于0時,說明圖片正在使用吹菱,應該放入activeResources弱引用緩存當中巍虫。而經(jīng)過release()之后,如果acquired值為0了鳍刷,說明圖片不再被使用了占遥,將會調用listener的onResourceReleased()方法來釋放資源。

onResourceReleased()

public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {

    private final MemoryCache cache;
    private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
    ...    

    @Override
    public void onResourceReleased(Key cacheKey, EngineResource resource) {
        Util.assertMainThread();
        activeResources.remove(cacheKey);
        if (resource.isCacheable()) {
            cache.put(cacheKey, resource);
        } else {
            resourceRecycler.recycle(resource);
        }
    }

    ...
}

這里首先會將緩存圖片從activeresources中移除输瓜,然后再將它put到LruResourceCache中瓦胎。否則就進行資源回收。這樣就實現(xiàn)了正在使用中的圖片使用弱引用來進行緩存尤揣,不在使用中的圖片使用LruCache來進行緩存搔啊。

硬盤緩存

禁止硬盤緩存可以這樣使用:

Glide.with(this)
    .load(url)
    .disCacheStrategy(DiskCacheStrategy.NONE)
    .into(imageview);

disCacheStrategy可以接收四種參數(shù):

  • DiskCacehStrategy.NONE:不緩存任何內(nèi)容
  • DiskCacehStrategy.SOURCE:只緩存原始圖片
  • DiskCacehStrategy.RESULT:只緩存轉換后的圖片
  • DiskCacehStrategy.ALL:既緩存原始圖片,也緩存轉換后的圖片

decode()

Glide開啟線程加載圖片后會執(zhí)行EngineRunnable的run()方法芹缔,run方法中又會調用一個decode方法

private Resource<?> decode() throws Exception {
    if (isDecodingFromCache()) {
        return decodeFromCache();
    } else {
        return decodeFromSource();
    }
}

這里有兩個方法:

  • decodeFromCache():從硬盤緩存中讀取圖片
  • decodeFromSource():讀取原始圖片

Glide會優(yōu)先從硬盤緩存中讀取

decodeFromCache

private Resource<?> decodeFromCache() throws Exception {
    Resource<?> result = null;
    try {
        //DisCacheStrategy.RESULT
        result = decodeJob.decodeResultFromCache();
    } catch (Exception e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Exception decoding result from cache: " + e);
        }
    }
    if (result == null) {
        //DisCacheStrategy.SOURCE
        result = decodeJob.decodeSourceFromCache();
    }
    return result;
}

執(zhí)行兩個方法的區(qū)別就在于傳入disCacheStrategy方法中的參數(shù)

decodeResultFromCache

public Resource<Z> decodeResultFromCache() throws Exception {
    if (!diskCacheStrategy.cacheResult()) {
        return null;
    }
    long startTime = LogTime.getLogTime();
    Resource<T> transformed = loadFromCache(resultKey);
    startTime = LogTime.getLogTime();
    Resource<Z> result = transcode(transformed);
    return result;
}
先調用loadFromCache()從緩存中讀取數(shù)據(jù)坯癣,直接將數(shù)據(jù)解碼并返回

decodeSourceFromCache()

public Resource<Z> decodeSourceFromCache() throws Exception {
    if (!diskCacheStrategy.cacheSource()) {
        return null;
    }
    long startTime = LogTime.getLogTime();
    Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
    return transformEncodeAndTranscode(decoded);
}

先調用loadFromCache()從緩存中讀取數(shù)據(jù),調用transformEncodeAndTrancode()方法先將數(shù)據(jù)轉換再解碼返回最欠。

這兩個方法在調用loadFromCache()方法中傳入的參數(shù)不同示罗。因為Glide緩存的key是由10個參數(shù)共同組成的。如果是緩存原始圖片芝硬,不需要那么多的參數(shù)蚜点。

硬盤緩存的讀取

private Resource<T> loadFromCache(Key key) throws IOException {
    File cacheFile = diskCacheProvider.getDiskCache().get(key);
    if (cacheFile == null) {
        return null;
    }
    Resource<T> result = null;
    try {
        result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
    } finally {
        if (result == null) {
            diskCacheProvider.getDiskCache().delete(key);
        }
    }
    return result;
}

硬盤緩存的讀取在loadFromCache()中。getDishCache()方法獲取到Glide自己編寫的DiskLruCache工具類的實例拌阴,然后調用它的get()方法把緩存key傳入绍绘,就獲得硬盤緩存的文件了。文件不為空迟赃,就將其解碼成Resource對象后返回就行陪拘。

decodeFromSource()

public Resource<Z> decodeFromSource() throws Exception {
    //解析原圖片
    Resource<T> decoded = decodeSource();
    //對圖片進行轉換和轉碼,再將轉換過后的圖片寫入到硬盤緩存中
    return transformEncodeAndTranscode(decoded);
}

在沒有緩存的情況下,會調用decodeFromDecode()方法來讀取原始圖片纤壁。

decodeSource()

private Resource<T> decodeSource() throws Exception {
    Resource<T> decoded = null;
    try {
        long startTime = LogTime.getLogTime();
        //當相應的資源不在緩存中時才調用此方法左刽,異步獲取可以從中解碼資源的數(shù)據(jù)
        //fetcher是ImageVideoFetcher HttpUrlFetcher中真正進行網(wǎng)絡請求
        final A data = fetcher.loadData(priority);
        if (isCancelled) {
            return null;
        }
        //對數(shù)據(jù)進行解碼
        decoded = decodeFromSourceData(data);
    } finally {
        fetcher.cleanup();
    }
    return decoded;
}

decodeFromSourceData

private Resource<T> decodeFromSourceData(A data) throws IOException {
    final Resource<T> decoded;
    //是否允許緩存原始圖片
    if (diskCacheStrategy.cacheSource()) {
        decoded = cacheAndDecodeSourceData(data);
    } else {
        long startTime = LogTime.getLogTime();
        decoded = loadProvider.getSourceDecoder().decode(data, width, height);
    }
    return decoded;
}
private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
    long startTime = LogTime.getLogTime();
    SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
    diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
    startTime = LogTime.getLogTime();
    Resource<T> result = loadFromCache(resultKey.getOriginalKey());
    return result;
}

調用getDiskCache()方法來獲取DiskLruCache實例,調用put方法寫入硬盤緩存酌媒。原始圖片的緩存key是用的resultKey.getOriginalKey()

參考文章

郭大神Glide系列源碼分析

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末欠痴,一起剝皮案震驚了整個濱河市迄靠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌喇辽,老刑警劉巖掌挚,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機唉匾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奇徒,“玉大人,你說我怎么就攤上這事缨硝∧Ω疲” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵查辩,是天一觀的道長胖笛。 經(jīng)常有香客問我,道長宜岛,這世上最難降的妖魔是什么长踊? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮萍倡,結果婚禮上身弊,老公的妹妹穿的比我還像新娘。我一直安慰自己列敲,他們只是感情好阱佛,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著戴而,像睡著了一般凑术。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上所意,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天淮逊,我揣著相機與錄音,去河邊找鬼扶踊。 笑死泄鹏,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的秧耗。 我是一名探鬼主播命满,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绣版!你這毒婦竟也來了胶台?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤杂抽,失蹤者是張志新(化名)和其女友劉穎诈唬,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缩麸,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡铸磅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了杭朱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阅仔。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖弧械,靈堂內(nèi)的尸體忽然破棺而出八酒,到底是詐尸還是另有隱情,我是刑警寧澤刃唐,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布羞迷,位于F島的核電站,受9級特大地震影響画饥,放射性物質發(fā)生泄漏衔瓮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一抖甘、第九天 我趴在偏房一處隱蔽的房頂上張望热鞍。 院中可真熱鬧,春花似錦衔彻、人聲如沸薇宠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昼接。三九已至,卻和暖如春悴晰,著一層夾襖步出監(jiān)牢的瞬間慢睡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工铡溪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留漂辐,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓棕硫,卻偏偏與公主長得像髓涯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子哈扮,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容