Glide源碼分析

原文鏈接:https://juejin.im/post/5af95dc66fb9a07aad17a4c3

在早期的Android開發中,圖片加載其實一直是個比較麻煩的問題。我們在處理圖片時會遇到各種各樣的問題:內存溢出、列表中圖片錯位等等。但到了如今,這些問題基本上是不會再遇到了。由於很多的優秀的圖片加載框架幫我們處理了圖片相關問題的痛點,所以現在Android中關於圖片加載的部分變得非常簡單。Android中最著名的圖片加載框架就是Glide了,我們今天來深入研究一下Glide的源碼。

使用方法

以Glide3.8.0版本來分析,我們先看下最常見使用方法:

Glide.with(fragment)
    .load(myUrl)
    .into(imageView);

上面的代碼是我們非常熟悉的Glide的基本用法,分爲3個步驟:

  • with(context)
  • load(url)
  • into(target) 在瞭解到Glide的3個入口方法之後,我會按照這3個方法來進行源碼的分析

with(context)方法

看一下with(context)d的源碼:

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

public static RequestManager with(Activity activity) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(activity);
}

public static RequestManager with(FragmentActivity activity) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(activity);
}

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方法有很多,但內容基本一致,都是通過 RequestManagerRetriever.get(); 獲取一個 RequestManagerRetriever 對象 retriever ,然後通過 retriever.get(context); 獲取一個 RequestManager 對象並返回。這些with方法關鍵的不同在於傳入的參數不一致,可以是Context、Activity、Fragment等等。那麼爲什麼要分這麼多種呢?其實我們應該都知道:Glide在加載圖片的時候會綁定 with(context) 方法中傳入的 context 的生命週期,如果傳入的是 Activity ,那麼在這個 Activity 銷燬的時候Glide會停止圖片的加載。這樣做的好處是顯而易見的:避免了消耗多餘的資源,也避免了在Activity銷燬之後加載圖片從而導致的空指針問題。

爲了更好的分析 with(context) 中的這兩步,我們來看一下 RequestManagerRetriever

public class RequestManagerRetriever implements Handler.Callback {
    //餓漢式創建單例
    private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever();
    
    //返回單例對象
    public static RequestManagerRetriever get() {
        return INSTANCE;
    }

    //根據傳入的參數,獲取不同的RequestManager
    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);
    }
    
    //省略無關代碼......
}

很明顯,這是個餓漢式的單例模式。關鍵在於 retriever.get(context),我們繼續看代碼:

//根據傳入的參數,獲取不同的RequestManager
public RequestManager get(Context context) {
    //context爲null則拋出異常
    if (context == null) {
        throw new IllegalArgumentException("You cannot start a load on a null Context");
    } else if (Util.isOnMainThread() && !(context instanceof Application)) {
        //當前線程是主線程並且此context並不是Application的實例,根據context的類型做不同的處理
        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);
}

上面這個方法主要是通過傳入context的不同類型來做不同的操作。context可以是Application、FragmentActivity、Activity或者是ContextWrapper。我們先看一下當context是Application時的操作:

private RequestManager getApplicationManager(Context context) {
    // 返回一個單例
    if (applicationManager == null) {
        synchronized (this) {
            if (applicationManager == null) {
                applicationManager = new RequestManager(context.getApplicationContext(),
                        new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
            }
        }
    }

    return applicationManager;
}

代碼應該都能看懂, getApplicationManager(Context context) 通過單例模式創建並返回了 applicationManager 。我們再來看一下如果傳入的context是Activity時的操作:

public RequestManager get(Activity activity) {
    //如果不在主線程或者Android SDK的版本低於HONEYCOMB,傳入的還是Application類型的context
    if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        return get(activity.getApplicationContext());
    } else {
        //判斷當前activity是否被銷燬
        assertNotDestroyed(activity);
        android.app.FragmentManager fm = activity.getFragmentManager();
       //通過fragmentGet(activity, fm)獲取RequestManager
        return fragmentGet(activity, fm);
    }
}

代碼邏輯很簡單:如果不在主線程或者Android SDK版本過低,走的還是傳入Application的方法,這個方法在上面提到過;反之,首先判斷當前activity是否被銷燬,如果沒有被銷燬,則通過fragmentGet(activity, fm)獲取RequestManager。關鍵是這個 fragmentGet(activity, fm) ,我們來看一下:

RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
    //在當前activity中創建一個沒有界面的的fragment並add到當前activity中
    RequestManagerFragment current = getRequestManagerFragment(fm);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
        //創建一個requestManager
        requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
        //將requestManager與fragment綁定        
        current.setRequestManager(requestManager);
    }
    return requestManager;
}

fragmentGet 這個方法主要是在當前activity中創建一個沒有界面的的fragment並add到當前activity中,以此來實現對activity生命週期的監聽。到此, with 方法已經基本介紹完畢了,做一下總結:

  • 通過RequestManagerRetriever的get獲取RequestManagerRetriever單例對象
  • 通過retriever.get(context)獲取RequestManager,在get(context)方法中通過對context類型的判斷做不同的處理:
    • context是Application,通過getApplicationManager(Context context) 創建並返回一個RequestManager對象
    • context是Activity,通過fragmentGet(activity, fm)在當前activity創建並添加一個沒有界面的fragment,從而實現圖片加載與activity的生命週期相綁定,之後創建並返回一個RequestManager對象

load(url)方法

with(context)返回一個RequestManager,接下來我們看一下RequestManger中的load(url)方法:

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

這個方法分兩步:fromString()、load(string),先看第一個方法:

public DrawableTypeRequest<String> fromString() {
        return loadGeneric(String.class);
    }
複製代碼

這個方法返回的是 loadGeneric(String.class) ,我們跟進去:

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

        //這句是核心,本質是創建並返回了一個DrawableTypeRequest
        return optionsApplier.apply(
                new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
                        glide, requestTracker, lifecycle, optionsApplier));
    }

loadGeneric(Class modelClass) 方法中,我們只需要關注核心即可。它的核心是最後一句,方法調用看着很複雜,其實本質是創建並返回了一個DrawableTypeRequest,Drawable類型的請求。再來看 load(string) 方法:

@Override
    public DrawableRequestBuilder<ModelType> load(ModelType model) {
        super.load(model);
        return this;
    }

需要注意的是這個方法存在於DrawableTypeRequest的父類DrawableRequestBuilder中,這個方法首先調用DrawableRequestBuilder的父類的load方法,然後返回自身。再看一下DrawableRequestBuilder父類中的load方法:

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
        this.model = model;
        isModelSet = true;
        return this;
    }

DrawableRequestBuilder的父類是GenericRequestBuilder,從名字中我們也可以看出來,前者是Drawable請求的構建者,後者是通用的請求構建者,他們是子父關係。這個load方法其實是把我們傳入的String類型的URL存入了內部的model成員變量中,再將數據來源是否已經設置的標誌位 isModelSet 設置爲true,意味着我們在調用 Glide.with(context).load(url) 之後數據來源已經設置成功了。

說到這裏,其實Glide中的load(url)基本已經結束了,小夥伴們可能會有問題要問:我平時使用Glide會加一些配置,比如:

Glide.with(context)
    .load(url)
    .placeholder(R.drawable.place_image)
    .error(R.drawable.error_image)
    .into(imageView);

其實大家在寫的時候是會有一種感覺的,這種寫法很像Builder模式。沒錯,這就是一個Builder模式。經過上面的分析我們知道,在 Glide.with(context).load(url) 之後會返回一個DrawableTypeRequest的對象,它的父類是DrawableRequestBuilder,DrawableRequestBuilder的父類是GenericRequestBuilder,我們寫的placeHolder()、error()等等相關圖片請求配置的方法都定義在GenericRequestBuilder中,我們來簡單的看一下:

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> placeholder(
        int resourceId) {
    this.placeholderId = resourceId;

    return this;
}

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> error(
        int resourceId) {
    this.errorId = resourceId;

    return this;
}

是不是一下子就明白了,我們平時對圖片請求的配置使用的就是Builder模式。

into(imageView)方法

簡單的說,Glide中的前兩步是創建了一個Request,這個Request可以理解爲對圖片加載的配置請求,需要注意的是僅僅是創建了一個 請求 ,而並沒有去執行。在Glide的最後一步into方法中,這個請求才會真實的執行。

我們來DrawableTypeRequest中找一下into方法,發現沒找到,那肯定是在他的父類DrawableRequestBuilder中,我們來看一下DrawableRequestBuilder中的into方法:

public Target<GlideDrawable> into(ImageView view) {
        return super.into(view);
    }

