Glide源碼分析

Glide源碼分析

在早期的Android開發(fā)中肩刃,圖片加載其實(shí)一直是個(gè)比較麻煩的問題。我們在處理圖片時(shí)會遇到各種各樣的問題:內(nèi)存溢出、列表中圖片錯(cuò)位等等娘香。但到了如今苍狰,這些問題基本上是不會再遇到了。由于很多的優(yōu)秀的圖片加載框架幫我們處理了圖片相關(guān)問題的痛點(diǎn)烘绽,所以現(xiàn)在Android中關(guān)于圖片加載的部分變得非常簡單淋昭。Android中最著名的圖片加載框架就是Glide了,我們今天來深入研究一下Glide的源碼安接。

使用方法

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

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

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

  • with(context)
  • load(url)
  • into(target)
    在了解到Glide的3個(gè)入口方法之后盏檐,我會按照這3個(gè)方法來進(jìn)行源碼的分析

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方法有很多,但內(nèi)容基本一致胡野,都是通過 RequestManagerRetriever.get(); 獲取一個(gè) RequestManagerRetriever 對象 retriever 材失,然后通過 retriever.get(context); 獲取一個(gè) RequestManager 對象并返回。這些with方法關(guān)鍵的不同在于傳入的參數(shù)不一致硫豆,可以是Context龙巨、Activity、Fragment等等够庙。那么為什么要分這么多種呢恭应?其實(shí)我們應(yīng)該都知道:Glide在加載圖片的時(shí)候會綁定 with(context) 方法中傳入的 context 的生命周期,如果傳入的是 Activity 耘眨,那么在這個(gè) Activity 銷毀的時(shí)候Glide會停止圖片的加載昼榛。這樣做的好處是顯而易見的:避免了消耗多余的資源,也避免了在Activity銷毀之后加載圖片從而導(dǎo)致的空指針問題剔难。

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

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

    //根據(jù)傳入的參數(shù),獲取不同的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);
    }
    
    //省略無關(guān)代碼......
}

很明顯偶宫,這是個(gè)餓漢式的單例模式非迹。關(guān)鍵在于 retriever.get(context),我們繼續(xù)看代碼:

//根據(jù)傳入的參數(shù)纯趋,獲取不同的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)) {
            //當(dāng)前線程是主線程并且此context并不是Application的實(shí)例憎兽,根據(jù)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);
    }

上面這個(gè)方法主要是通過傳入context的不同類型來做不同的操作。context可以是Application吵冒、FragmentActivity纯命、Activity或者是ContextWrapper。我們先看一下當(dāng)context是Application時(shí)的操作:

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

        return applicationManager;
    }

代碼應(yīng)該都能看懂痹栖, getApplicationManager(Context context) 通過單例模式創(chuàng)建并返回了 applicationManager 亿汞。我們再來看一下如果傳入的context是Activity時(shí)的操作:

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 {
            //判斷當(dāng)前activity是否被銷毀
            assertNotDestroyed(activity);
            android.app.FragmentManager fm = activity.getFragmentManager();
           //通過fragmentGet(activity, fm)獲取RequestManager
            return fragmentGet(activity, fm);
        }
    }

代碼邏輯很簡單:如果不在主線程或者Android SDK版本過低揪阿,走的還是傳入Application的方法疗我,這個(gè)方法在上面提到過咆畏;反之,首先判斷當(dāng)前activity是否被銷毀吴裤,如果沒有被銷毀旧找,則通過fragmentGet(activity, fm)獲取RequestManager。關(guān)鍵是這個(gè) fragmentGet(activity, fm) 嚼摩,我們來看一下:

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

