Glide圖片框架使用詳細介紹(五)之Glide-源碼詳解

一.Glide的構造

//Glide.java

 Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context, DecodeFormat decodeFormat) {
              ...
    }

Glide是通過GlideBuilder中的createGlide方法生成的(核心代碼如下)

//GlideBuilder.java

Glide createGlide() {
        ...
        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
    }

Glide的構造參數主要有四個,都是通過createGlide生成的.

MemoryCache 內存緩存

BitmapPool 圖片池

DecodeFormat 圖片格式

Engine 引擎類

1.MemoryCache :內存緩存 LruResourceCache

//MemorySizeCalculator.java

final int maxSize = getMaxSize(activityManager);

private static int getMaxSize(ActivityManager activityManager) {
    //每個進程可用的最大內存
    final int memoryClassBytes = activityManager.getMemoryClass() * 1024 * 1024;

    //判斷是否低配手機
    final boolean isLowMemoryDevice = isLowMemoryDevice(activityManager);

    return Math.round(memoryClassBytes
            * (isLowMemoryDevice ? LOW_MEMORY_MAX_SIZE_MULTIPLIER : MAX_SIZE_MULTIPLIER));
}

最大內存:如果是低配手機,就每個進程可用的最大內存乘以0.33,否則就每個進程可用的最大內存乘以0.4

//MemorySizeCalculator.java

int screenSize = screenDimensions.getWidthPixels() * screenDimensions.getHeightPixels()
                * BYTES_PER_ARGB_8888_PIXEL;(寬*高*4)
int targetPoolSize = screenSize * BITMAP_POOL_TARGET_SCREENS;(寬*高*4*4)
int targetMemoryCacheSize = screenSize * MEMORY_CACHE_TARGET_SCREENS;(寬*高*4*2)

//判斷是否超過最大值,否則就等比縮小
if (targetMemoryCacheSize + targetPoolSize <= maxSize) {
    memoryCacheSize = targetMemoryCacheSize;
    bitmapPoolSize = targetPoolSize;
} else {
    int part = Math.round((float) maxSize / (BITMAP_POOL_TARGET_SCREENS + MEMORY_CACHE_TARGET_SCREENS));
    memoryCacheSize = part * MEMORY_CACHE_TARGET_SCREENS;
    bitmapPoolSize = part * BITMAP_POOL_TARGET_SCREENS;
}
targetPoolSize 和 targetMemoryCacheSize 之和不能超過maxSize 否則就等比縮小

//GlideBuilder.java

memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());

內存緩存用的是targetMemoryCacheSize (即一般是緩存大小是屏幕的寬 * 高 * 4 * 2)

2.BitmapPool 圖片池 LruBitmapPool

int size = calculator.getBitmapPoolSize();
bitmapPool = new LruBitmapPool(size);

圖片池用的是targetPoolSize(即一般是緩存大小是屏幕的寬*高*4*4)

3.DecodeFormat 圖片格式

DecodeFormat DEFAULT = PREFER_RGB_565

默認是RGB_565

4.Engine 引擎類

//GlideBuilder.java

engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);

engine 裏面主要參數

內存緩存 memoryCache
本地緩存 diskCacheFactory
處理源資源的線程池 sourceService
處理本地緩存的線程池 diskCacheService
(1)memoryCache:內存緩存 LruBitmapPool

上面已經做了介紹

(2)diskCacheFactory:本地緩存 DiskLruCacheFactory

//DiskCache.java

/** 250 MB of cache. */
        int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
        String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";

默認大小:250 MB

默認目錄:image_manager_disk_cache

(3)sourceService 處理源資源的線程池 (ThreadPoolExecutor的子類)

final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());//獲得可用的處理器個數
sourceService = new FifoPriorityThreadPoolExecutor(cores);
線程池的核心線程數量等於獲得可用的處理器個數

(4)diskCacheService 處理本地緩存的線程池 (ThreadPoolExecutor的子類)

diskCacheService = new FifoPriorityThreadPoolExecutor(1);

線程池的核心線程數量爲1

二.with方法

with方法有很多重載,最後會返回一個RequestManager

//Glide.java

/**
 * @see #with(android.app.Activity)
 * @see #with(android.app.Fragment)
 * @see #with(android.support.v4.app.Fragment)
 * @see #with(android.support.v4.app.FragmentActivity)
 *
 * @param context Any context, will not be retained.
 * @return A RequestManager for the top level application that can be used to start a load.
 */
