Glide 提煉總結

使用

一個簡單的使用示例以下:算法

// 須要注意的是,佔位符不是異步加載的
RequestOptions options = new RequestOptions()
        .placeholder(R.drawable.ic_launcher_background)
        .error(R.drawable.error)
        .diskCacheStrategy(DiskCacheStrategy.NONE);
        
Glide.with(this)
     .load(url)
     .apply(options)
     .into(imageView);
複製代碼

若是但願只獲取資源,而不顯示,可使用 CustomTarget:數組

Glide.with(context
    .load(url)
    .into(new CustomTarget<Drawable>() {
        @Override
        public void onResourceReady(Drawable resource, Transition<Drawable> transition) {
            // Do something with the Drawable here.
        }

        @Override
        public void onLoadCleared(@Nullable Drawable placeholder) {
            // Remove the Drawable provided in onResourceReady from any Views and ensure
            // no references to it remain.
        }
    });
複製代碼

若是須要在後臺線程加載,可使用 submit 獲取一個 FutureTarget:緩存

FutureTarget<Bitmap> futureTarget = Glide.with(context)
    .asBitmap()
    .load(url)
    .submit(width, height);

Bitmap bitmap = futureTarget.get();

// Do something with the Bitmap and then when you're done with it:
Glide.with(context).clear(futureTarget);
複製代碼

代碼架構

關鍵接口

Glide 源碼中最關鍵的 5 個接口以下:服務器

  1. Request,用於表明一個圖片加載請求,相關的類有 RequestOptions(用於指定圓角、佔位圖等選項)、RequestListener(用於監聽執行結果)、RequestCoordinator(用於協調主圖、縮略圖、出錯圖的加載工做)、RequestManager(用於在生命週期回調時控制圖片加載請求的執行)等markdown

  2. DataFecher,用於獲取數據,數據源多是本地文件、Asset 文件、服務器文件等網絡

  3. Resource,表明一個具體的圖片資源,好比 Bitmap、Drawable 等架構

  4. ResourceDecoder,用於讀取數據並轉爲對應的資源類型,例如將文件轉化爲 Bitmapapp

  5. Target,圖片加載的目標,好比 ImageViewTarget異步

緩存接口

緩存相關的接口以下:socket

  1. MemoryCache,內存緩存,使用 LruCache 實現
  2. DiskCache,本地文件緩存,使用 DiskLruCache 實現
  3. BitmapPool,用於緩存 Bitmap 對象
  4. ArrayPool,用於緩存數據對象
  5. Encoder,用於將數據持久化,例如將 Bitmap 寫到本地文件

輔助接口

輔助接口以下:

  1. LifeCycleListener,用於監聽 Activity/Fragment 的生命週期,以便 Target 控制動畫效果、清除資源
  2. ConnectivityListener,用於監聽設備網絡狀況,以執行對應的操做,好比從新開始加載圖片
  3. Transformation,用於執行圓角、旋轉等圖片變換操做,須要注意的是,這些變換不會應用到佔位符中
  4. Transition,用於執行過渡動畫效果
  5. ResourceTranscoder,用於將轉換資源類型,好比將 Bitmap 轉換爲 Drawable

其它

還有一些重要的類以下:

  1. Glide,用於提供統一的對外接口
  2. Engine,用於統籌全部的圖片加載工做,相關的類有 EngineJob、EngineKey 等
  3. MemorySizeCalculator,用於根據設備分辨率計算內存緩存、BitmapPool、ArrayPool 的大小

所以,一個完整的圖片加載流程大體以下:

圖片加載流程

添加 Fragment,監聽生命週期

方法 Glide.with 用於返回一個 RequestManager,同時添加一個 Fragment 到對應的 Activity/Fragment 上,以監聽生命週期:

public class RequestManagerRetriever implements Handler.Callback {

    static final String FRAGMENT_TAG = "com.bumptech.glide.manager";

    @NonNull
    private RequestManagerFragment getRequestManagerFragment(...) {
        RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
        if (current == null) {
            // 建立 Fragment,並添加到當前 Activity/Fragment 上
            current = new RequestManagerFragment();
            fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
        }
        return current;
    }
}
複製代碼

這樣,RequestManager 就能在 Activity/Fragment 生命週期回調時執行相應的操做,好比暫停/恢復執行網絡加載請求、清理資源等:

public class RequestManager implements LifecycleListener {
    @Override
    public synchronized void onStart() {
        resumeRequests(); // 恢復加載
    }

    @Override
    public synchronized void onStop() { 
        pauseRequests(); // 暫停加載
    }

    @Override
    public synchronized void onDestroy() {
        // 清除資源
        for (Target<?> target : targetTracker.getAll()) {
            clear(target);
        }
        requestTracker.clearRequests();
        lifecycle.removeListener(this);
    }
}
複製代碼

協調圖層

RequestManager 的 load 方法用於返回一個 RequestBuilder,在執行 into 方法時才正式構建請求並執行。

由於一個圖片加載請求可能附加了縮略圖或出錯時顯示的圖片,爲了協調多張圖片請求,Glide 將加載請求分爲 SingleRequest 、ErrorRequestCoordinator、ThumbnailRequestCoordinator 三種。

協調的方法很簡單:

  1. 縮略圖和主圖同時加載,若是主圖先加載完成,則取消縮略圖的加載請求,不然先顯示縮略圖,等主圖加載完成後再清除
  2. 對於出錯圖,則是在主圖加載失敗以後,纔開始加載並顯示

其中,縮略圖、錯圖都是一個獨立的 SingleRequest,都有一套完整的加載流程,而且在加載完成後將資源發送給 Target。

爲何不須要協調佔位圖?由於佔位圖是直接在主線程中從 Android Resource 中加載的,而且第一時間就會加載並顯示,所以不須要協調。

獲取目標尺寸,設置佔位圖

圖片的加載流程是從 Request 的 begin 方法開始的,在正式加載數據以前,Glide 須要獲取 Target 的尺寸,以便在加載完成以後對圖片進行縮放。此外,佔位圖也是在這時設置的:

public final class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback {

    @Override
    public void begin() {
        status = Status.WAITING_FOR_SIZE;
        // 獲取 Target 的尺寸,獲取完成後回調 SizeReadyCallback
        target.getSize(this);
        // 設置佔位圖
        target.onLoadStarted(getPlaceholderDrawable());
    }
}
複製代碼

以 ViewTarget 爲例,它是經過監聽 ViewTreeObserver 來拿到自身寬高的:

public abstract class ViewTarget<T extends View, Z> extends BaseTarget<Z> {

    void getSize(@NonNull SizeReadyCallback cb) {
        cbs.add(cb);
        // 在 View 即將被渲染時回調
        ViewTreeObserver observer = view.getViewTreeObserver();
        observer.addOnPreDrawListener(layoutListener);
    }

    // 獲取尺寸並回調
    void onPreDraw() {
        int currentWidth = getTargetWidth();
        int currentHeight = getTargetHeight();
        notifyCbs(currentWidth, currentHeight);
    }
}
複製代碼

獲取到尺寸以後,就能夠正式執行加載操做了:

@Override
public void onSizeReady(int width, int height) {
    status = Status.RUNNING;
    // 加載
    engine.load(...);
}
複製代碼

內存緩存

加載的第一步是嘗試從內存緩存中獲取:

public class Engine {

    public <R> LoadStatus load(...) {
        EngineKey key = keyFactory.buildKey(...);

        // 檢查內存緩存
        EngineResource<?> memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
        if (memoryResource != null) { // 若是能找到,直接返回便可
            cb.onResourceReady(...);
            return null
        }

        // 不然建立新的加載工做
        return waitForExistingOrStartNewJob(...);
    }
}
複製代碼

Glide 的內存緩存包括兩部分:

  1. 活躍的資源,即加載完成後回調給 Target 而且未被釋放的資源,能夠簡單理解爲正在顯示的圖片
  2. 內存緩存,即再也不顯示,但仍處於內存中未被 LRU 算法移除的資源
@Nullable
private EngineResource<?> loadFromMemory(...) {
    // 活躍的資源
    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
        return active;
    }

    // 內存緩存
    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
        return cached;
    }

    return null;
}
複製代碼