fragmentGet 這個(gè)方法主要是在當(dāng)前activity中創(chuàng)建一個(gè)沒有界面的的fragment并add到當(dāng)前activity中钦讳,以此來實(shí)現(xiàn)對activity生命周期的監(jiān)聽。到此枕面, with 方法已經(jīng)基本介紹完畢了愿卒,做一下總結(jié):

  • 通過RequestManagerRetriever的get獲取RequestManagerRetriever單例對象
  • 通過retriever.get(context)獲取RequestManager,在get(context)方法中通過對context類型的判斷做不同的處理:
    • context是Application潮秘,通過getApplicationManager(Context context) 創(chuàng)建并返回一個(gè)RequestManager對象
    • context是Activity琼开,通過fragmentGet(activity, fm)在當(dāng)前activity創(chuàng)建并添加一個(gè)沒有界面的fragment,從而實(shí)現(xiàn)圖片加載與activity的生命周期相綁定枕荞,之后創(chuàng)建并返回一個(gè)RequestManager對象

load(url)方法

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

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

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

public DrawableTypeRequest<String> fromString() {
        return loadGeneric(String.class);
    }

這個(gè)方法返回的是 loadGeneric(String.class) 渣刷,我們跟進(jìn)去:

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

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

loadGeneric(Class<T> modelClass) 方法中矗烛,我們只需要關(guān)注核心即可辅柴。它的核心是最后一句,方法調(diào)用看著很復(fù)雜瞭吃,其實(shí)本質(zhì)是創(chuàng)建并返回了一個(gè)DrawableTypeRequest碌嘀,Drawable類型的請求。再來看 load(string) 方法:

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

需要注意的是這個(gè)方法存在于DrawableTypeRequest的父類DrawableRequestBuilder中歪架,這個(gè)方法首先調(diào)用DrawableRequestBuilder的父類的load方法股冗,然后返回自身。再看一下DrawableRequestBuilder父類中的load方法:

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

DrawableRequestBuilder的父類是GenericRequestBuilder和蚪,從名字中我們也可以看出來止状,前者是Drawable請求的構(gòu)建者,后者是通用的請求構(gòu)建者攒霹,他們是子父關(guān)系导俘。這個(gè)load方法其實(shí)是把我們傳入的String類型的URL存入了內(nèi)部的model成員變量中,再將數(shù)據(jù)來源是否已經(jīng)設(shè)置的標(biāo)志位 isModelSet 設(shè)置為true剔蹋,意味著我們在調(diào)用 Glide.with(context).load(url) 之后數(shù)據(jù)來源已經(jīng)設(shè)置成功了。

說到這里辅髓,其實(shí)Glide中的load(url)基本已經(jīng)結(jié)束了泣崩,小伙伴們可能會有問題要問:我平時(shí)使用Glide會加一些配置少梁,比如:

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

其實(shí)大家在寫的時(shí)候是會有一種感覺的,這種寫法很像Builder模式矫付。沒錯(cuò)凯沪,這就是一個(gè)Builder模式。經(jīng)過上面的分析我們知道买优,在 Glide.with(context).load(url) 之后會返回一個(gè)DrawableTypeRequest的對象妨马,它的父類是DrawableRequestBuilder,DrawableRequestBuilder的父類是GenericRequestBuilder杀赢,我們寫的placeHolder()烘跺、error()等等相關(guān)圖片請求配置的方法都定義在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;
    }

是不是一下子就明白了脂崔,我們平時(shí)對圖片請求的配置使用的就是Builder模式滤淳。

into(imageView)方法

簡單的說,Glide中的前兩步是創(chuàng)建了一個(gè)Request砌左,這個(gè)Request可以理解為對圖片加載的配置請求脖咐,需要注意的是僅僅是創(chuàng)建了一個(gè) 請求 ,而并沒有去執(zhí)行汇歹。在Glide的最后一步into方法中屁擅,這個(gè)請求才會真實(shí)的執(zhí)行。

我們來DrawableTypeRequest中找一下into方法产弹,發(fā)現(xiàn)沒找到派歌,那肯定是在他的父類DrawableRequestBuilder中,我們來看一下DrawableRequestBuilder中的into方法:

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