public static RequestManager with(Context context) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(context);
}
就算你傳入的是Context ,這裏也會根據你Context 實際的類型,走不同的分支

//RequestManagerRetriever.java

public RequestManager get(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) {
            return get(((ContextWrapper) context).getBaseContext());
        }
    }
    return getApplicationManager(context);
}
這裏以FragmentActivity爲例,最後會創建一個無界面的Fragment,即SupportRequestManagerFragment ,讓請求和你的activity的生命週期同步

//RequestManagerRetriever.java

public RequestManager get(FragmentActivity activity) {
    if (Util.isOnBackgroundThread()) {
        return get(activity.getApplicationContext());
    } else {
        assertNotDestroyed(activity);
        FragmentManager fm = activity.getSupportFragmentManager();
        return supportFragmentGet(activity, fm);
    }
}
RequestManager supportFragmentGet(Context context, FragmentManager fm) {
    SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
        requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
        current.setRequestManager(requestManager);
    }
    return requestManager;
}

這裏需要注意一下,如果你是在子線程調用with方法,或者傳入的Context是Application的話,請求是跟你的Application的生命週期同步

//RequestManagerRetriever.java

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

三.load方法

這裏方法也有很多重載

//RequestManager.java

public DrawableTypeRequest<String> load(String string) {
    return (DrawableTypeRequest<String>) fromString().load(string);
}

但是最後都會返回一個DrawableTypeRequest (繼承了DrawableRequestBuilder)

DrawableRequestBuilder就是支持鏈式調用的一個類,我們平時有類似的需求的時候也可以模仿這樣的處理方式,把一些非必須參數用鏈式調用的方式來設置

四.into方法

//GenericRequestBuilder.java    

public Target<TranscodeType> into(ImageView view) {
        Util.assertMainThread();
        if (view == null) {
            throw new IllegalArgumentException("You must pass in a non null View");
        }
        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.
            }
        }
        return into(glide.buildImageViewTarget(view, transcodeClass));
    }

這裏有三點需要注意的:

1.Util.assertMainThread();這裏會檢查是否主線程,不是的話會拋出異常,所以into方法必須在主線程中調用.

2.當你沒有調用transform方法,並且你的ImageView設置了ScaleType,那麼他會根據你的設置,對圖片做處理(具體處理可以查看DrawableRequestBuilder的applyCenterCrop或者applyFitCenter方法,我們自己自定義BitmapTransformation也可以參考這裏的處理).

3.view在這裏被封裝成一個Target.

//GenericRequestBuilder.java        

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())");
    }
    Request previous = target.getRequest();
    if (previous != null) {
        previous.clear();
        requestTracker.removeRequest(previous);
        previous.recycle();
    }
    Request request = buildRequest(target);
    target.setRequest(request);
    lifecycle.addListener(target);
    requestTracker.runRequest(request);
    return target;
}

這裏可以看到控件封裝成的Target能夠獲取自身綁定的請求,當發現之前的請求還在的時候,會把舊的請求清除掉,綁定新的請求,這也就是爲什麼控件複用時不會出現圖片錯位的問題(這點跟我在Picasso源碼中看到的處理方式很相像).

接着在into裏面會調用buildRequest方法來創建請求

//GenericRequestBuilder.java    

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

private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
        if (thumbnailRequestBuilder != null) {
           ...
            Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
           ...
            Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator);
           ...
            coordinator.setRequests(fullRequest, thumbRequest);
            return coordinator;
        } else if (thumbSizeMultiplier != null) {           
            ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
            Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
            Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator);
            coordinator.setRequests(fullRequest, thumbnailRequest);
            return coordinator;
        } else {
            // Base case: no thumbnail.
            return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);
        }
    }

1.這裏就是請求的生成,buildRequestRecursive裏面if有三個分支,這裏是根據你設置thumbnail的情況來判斷的,第一個是設置縮略圖爲新的請求的情況,第二個是設置縮略圖爲float的情況,第三個就是沒有設置縮略圖的情況.

前兩個設置了縮略圖的是有兩個請求的,fullRequest和thumbnailRequest,沒有設置縮略圖則肯定只有一個請求了.

2.請求都是通過obtainRequest方法生成的(這個簡單瞭解一下就行)

//GenericRequestBuilder.java

private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
            RequestCoordinator requestCoordinator) {
        return GenericRequest.obtain(...);
    }