檢查是否有相同的工做正在執行

Glide 使用 Job 表明一個圖片加載工做,在建立新的 Job 對象以前,須要先檢查是否有相同的工做正在執行,若是有,則添加回調並返回,不然建立新的 Job 對象並執行:

private <R> LoadStatus waitForExistingOrStartNewJob(...) {
    // 檢查是否有正在執行的相同的工做
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) { // 若是有,則添加回調並返回
        current.addCallback(cb, callbackExecutor);
        return new LoadStatus(cb, current);
    }

    // 不然建立新的加載工做並執行
    EngineJob<R> engineJob = engineJobFactory.build(...);
    // 執行
    engineJob.start(decodeJob);
    return new LoadStatus(cb, engineJob);
}
複製代碼

磁盤緩存

圖片加載的第二步是嘗試從磁盤緩存中獲取數據,Glide 的磁盤緩存分爲兩類:

  1. ResourceCache,指執行了縮放、變換等操做後的圖片
  2. DataCache,指原始圖片緩存

Glide 首先會從縮放、變換後的文件緩存中查找,若是找不到,再到原始的圖片緩存中查找。

以 ResourceCache 爲例,它首先會經過 DiskCache 獲取文件:

class ResourceCacheGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object> {

    @Override
    public boolean startNext() {
        currentKey = new ResourceCacheKey(...);
        // 獲取文件
        cacheFile = helper.getDiskCache().get(currentKey);
        // 獲取數據
        loadData.fetcher.loadData(helper.getPriority(), this);

        return started;
    }
}
複製代碼