嗯,它調用的是父類GenericRequestBuilder的方法,那我們繼續看GenericRequestBuilder的into方法:

public Target<TranscodeType> into(ImageView view) {
        //確保在主線程
        Util.assertMainThread();
        //確保view不爲空
        if (view == null) {
            throw new IllegalArgumentException("You must pass in a non null View");
        }
        //對ScaleType進行配置
        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));
    }

可以看到,上面的方法就是into的核心代碼了,它定義在GenericRequestBuilder這個通用的請求構建者中。方法的核心是最後一行: into(glide.buildImageViewTarget(view, transcodeClass)) ,首先是通過 glide.buildImageViewTarget(view, transcodeClass) 創建出一個 Target 類型的對象,然後把這個target傳入GenericRequestBuilder中的into方法中。我們先來看一下Glide中的 buildImageViewTarget(view, transcodeClass) 方法:

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

這個方法的目的是把我們傳入的imageView包裝成一個Target。內部調用了 imageViewTargetFactory.buildTarget(imageView, transcodedClass) 繼續跟進去看一下:

public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
        //圖片來源是GlideDrawable
        if (GlideDrawable.class.isAssignableFrom(clazz)) {
            //創建GlideDrawable對應的target
            return (Target<Z>) new GlideDrawableImageViewTarget(view);
        } else if (Bitmap.class.equals(clazz)) {
            //如果圖片來源是Bitmap,創建Bitmap對應的target
            return (Target<Z>) new BitmapImageViewTarget(view);
        } else if (Drawable.class.isAssignableFrom(clazz)) {
            //如果圖片來源是Drawable,創建Drawable對應的target
            return (Target<Z>) new DrawableImageViewTarget(view);
        } else {
            throw new IllegalArgumentException("Unhandled class: " + clazz
                    + ", try .as*(Class).transcode(ResourceTranscoder)");
        }
    }

這個方法的的本質是:通過對圖片來源類型的判斷,創建並返回與圖片來源對應的imageViewTarget。獲取到相應的target之後,我們來看GenericRequestBuilder中的into方法:

public <Y extends Target<TranscodeType>> Y into(Y target) {
        //確保在主線程
        Util.assertMainThread();
        //確保target不爲空
        if (target == null) {
            throw new IllegalArgumentException("You must pass in a non null Target");
        }
        //確保數據來源已經確定,即已經調用了load(url)方法
        if (!isModelSet) {
            throw new IllegalArgumentException("You must first set a model (try #load())");
        }

        //獲取當前target已經綁定的Request對象
        Request previous = target.getRequest();

        //如果當前target已經綁定了Request對象,則清空這個Request對象
        if (previous != null) {
            previous.clear();
            //停止綁定到當前target的上一個Request的圖片請求處理
            requestTracker.removeRequest(previous);
            previous.recycle();
        }
        
        //創建Request對象
        Request request = buildRequest(target);
        //與target綁定
        target.setRequest(request);
        lifecycle.addListener(target);
        //執行request
        requestTracker.runRequest(request);

        return target;
    }

我們梳理一下方法中的邏輯:

  • 獲取當前target中的Request對象,如果存在,則清空並終止這個Request對象的執行
  • 創建新的Request對象並與當前target綁定
  • 執行新創建的圖片處理請求Request 邏輯還是比較清晰的,這裏有一個問題需要說明一下。爲什麼要終止並清除target之前綁定的請求呢?

在沒有Glide之前,我們處理ListView中的圖片加載其實是一件比較麻煩的事情。由於ListView中Item的複用機制,會導致網絡圖片加載的錯位或者閃爍。那我們解決這個問題的辦法也很簡單,就是給當前的ImageView設置tag,這個tag可以是圖片的URL等等。當從網絡中獲取到圖片時判斷這個ImageVIew中的tag是否是這個圖片的URL,如果是就加載圖片,如果不是則跳過。

在有了Glide之後,我們處理ListView或者Recyclerview中的圖片加載就很無腦了,根本不需要作任何多餘的操作,直接正常使用就行了。這其中的原理是Glide給我們處理了這些判斷,我們來看一下Glide內部是如何處理的:

public Request getRequest() {
    //本質還是getTag
    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;
}

@Override
public void setRequest(Request request) {
    //本質是setTag
    setTag(request);
}

可以看到, target.getRequest()target.setRequest(Request request) 本質上還是通過setTag和getTag來做的處理,這也印證了我們上面所說。