REQUEST_POOL是一個隊列,當隊列中有,那麼就從隊列中取,沒有的話就新建一個GenericRequest

//GenericRequest.java

public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(...) {

        GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
        if (request == null) {
            request = new GenericRequest<A, T, Z, R>();
        }
        request.init(...);
        return request;
    }

回到into方法:當創建了請求後runRequest會調用Request的begin方法,即調用GenericRequest的begin方法

//GenericRequestBuilder.java

 public <Y extends Target<TranscodeType>> Y into(Y target) {

        Request request = buildRequest(target);
      ...
        requestTracker.runRequest(request);
      ...
    }
//GenericRequest.java

 public void begin() {
    ...
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
            onSizeReady(overrideWidth, overrideHeight);
        } else {
            target.getSize(this);
        }
    ...
    }
最終會調用Engine的load方法

//GenericRequest.java

public void onSizeReady(int width, int height) {
    ...
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
                priority, isMemoryCacheable, diskCacheStrategy, this);
    ...
    }

我們先看load方法的前面一段:

1.首先會嘗試從cache裏面取,這裏cache就是Glide的構造函數裏面的MemoryCache(是一個LruResourceCache),如果取到了,就從cache裏面刪掉,然後加入activeResources中

2.如果cache裏面沒取到,就會從activeResources中取,activeResources是一個以弱引用爲值的map,他是用於存儲使用中的資源.之所以在內存緩存的基礎上又多了這層緩存,是爲了當內存不足而清除cache中的資源中,不會影響使用中的資源.

//Engine.java   

public <T, Z, R> LoadStatus load(...) {
        ...
        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;
        }
        ...
    }

load方法接着會通過EngineJobFactory創建一個EngineJob,裏面主要管理裏兩個線程池,diskCacheService和sourceService,他們就是Glide構造函數中Engine裏面創建的那兩個線程池.

//Engine.java

public <T, Z, R> LoadStatus load(...) {
            ...
            EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
           ...
        }
//Engine.java
static class EngineJobFactory {
    private final ExecutorService diskCacheService;
    private final ExecutorService sourceService;
    private final EngineJobListener listener;

    public EngineJobFactory(ExecutorService diskCacheService, ExecutorService sourceService,
            EngineJobListener listener) {
        this.diskCacheService = diskCacheService;
        this.sourceService = sourceService;
        this.listener = listener;
    }

    public EngineJob build(Key key, boolean isMemoryCacheable) {
        return new EngineJob(key, diskCacheService, sourceService, isMemoryCacheable, listener);
    }
}
接着說load方法,前面創建了EngineJob,接着調用EngineJob的start方法,並將EngineRunnable放到diskCacheService(處理磁盤緩存的線程池裏面運行),接着線程池就會調用EngineRunnable的run方法.

//Engine.java

public <T, Z, R> LoadStatus load(...) {
        ...
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        engineJob.start(runnable);
       ...
    }
//EngineJob.java

public void start(EngineRunnable engineRunnable) {
    this.engineRunnable = engineRunnable;
    future = diskCacheService.submit(engineRunnable);
}
//EngineRunnable.java

public void run() {
       ...
        try {
            resource = decode();
        } catch (Exception e) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "Exception decoding", e);
            }
            exception = e;
        }
       ...
        if (resource == null) {
            onLoadFailed(exception);
        } else {
            onLoadComplete(resource);
        }
    }

run裏面調用的是decode()方法,裏面會嘗試先從磁盤緩存中讀取,如果不行就從源資源中讀取

//EngineRunnable.java   

private Resource<?> decode() throws Exception {
    if (isDecodingFromCache()) {
        //第一次會走這
        return decodeFromCache();//從磁盤緩存中讀取

    } else {

        return decodeFromSource();//從源資源中讀取

    }
}

我們先來看從磁盤中讀取的策略

//EngineRunnable.java

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

    if (result == null) {
        result = decodeJob.decodeSourceFromCache();
    }
    return result;
}

我們可以看到這裏先嚐試讀取處理後的圖片(Result),然後再嘗試讀取原圖,但是這裏面具體邏輯會根據你設置的磁盤緩存策略來決定是否真的會讀取處理圖和原圖

那麼我們再回到EngineRunnable的run()方法中

public void run() {
           ...
            try {
                resource = decode();
            } catch (Exception e) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "Exception decoding", e);
                }
                exception = e;
            }
           ...
            if (resource == null) {
                onLoadFailed(exception);
            } else {
                onLoadComplete(resource);
            }
        }