接着再經過 DataFetcher 獲取數據:

private static final class FileFetcher<Data> implements DataFetcher<Data> {

    @Override
    public void loadData(...) {
        try {
            data = new InputStream(file);
            callback.onDataReady(data);
        } catch (FileNotFoundException e) {
            callback.onLoadFailed(e);
        }
    }
}
複製代碼

獲取服務器數據

若是磁盤緩存中找不到對應的圖片,則須要到服務器中獲取數據。以 OkHttp 爲例:

public class OkHttpStreamFetcher implements DataFetcher<InputStream>, okhttp3.Callback {
    @Override
    public void loadData(...) {
    	// 執行網絡請求
        Request request = new Request.Builder().url(url.toStringUrl()).build;
        call = client.newCall(request);
        call.enqueue(this);
    }

    @Override
    public void onResponse(@NonNull Call call, @NonNull Response response) {
    	// 獲取 socket I/O 流
        responseBody = response.body();
        if (response.isSuccessful()) {
            long contentLength = responseBody.contentLength();
            stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
            callback.onDataReady(stream);
        } else {
            callback.onLoadFailed(new HttpException(response.message(), response.code()));
        }
    }
}
複製代碼

緩存原始數據

成功獲取到數據以後,DataFetcher 的 Callback 就會將原始數據回調給 SourceGenerator,並緩存到磁盤文件中:

class SourceGenerator implements DataFetcherGenerator, DataFetcherGenerator.FetcherReadyCallback {

    void onDataReadyInternal(LoadData<?> loadData, Object data) {
        // 根據緩存策略判斷是否能夠緩存
        DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
        if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
            dataToCache = data;
            cb.reschedule();
        }
    }

    @Override
    public boolean startNext() {
        if (dataToCache != null) {
            Object data = dataToCache;
            dataToCache = null;
            cacheData(data);
        }
        ...
    }

    // 寫入到磁盤
    private void cacheData(Object dataToCache) {
        helper.getDiskCache().put(originalKey, writer);
    }
}
複製代碼

解碼

從磁盤緩存/遠程服務器中獲取到的是數據流,還須要解碼爲 Bitmap、Gif 等資源類型。這個工做是 ResourceDecoder 來完成的:

public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {
    @Override
    public Resource<Bitmap> decode(...) {
        return downsampler.decode(...); // 縮放
    }
}
複製代碼
public final class Downsampler {

    private Resource<Bitmap> decode(...) {
        Bitmap result = decodeFromWrappedStreams(...);
        return BitmapResource.obtain(result, bitmapPool);
    }
}
複製代碼