繼續回到into方法中,在創建並綁定了Request後,關鍵的就是 requestTracker.runRequest(request) 來執行我們創建的請求了。

public void runRequest(Request request) {
        //將請求加入請求集合
        requests.add(request);
        
        if (!isPaused) {
            如果處於非暫停狀態,開始執行請求
            request.begin();
        } else {
            //如果處於暫停狀態,將請求添加到等待集合
            pendingRequests.add(request);
        }
    }

這個方法定義在 RequestTracker 中,這個類主要負責Request的執行,暫停,取消等等關於圖片請求的操作。我們着重看 request.begin() ,這句代碼意味着開始執行圖片請求的處理。Request是個接口, request.begin() 實際調用的是Request的子類 GenericRequest 的begin方法,我們跟進去看一下:

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

    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        //如果長寬尺寸已經確定
        onSizeReady(overrideWidth, overrideHeight);
    } else {
        //獲取長寬尺寸,獲取完之後會調用onSizeReady(overrideWidth, overrideHeight)
        target.getSize(this);
    }

    if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
        //開始加載圖片,先顯示佔位圖
        target.onLoadStarted(getPlaceholderDrawable());
    }
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }
}

方法的邏輯大致是這樣的:

  • 獲取圖片的長寬尺寸,如果長寬已經確定,走 onSizeReady(overrideWidth, overrideHeight) 流程;如果未確定,先獲取長寬,再走 onSizeReady(overrideWidth, overrideHeight)
  • 圖片開始加載,首先顯示佔位圖 可以明白,主要的邏輯還是在 onSizeReady 這個方法中:
@Override
public void onSizeReady(int width, int height) {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
    if (status != Status.WAITING_FOR_SIZE) {
        return;
    }
    status = Status.RUNNING;

    width = Math.round(sizeMultiplier * width);
    height = Math.round(sizeMultiplier * height);

    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;
    }
    ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    }
    loadedFromMemoryCache = true;
    
    //核心代碼,加載圖片
    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));
    }
}

這段代碼看起來很複雜,我們只需要關注核心代碼: engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, priority, isMemoryCacheable, diskCacheStrategy, this), 我們看一下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();

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

    //使用LruCache獲取緩存
    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);
}

load方法位於 Engine 類中。load方法內部會從三個來源獲取圖片數據,我們最熟悉的就是LruCache了。如何獲取數據過於複雜,這裏就不再展開分析,我們這裏主要關注圖片數據獲取到之後的操作。獲取到圖片數據之後,通過 cb.onResourceReady(cached) 來處理,我們來看一下這個回調的具體實現:

@Override
public void onResourceReady(Resource<?> resource) {
    if (resource == null) {
        onException(new Exception("Expected to receive a Resource<R> with an object of " + transcodeClass
                + " inside, but instead got null."));
        return;
    }

    Object received = resource.get();
    if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
        releaseResource(resource);
        onException(new Exception("Expected to receive an object of " + transcodeClass
                + " but instead got " + (received != null ? received.getClass() : "") + "{" + received + "}"
                + " inside Resource{" + resource + "}."
                + (received != null ? "" : " "
                    + "To indicate failure return a null Resource object, "
                    + "rather than a Resource object containing null data.")
        ));
        return;
    }

    if (!canSetResource()) {
        releaseResource(resource);
        // We can't set the status to complete before asking canSetResource().
        status = Status.COMPLETE;
        return;
    }
    
    //核心是這一句
    onResourceReady(resource, (R) received);
}

我們繼續看 onResourceReady(resource, (R) received) 這個方法:

private void onResourceReady(Resource<?> resource, R result) {
    // We must call isFirstReadyResource before setting status.
    boolean isFirstResource = isFirstReadyResource();
    status = Status.COMPLETE;
    this.resource = resource;

    if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache,
            isFirstResource)) {
        GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource);
        
        //核心,通過調用target的onResourceReady方法加載圖片
        target.onResourceReady(result, animation);
    }

    notifyLoadSuccess();

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: "
                + (resource.getSize() * TO_MEGABYTE) + " fromCache: " + loadedFromMemoryCache);
    }
}

我們可以看到核心代碼:target.onResourceReady(result, animation),其實在這句代碼的內部最終是通過:

public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {
    public DrawableImageViewTarget(ImageView view) {
        super(view);
    }

    @Override
    protected void setResource(Drawable resource) {
       view.setImageDrawable(resource);
    }
}