嗯取视,它調(diào)用的是父類GenericRequestBuilder的方法硝皂,那我們繼續(xù)看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進(jìn)行配置
        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這個(gè)通用的請求構(gòu)建者中稽物。方法的核心是最后一行: into(glide.buildImageViewTarget(view, transcodeClass)) ,首先是通過 glide.buildImageViewTarget(view, transcodeClass) 創(chuàng)建出一個(gè)
Target 類型的對象折欠,然后把這個(gè)target傳入GenericRequestBuilder中的into方法中贝或。我們先來看一下Glide中的
buildImageViewTarget(view, transcodeClass) 方法:

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

這個(gè)方法的目的是把我們傳入的imageView包裝成一個(gè)Target。內(nèi)部調(diào)用了 imageViewTargetFactory.buildTarget(imageView, transcodedClass) 繼續(xù)跟進(jìn)去看一下:

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

這個(gè)方法的的本質(zhì)是:通過對圖片來源類型的判斷,創(chuàng)建并返回與圖片來源對應(yīng)的imageViewTarget酱床。獲取到相應(yīng)的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");
        }
        //確保數(shù)據(jù)來源已經(jīng)確定,即已經(jīng)調(diào)用了load(url)方法
        if (!isModelSet) {
            throw new IllegalArgumentException("You must first set a model (try #load())");
        }

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

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

        return target;
    }

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

  • 獲取當(dāng)前target中的Request對象昧捷,如果存在闲昭,則清空并終止這個(gè)Request對象的執(zhí)行
  • 創(chuàng)建新的Request對象并與當(dāng)前target綁定
  • 執(zhí)行新創(chuàng)建的圖片處理請求Request
    邏輯還是比較清晰的,這里有一個(gè)問題需要說明一下靡挥。為什么要終止并清除target之前綁定的請求呢序矩?

在沒有Glide之前,我們處理ListView中的圖片加載其實(shí)是一件比較麻煩的事情跋破。由于ListView中Item的復(fù)用機(jī)制簸淀,會導(dǎo)致網(wǎng)絡(luò)圖片加載的錯(cuò)位或者閃爍。那我們解決這個(gè)問題的辦法也很簡單毒返,就是給當(dāng)前的ImageView設(shè)置tag租幕,這個(gè)tag可以是圖片的URL等等。當(dāng)從網(wǎng)絡(luò)中獲取到圖片時(shí)判斷這個(gè)ImageVIew中的tag是否是這個(gè)圖片的URL饿悬,如果是就加載圖片令蛉,如果不是則跳過。

在有了Glide之后狡恬,我們處理ListView或者Recyclerview中的圖片加載就很無腦了珠叔,根本不需要作任何多余的操作,直接正常使用就行了弟劲。這其中的原理是Glide給我們處理了這些判斷祷安,我們來看一下Glide內(nèi)部是如何處理的:

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

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

繼續(xù)回到into方法中,在創(chuàng)建并綁定了Request后庸追,關(guān)鍵的就是 requestTracker.runRequest(request) 來執(zhí)行我們創(chuàng)建的請求了霍骄。

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

這個(gè)方法定義在 RequestTracker 中读整,這個(gè)類主要負(fù)責(zé)Request的執(zhí)行,暫停咱娶,取消等等關(guān)于圖片請求的操作米间。我們著重看 request.begin() ,這句代碼意味著開始執(zhí)行圖片請求的處理膘侮。Request是個(gè)接口屈糊, request.begin() 實(shí)際調(diào)用的是Request的子類 GenericRequest 的begin方法,我們跟進(jìn)去看一下:

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

        status = Status.WAITING_FOR_SIZE;
        if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
            //如果長寬尺寸已經(jīng)確定
            onSizeReady(overrideWidth, overrideHeight);
        } else {
            //獲取長寬尺寸琼了,獲取完之后會調(diào)用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));
        }
    }

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

  • 獲取圖片的長寬尺寸,如果長寬已經(jīng)確定,走 onSizeReady(overrideWidth, overrideHeight) 流程谦去;如果未確定慷丽,先獲取長寬,再走 onSizeReady(overrideWidth, overrideHeight)
  • 圖片開始加載鳄哭,首先顯示占位圖
    可以明白,主要的邏輯還是在 onSizeReady 這個(gè)方法中:
@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));
        }
    }

這段代碼看起來很復(fù)雜妆丘,我們只需要關(guān)注核心代碼:
engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, priority, isMemoryCacheable, diskCacheStrategy, this)
我們看一下load方法內(nèi)部做了什么處理:

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

        //開啟線程從網(wǎng)絡(luò)中加載圖片......
        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方法內(nèi)部會從三個(gè)來源獲取圖片數(shù)據(jù)勺拣,我們最熟悉的就是LruCache了。如何獲取數(shù)據(jù)過于復(fù)雜鱼填,這里就不再展開分析药有,我們這里主要關(guān)注圖片數(shù)據(jù)獲取到之后的操作。獲取到圖片數(shù)據(jù)之后苹丸,通過 cb.onResourceReady(cached) 來處理愤惰,我們來看一下這個(gè)回調(diào)的具體實(shí)現(xiàn):

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

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

    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);
            
            //核心,通過調(diào)用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)赘理,其實(shí)在這句代碼的內(nèi)部最終是通過:

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

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

本質(zhì)是通過 setResource(Drawable resource) 來實(shí)現(xiàn)的宦言,在這個(gè)方法的內(nèi)部調(diào)用了Android內(nèi)部最常用的加載圖片的方法 view.setImageDrawable(resource)

到此為止商模,into方法基本已經(jīng)分析完了奠旺,我們忽略了網(wǎng)絡(luò)圖片獲取的過程,專注于獲取圖片后的處理∈┝鳎現(xiàn)在來對into方法做個(gè)總結(jié):

  • 將imageview包裝成imageViewTarget
  • 清除這個(gè)imageViewTarget之前綁定的請求响疚,綁定新的請求
  • 執(zhí)行新的請求
  • 獲取圖片數(shù)據(jù)之后,成功則會調(diào)用ImageViewTarget中的onResourceReady()方法瞪醋,失敗則會調(diào)用ImageViewTarget中的onLoadFailed();二者的本質(zhì)都是通過調(diào)用Android中的imageView.setImageDrawable(drawable)來實(shí)現(xiàn)對imageView的圖片加載

LruCache源碼分析

Glide的基本源碼分析其實(shí)到這里已經(jīng)結(jié)束了忿晕,但提起圖片加載,LruCache是一個(gè)不可忽視的關(guān)鍵點(diǎn)趟章,在Glide源碼分析的最后我們再來分析一下LruCache的源碼杏糙,這個(gè)LruCache來自于 android.support.v4.util 中:

public class LruCache<K, V> {
    //存儲緩存
    private final LinkedHashMap<K, V> map;
    //當(dāng)前緩存的總大小
    private int size;
    //最大緩存大小
    private int maxSize;
    //添加到緩存的個(gè)數(shù)
    private int putCount;
    //創(chuàng)建的個(gè)數(shù)
    private int createCount;
    //移除的個(gè)數(shù)
    private int evictionCount;
    //命中個(gè)數(shù)
    private int hitCount;
    //未命中個(gè)數(shù)
    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);
    }

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

        synchronized (this) {
            this.maxSize = maxSize;
        }
        //對當(dāng)前的緩存做一些操作以適應(yīng)新的最大緩存大小
        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對應(yīng)的value
            if (mapValue != null) {
                //命中數(shù)加一
                hitCount++;
                return mapValue;
            }
            //如果根據(jù)key獲取的value為null,未命中數(shù)加一
            missCount++;
        }
        
        //省略無關(guān)代碼......
    }

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

        V previous;
        synchronized (this) {
            //添加到緩存的個(gè)數(shù)加一
            putCount++;
            //更新當(dāng)前緩存大小
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                //如果之前map中對應(yīng)key存在value不為null蚓土,由于重復(fù)的key新添加的value會覆蓋上一個(gè)value宏侍,所以當(dāng)前緩存大小應(yīng)該再減去之前value的大小
                size -= safeSizeOf(key, previous);
            }
        }
        //根據(jù)緩存最大值調(diào)整緩存
        trimToSize(maxSize);
        return previous;
    }

    //根據(jù)最大緩存大小對map中的緩存做調(diào)整
    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!");
                }
                //當(dāng)前緩存大小小于最大緩存,或LinkedHashMap為空時(shí)跳出循環(huán)
                if (size <= maxSize || map.isEmpty()) {
                    break;
                }
                
                //遍歷LinkedHashMap,刪除頂部的(也就是最先添加的)元素蜀漆,直到當(dāng)前緩存大小小于最大緩存谅河,或LinkedHashMap為空
                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

          //省略無關(guān)代碼......
        }
    }

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