第一次走decode的時候會先嚐試從磁盤中獲取,如果獲取的爲null,那麼在onLoadFailed方法裏面又會把這個run再次放入線程池中,但是這次是放入sourceService(處理源資源的線程池)

//EngineRunnable.java

private void onLoadFailed(Exception e) {
    if (isDecodingFromCache()) {
        stage = Stage.SOURCE;
        manager.submitForSource(this);
    } else {
        manager.onException(e);
    }
}
//EngineJob.java

@Override
public void submitForSource(EngineRunnable runnable) {
    future = sourceService.submit(runnable);
}

接着sourceService裏面又會調用調用EngineRunnable的run方法,這次decode裏面會走從源資源讀取的那條分支

//EngineRunnable.java   

private Resource<?> decode() throws Exception {
    if (isDecodingFromCache()) {
        //第一次會走這
        return decodeFromCache();//從磁盤緩存中讀取

    } else {
        //第二次會走這
        return decodeFromSource();//從源資源讀取

    }
}

//DecodeJob.java

public Resource<Z> decodeFromSource() throws Exception {
    Resource<T> decoded = decodeSource();//獲取數據,並解碼
    return transformEncodeAndTranscode(decoded);//處理圖片
}

裏面主要做了兩件事,一個是獲取圖片,一個是處理圖片

1.我們先來看獲取圖片的decodeSource方法

//DecodeJob.java

private Resource<T> decodeSource() throws Exception {
       ...
        //拉取數據
        final A data = fetcher.loadData(priority);
       ...
        //解碼,並保存源資源到磁盤
        decoded = decodeFromSourceData(data);
       ...
    return decoded;
}
//DecodeJob.java
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);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Decoded from source", startTime);
        }
    }
    return decoded;
}

這裏調用了DataFetcher的loadData方法來獲取數據,DataFetcher有很多實現類,一般來說我們都是從網絡中讀取數據,我們這邊就以HttpUrlFetcher爲例

//HttpUrlFetcher.java

@Override
public InputStream loadData(Priority priority) throws Exception {
    return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
}

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
        throws IOException {
    if (redirects >= MAXIMUM_REDIRECTS) {
        throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
    } else {
        // Comparing the URLs using .equals performs additional network I/O and is generally broken.
        // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
        try {
            if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
                throw new IOException("In re-direct loop");
            }
        } catch (URISyntaxException e) {
            // Do nothing, this is best effort.
        }
    }
    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    urlConnection.setConnectTimeout(2500);
    urlConnection.setReadTimeout(2500);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);

    // Connect explicitly to avoid errors in decoders if connection fails.
    urlConnection.connect();
    if (isCancelled) {
        return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (statusCode / 100 == 2) {
        //請求成功
        return getStreamForSuccessfulRequest(urlConnection);
    } else if (statusCode / 100 == 3) {
        String redirectUrlString = urlConnection.getHeaderField("Location");
        if (TextUtils.isEmpty(redirectUrlString)) {
            throw new IOException("Received empty or null redirect url");
        }
        URL redirectUrl = new URL(url, redirectUrlString);
        return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
    } else {
        if (statusCode == -1) {
            throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
        }
        throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
    }
}

2.看完了獲取圖片的方法,我們再來看看處理圖片的方法transformEncodeAndTranscode

//DecodeJob.java

private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
   ...
    //對圖片做剪裁等處理
    Resource<T> transformed = transform(decoded);
  ...
    //將處理後的圖片寫入磁盤緩存(會根據配置來決定是否寫入)
    writeTransformedToCache(transformed);
 ...
    //轉碼,轉爲需要的類型
    Resource<Z> result = transcode(transformed);
 ...
    return result;
}

Glide的整個加載流程就基本上走完了,整個篇幅還是比較長的,第一次看得時候可能會有點懵逼,需要結合着源碼多走幾遍才能夠更加熟悉.

閱讀源碼並不是我們最終的目的,我們閱讀源碼主要有兩個目的

一個是能夠更加熟悉這個框架,那麼使用的時候就更加得心應手了,比如我從源碼中發現了很多文章都說錯了默認的緩存策略

一個是學習裏面的設計模式和思想,應用到我們自己的項目中,比如說面對接口編程,對於不同的數據,用DataFetcher的不同實現類來拉取數據

發佈了72 篇原創文章 · 獲贊 29 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章