本質是通過 setResource(Drawable resource) 來實現的,在這個方法的內部調用了Android內部最常用的加載圖片的方法 view.setImageDrawable(resource)

到此爲止,into方法基本已經分析完了,我們忽略了網絡圖片獲取的過程,專注於獲取圖片後的處理。現在來對into方法做個總結:

  • 將imageview包裝成imageViewTarget
  • 清除這個imageViewTarget之前綁定的請求,綁定新的請求
  • 執行新的請求
  • 獲取圖片數據之後,成功則會調用ImageViewTarget中的onResourceReady()方法,失敗則會調用ImageViewTarget中的onLoadFailed();二者的本質都是通過調用Android中的imageView.setImageDrawable(drawable)來實現對imageView的圖片加載

LruCache源碼分析

Glide的基本源碼分析其實到這裏已經結束了,但提起圖片加載,LruCache是一個不可忽視的關鍵點,在Glide源碼分析的最後我們再來分析一下LruCache的源碼,這個LruCache來自於 android.support.v4.util 中:

public class LruCache<K, V> {
    //存儲緩存
    private final LinkedHashMap<K, V> map;
    //當前緩存的總大小
    private int size;
    //最大緩存大小
    private int maxSize;
    //添加到緩存的個數
    private int putCount;
    //創建的個數
    private int createCount;
    //移除的個數
    private int evictionCount;
    //命中個數
    private int hitCount;
    //未命中個數
    private int missCount;

    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

    //重新設置最大緩存
    public void resize(int maxSize) {
        //確保最大緩存大於0
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

        synchronized (this) {
            this.maxSize = maxSize;
        }
        //對當前的緩存做一些操作以適應新的最大緩存大小
        trimToSize(maxSize);
    }

    //獲取緩存
    public final V get(K key) {
        //確保key不爲null
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            //如果可以獲取key對應的value
            if (mapValue != null) {
                //命中數加一
                hitCount++;
                return mapValue;
            }
            //如果根據key獲取的value爲null,未命中數加一
            missCount++;
        }
        
        //省略無關代碼......
    }

    
    public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            //添加到緩存的個數加一
            putCount++;
            //更新當前緩存大小
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                //如果之前map中對應key存在value不爲null,由於重複的key新添加的value會覆蓋上一個value,所以當前緩存大小應該再減去之前value的大小
                size -= safeSizeOf(key, previous);
            }
        }
        //根據緩存最大值調整緩存
        trimToSize(maxSize);
        return previous;
    }

    //根據最大緩存大小對map中的緩存做調整
    public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }
                //當前緩存大小小於最大緩存,或LinkedHashMap爲空時跳出循環
                if (size <= maxSize || map.isEmpty()) {
                    break;
                }
                
                //遍歷LinkedHashMap,刪除頂部的(也就是最先添加的)元素,直到當前緩存大小小於最大緩存,或LinkedHashMap爲空
                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

          //省略無關代碼......
        }
    }

    public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V previous;
        synchronized (this) {
            previous = map.remove(key);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        return previous;
    }

    private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }

     protected int sizeOf(K key, V value) {
        return 1;
    }


    public final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

    @Override public synchronized final String toString() {
        int accesses = hitCount + missCount;
        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
        return String.format(Locale.US, "LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
                maxSize, hitCount, missCount, hitPercent);
    }
}

其實從上面的代碼可以看出,LruCache內部主要靠一個LinkedHashMap來存儲緩存,這裏使用LinkedHashMap而不使用普通的HashMap正是看中了它的順序性,即LinkedHashMap中元素的存儲順序就是我們存入的順序,而HashMap則無法保證這一點。

我們都知道Lru算法就是最近最少使用的算法,而LruCache是如何保證在緩存大於最大緩存大小之後移除的就是最近最少使用的元素呢?關鍵在於 trimToSize(int maxSize) 這個方法內部,在它的內部開啓了一個循環,遍歷LinkedHashMap,刪除頂部的(也就是最先添加的)元素,直到當前緩存大小小於最大緩存,或LinkedHashMap爲空。這裏需要注意的是由於LinkedHashMap的特點,它的存儲順序就是存放的順序,所以位於頂部的元素就是最近最少使用的元素,正是由於這個特點,從而實現了當緩存不足時優先刪除最近最少使用的元素。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章