其實(shí)從上面的代碼可以看出,LruCache內(nèi)部主要靠一個(gè)LinkedHashMap來存儲緩存,這里使用LinkedHashMap而不使用普通的HashMap正是看中了它的順序性绷耍,即LinkedHashMap中元素的存儲順序就是我們存入的順序吐限,而HashMap則無法保證這一點(diǎn)。

我們都知道Lru算法就是最近最少使用的算法褂始,而LruCache是如何保證在緩存大于最大緩存大小之后移除的就是最近最少使用的元素呢诸典?關(guān)鍵在于 trimToSize(int maxSize) 這個(gè)方法內(nèi)部,在它的內(nèi)部開啟了一個(gè)循環(huán)崎苗,遍歷LinkedHashMap,刪除頂部的(也就是最先添加的)元素狐粱,直到當(dāng)前緩存大小小于最大緩存,或LinkedHashMap為空胆数。這里需要注意的是由于LinkedHashMap的特點(diǎn)肌蜻,它的存儲順序就是存放的順序,所以位于頂部的元素就是最近最少使用的元素必尼,正是由于這個(gè)特點(diǎn)蒋搜,從而實(shí)現(xiàn)了當(dāng)緩存不足時(shí)優(yōu)先刪除最近最少使用的元素。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末判莉,一起剝皮案震驚了整個(gè)濱河市豆挽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌骂租,老刑警劉巖祷杈,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異渗饮,居然都是意外死亡但汞,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門互站,熙熙樓的掌柜王于貴愁眉苦臉地迎上來私蕾,“玉大人,你說我怎么就攤上這事胡桃〔劝龋” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵翠胰,是天一觀的道長容贝。 經(jīng)常有香客問我,道長之景,這世上最難降的妖魔是什么斤富? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮锻狗,結(jié)果婚禮上满力,老公的妹妹穿的比我還像新娘焕参。我一直安慰自己,他們只是感情好油额,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布叠纷。 她就那樣靜靜地躺著,像睡著了一般潦嘶。 火紅的嫁衣襯著肌膚如雪涩嚣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天衬以,我揣著相機(jī)與錄音缓艳,去河邊找鬼。 笑死看峻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的衙吩。 我是一名探鬼主播互妓,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坤塞!你這毒婦竟也來了冯勉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤摹芙,失蹤者是張志新(化名)和其女友劉穎灼狰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浮禾,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡交胚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盈电。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝴簇。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖匆帚,靈堂內(nèi)的尸體忽然破棺而出熬词,到底是詐尸還是另有隱情,我是刑警寧澤吸重,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布互拾,位于F島的核電站,受9級特大地震影響嚎幸,放射性物質(zhì)發(fā)生泄漏颜矿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一鞭铆、第九天 我趴在偏房一處隱蔽的房頂上張望或衡。 院中可真熱鬧焦影,春花似錦、人聲如沸封断。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坡疼。三九已至彬呻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柄瑰,已是汗流浹背闸氮。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留教沾,地道東北人蒲跨。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像授翻,于是被迫代替她去往敵國和親或悲。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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