在解碼過程當中,若是檢查到設備支持,則使用 GPU 存儲 Bitmap 數據:

boolean setHardwareConfigIfAllowed(...) {
    boolean result = isHardwareConfigAllowed(...);
    if (result) {
        optionsWithScaling.inPreferredConfig = Bitmap.Config.HARDWARE;
        optionsWithScaling.inMutable = false;
    }
    return result;
}
複製代碼

不然使用 ARGB_8888 存儲:

public enum DecodeFormat {
    PREFER_ARGB_8888,

    PREFER_RGB_565;

    public static final DecodeFormat DEFAULT = PREFER_ARGB_8888;
}
複製代碼

值得注意的是,此時 Glide 會判斷數據的來源,若是是否是從變換後的文件緩存中獲取的,就須要將圓角、旋轉等效果應用到 Bitmap 上:

class DecodeJob<R> {

    <Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {
        Resource<Z> transformed = decoded;
        if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
            appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
            transformed = appliedTransformation.transform(glideContext, decoded, width, height);
        }
        return result;
    }
}
複製代碼

收尾、顯示

解碼完成以後,首先會將結果回調給 Engine、Target 等對象,接着將變換後的圖片數據寫入到磁盤緩存,最後清理資源:

class DecodeJob<R> {

    private void decodeFromRetrievedData() {
        Resource<R> resource = decodeFromData(currentFetcher, currentData, currentDataSource);
        notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
    }

    private void notifyEncodeAndRelease(...) {
        // 將結果回調給 Engine、SingleRequest 等對象
        notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey);
        // 將數據寫入到磁盤緩存
        deferredEncodeManager.encode(diskCacheProvider, options);
        // 清理資源
        onEncodeComplete();
    }

    private static class DeferredEncodeManager<Z> {

        void encode(DiskCacheProvider diskCacheProvider, Options options) {
            // 這裏緩存的是變換後的圖片數據
            diskCacheProvider
                .getDiskCache()
                .put(key, new DataCacheWriter<>(encoder, toEncode, options));
        }
    }
}
複製代碼

Engine 收到回調後會使用 ActiveResources 記錄該資源,並移除當前加載工做:

public class Engine {

    @Override
    public synchronized void onEngineJobComplete(...) {
        activeResources.activate(key, resource);
        jobs.removeIfCurrent(key, engineJob);
    }
}
複製代碼

以 ImageView 爲例,Target 收到回調後首先判斷是否須要執行過渡動畫,接着將圖片資源設置到 View 裏面,最後判斷 Resource 是否爲 gif 動畫,若是是,則開始執行動畫:

public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z> {

    @Override
    public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
        if (transition == null || !transition.transition(resource, this)) { // 若是沒有過渡動畫,則直接設置圖片資源
            setResourceInternal(resource);
        } else { // 不然執行動畫
            maybeUpdateAnimatable(resource);
        }
    }

    private void setResourceInternal(@Nullable Z resource) {
        setResource(resource);
        maybeUpdateAnimatable(resource);
    }

    private void maybeUpdateAnimatable(@Nullable Z resource) {
        if (resource instanceof Animatable) { // gif 動畫
            animatable = (Animatable) resource;
            animatable.start();
        } else {
            animatable = null;
        }
    }

    protected abstract void setResource(@Nullable Z resource);
}
複製代碼

緩存 & 資源重用機制

分配內存

在使用內存緩存、對象池以前,Glide 首先會根據設備狀況分配對應的內存:

public final class GlideBuilder {

    Glide build(@NonNull Context context) {
        bitmapPool = new LruBitmapPool(memorySizeCalculator.getBitmapPoolSize());
        arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
        memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }
}
複製代碼

能夠看到,具體的內存大小是 MemorySizeCaculator 計算的。

對於 ArrayPool,若是是低內存設備,則默認分配 2MB,不然分配 4MB:

public final class MemorySizeCalculator {

    private static final int LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR = 2;

    MemorySizeCalculator(MemorySizeCalculator.Builder builder) {

        arrayPoolSize = isLowMemoryDevice(builder.activityManager)
                ? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
                : builder.arrayPoolSizeBytes;
    }

    static boolean isLowMemoryDevice(ActivityManager activityManager) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            return activityManager.isLowRamDevice();
        } else {
            return true;
        }
    }

    public static final class Builder {
        // 4MB.
        static final int ARRAY_POOL_SIZE_BYTES = 4 * 1024 * 1024;

        int arrayPoolSizeBytes = ARRAY_POOL_SIZE_BYTES;
    }
}
複製代碼

對於 Bitmap 對象池,由於 Glide 在 API 26 以上統一使用硬件(GPU)存儲 Bitmap,所以對內存的需求小了不少,只須要一個屏幕分辨率(ARGB),對於 1920x1080 的手機,大約是 8MB,而在 API 26 以前,則須要 4 個屏幕分辨率,也就是 32 MB。

static final int BITMAP_POOL_TARGET_SCREENS =
    Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 4 : 1;

int widthPixels = builder.screenDimensions.getWidthPixels();
int heightPixels = builder.screenDimensions.getHeightPixels();
int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;

int targetBitmapPoolSize = Math.round(screenSize * BITMAP_POOL_TARGET_SCREENS);
複製代碼

內存緩存則默認使用 2 個屏幕分辨率,對於 1920x1080 的手機,大約是 16MB:

static final int MEMORY_CACHE_TARGET_SCREENS = 2;
int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
複製代碼

另外,Bitmap 對象池和內存緩存受到應用最大可用緩存的限制:

public class ActivityManager {

    public int getMemoryClass() {
        return staticGetMemoryClass();
    }
}
複製代碼

但如今的手機性能已經很高了,這個值通常比較大,即便 Glide 會在此基礎之上乘以一個係數(0.4 或 0.33),也基本夠用,所以就不考慮了。

重用 Bitmap

BitmapPool 使用 LRU 算法實現,主要接口以下:

public interface BitmapPool {

    void put(Bitmap bitmap);

    // 返回寬高、config 如出一轍的對象,同時將像素數據清除,若是找不到,就分配一個新的
    Bitmap get(int width, int height, Bitmap.Config config);

    // 返回寬高、config 如出一轍的對象,若是找不到,就分配一個新的
    Bitmap getDirty(int width, int height, Bitmap.Config config);

    // 清除全部對象,在設備內存低時(onLowMemory)調用
    void clearMemory();

    // 根據內存狀況選擇移除一半或全部對象
    void trimMemory(int level);
}
複製代碼

在回收 Bitmap 對象時就會將它移入對象池中:

public class BitmapResource implements Resource<Bitmap>, Initializable {
    @Override
    public void recycle() {
        bitmapPool.put(bitmap);
    }
}
複製代碼

重用時機主要是在執行變換效果時:

public final class TransformationUtils {
    public static Bitmap centerCrop(...) {
        ...
        Bitmap result = pool.get(width, height, getNonNullConfig(inBitmap));
        applyMatrix(inBitmap, result, m);
        return result;
    }
}
複製代碼

重用活躍的資源

活躍的資源指已加載完成,而且未被釋放的資源:

public class Engine {

    private final ActiveResources activeResources;

    @Override
    public synchronized void onEngineJobComplete(...) {
        activeResources.activate(key, resource);
    }

    @Override
    public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
        activeResources.deactivate(cacheKey);
    }
}
複製代碼

釋放的時機主要有三個:

  1. 生命週期方法 onStop 回調
  2. 生命週期方法 onDestroy 回調
  3. onTrimMemory 回調,級別爲 MODERATE

此外,縮略圖會在主圖加載完成後釋放。所以,活躍的資源能夠簡單理解爲正在顯示的資源。

這些資源使用引用計數的方式管理,計數爲 0 時釋放:

class EngineResource<Z> implements Resource<Z> {

    private int acquired;

    synchronized void acquire() {
        ++acquired;
    }

    void release() {
        boolean release = false;
        synchronized (this) {
            if (--acquired == 0) {
                release = true;
            }
        }
        if (release) {
            listener.onResourceReleased(key, this);
        }
    }
}
複製代碼

內存緩存

MemoryCache 的接口和 BitmapPool、ArrayPool 基本同樣:

public interface MemoryCache {

    Resource<?> remove(@NonNull Key key);

    Resource<?> put(@NonNull Key key, @Nullable Resource<?> resource);

    // 移除全部對象,在設備內存低時(onLowMemory)調用
    void clearMemory();

    // 根據內存狀況選擇移除一半或全部對象
    void trimMemory(int level);
}
複製代碼

在資源釋放時,若是可使用內存緩存(可經過 RequestOptions 配置,默承認以),則緩存,不然回收:

public class Engine {

    @Override
    public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
        activeResources.deactivate(cacheKey);
        if (resource.isMemoryCacheable()) {
            cache.put(cacheKey, resource);
        } else {
            resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
        }
    }
}
複製代碼

回收時會根據資源類型執行不一樣的操做,若是是 Bitmap,則移入 Bitmap 對象池中,若是不是,要麼清除資源,要麼什麼都不作。

內存緩存使用的 Key 和活躍資源同樣,都是 EngineKey,主要根據圖片寬高、變換信息、資源類型信息來區分:

class EngineKey implements Key {
    private final int width;
    private final int height;
    private final Class<?> resourceClass;
    private final Class<?> transcodeClass;
    private final Map<Class<?>, Transformation<?>> transformations;
}
複製代碼

磁盤緩存

DiskCache 主要提供了三個接口:

public interface DiskCache {

    interface Factory {
        /**
         * 默認緩存大小爲 250 MB
         */
        int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;

        /**
         * 默認緩存文件夾
         */
        String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";

        DiskCache build();
    }

    File get(Key key);

    void put(Key key, Writer writer);

    void clear();
}
複製代碼

其中,緩存的文件分爲兩種:

  1. 原始圖片
  2. 變換後的圖片

具體的文件寫入操做是由 Encoder 完成的,以 Bitmap 爲例,緩存 Bitmap 時經過 compress 方法寫入到文件便可:

public class BitmapEncoder implements ResourceEncoder<Bitmap> {

    @Override
    public boolean encode(...) {
        final Bitmap bitmap = resource.get();
        boolean success = false;
        OutputStream os = new FileOutputStream(file);
        bitmap.compress(format, quality, os);
        os.close();
    }
}
複製代碼

若是須要清除文件緩存,能夠調用 Glide 的 tearDown 方法:

public class Glide {

    public static void tearDown() {
        glide.engine.shutdown();
    }
}
複製代碼
public class Engine {
    public void shutdown() {
        diskCacheProvider.clearDiskCacheIfCreated();
    }

    private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {
        synchronized void clearDiskCacheIfCreated() {
            diskCache.clear();
        }
    }
}
複製代碼

磁盤緩存使用的 Key 能夠分爲三類:

  1. GlideUrl,在獲取遠程圖片數據的時候使用,主要根據 url 地址和 headers 信息來區分
  2. ObjectKey,在獲取本地圖片數據的時候使用,主要根據 Uri、File 來區分
  3. ResourceCacheKey,在獲取變換後的圖片緩存時使用,主要根據圖片寬高、變換信息、資源類型信息來區分

線程池

Glide 主要分了三個線程池:

  1. 用於獲取數據,線程數爲 CPU 個數,但不大於 4 個
  2. 用於執行讀寫緩存,線程數爲 1

) 用於執行 gif 動畫,在 CPU 個數大於等於 4 時,線程數爲 2,不然爲 1

public final class GlideBuilder {
    private GlideExecutor sourceExecutor;
    private GlideExecutor diskCacheExecutor;
    private GlideExecutor animationExecutor;

    Glide build(@NonNull Context context) {
        sourceExecutor = GlideExecutor.newSourceExecutor();
        diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
        animationExecutor = GlideExecutor.newAnimationExecutor();
    }
}
複製代碼

總結

圖片加載流程

總的來講,圖片加載流程大體可分爲 4 步:

  1. 準備工做
  2. 內存緩存
  3. 數據加載
  4. 解碼顯示

準備工做包括:

  1. 添加一個 Fragment 到對應的 Activity/Fragment 上面,監聽生命週期。以控制請求的暫停執行、恢復執行,或清理資源
  2. 遞歸構建 Request。以協調主圖、縮略圖、出錯圖的加載請求。縮略圖和主圖同時加載,若是主圖先加載完成,則取消縮略圖的加載請求,不然先顯示縮略圖,等主圖加載完成後再清除。出錯圖則是在主圖加載失敗後纔開始加載並顯示
  3. 獲取目標尺寸。以 ImageView 爲例,尺寸是在 ViewTreeObserver 的 onPreDraw 方法回調後獲取的。
  4. 設置佔位圖。佔位圖是直接調用 setImageDrawable 方法設置的,所以不須要和主圖協調

內存緩存包括兩部分:

  1. 活躍的資源,即加載完成後已回調給 Target 而且未被釋放的資源,能夠簡單理解爲正在顯示的圖片,經過引用計數判斷具體釋放的時機
  2. 內存緩存,即再也不顯示,但仍處於內存中未被 LRU 算法移除的資源

數據加載包括兩部分:

  1. 從磁盤緩存中加載
  2. 從目標地址中加載

磁盤緩存又分爲兩部分:

  1. 從縮放、變換後的文件緩存中查找
  2. 若是找不到,再到原始的圖片緩存中查找

目標地址有不少種,好比本地文件路徑、文件句柄、Asset、服務器連接等。

成功獲取到數據以後:

  1. 首先須要將原始圖像數據緩存到本地磁盤中
  2. 接着將數據解碼爲 Bitmap/Drawable 等資源類型,同時應用圓角等變換效果(若是不是從變換後的文件緩存中獲取的話)
  3. 而後才能回調給 Target 並顯示
  4. 最後還須要將縮放、變換後的圖片數據寫入到磁盤緩存,並清理資源

Target 顯示圖片時還會判斷是否須要執行過渡動畫,若是須要,則執行過渡動畫後再顯示資源。顯示資源時,會判斷該資源類型是否爲 gif 圖,若是是,則開始執行動畫,不然直接顯示便可。

緩存、資源重用機制

Glide 的緩存機制主要分爲三部分:

  1. 資源重用(數組對象、Bitmap 對象、活躍資源)
  2. 內存緩存
  3. 磁盤緩存(原始圖片數據、應用變換後的圖片數據)

其中,活躍資源使用引用計數的方式來判斷釋放的時機,除此以外的其它緩存機制都是經過 LRU 算法來管理數據的。

活躍資源指已加載完成,而且未被釋放的資源,能夠簡單地理解爲正在顯示的資源。

資源釋放的時機主要有三個:

  1. 生命週期方法 onStop 回調
  2. 生命週期方法 onDestroy 回調
  3. onTrimMemory 回調,級別爲 MODERATE

資源釋放時,首先會判斷是否可使用內存緩存(可經過 RequestOptions 配置,默承認以),若是能夠,則使用內存緩存資源,不然回收資源。回收時會根據資源類型執行不一樣的操做,若是是 Bitmap,則移入 Bitmap 對象池中,若是不是,要麼清除資源,要麼什麼都不作。

內存/磁盤分配:

  1. 數組對象池,若是是低內存設備,則默認分配 2MB,不然分配 4MB
  2. Bitmap 對象池,API 26 以前分配四個屏幕分辨率(1920x1080 的手機大約是 32MB),API 26 以後統一使用 GPU 存儲 Bitmap,所以只須要一個屏幕分辨率(1920x1080 的手機大約是 8MB)
  3. 內存緩存,默認使用 2 個屏幕分辨率(1920x1080 的手機大約是 16MB)
  4. 磁盤緩存默認最多緩存 250MB 數據

另外,Bitmap 對象池和內存緩存受到應用最大可用緩存的限制(在此基礎之上乘以係數 0.4 或 0.33),若是不夠,則按比例分配。

線程池主要分了三個:

  1. 用於獲取數據,線程數爲 CPU 個數,但不大於 4 個
  2. 用於執行讀寫緩存,線程數爲 1
  3. 用於執行 gif 動畫,在 CPU 個數大於等於 4 時,線程數爲 2,不然爲 1
相關文章
相關標籤/搜索