Glide分析

參考
作者:BlackFlag
鏈接:[http://www.reibang.com/p/57123450a9c8]

基本概念

image.png

image.png

使用

implementation 'com.github.bumptech.glide:glide:3.8.0'
annotationProcessor 'com.github.bumptech.glide:compiler:3.8.0'

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

Glide.with(getApplicationContext()) //指定Context
                .load(url)  //指定圖片的URL
                .placeholder(R.mipmap.ic_launcher)  //指定圖片未成功加載前顯示的圖片
                .error(R.mipmap.ic_launcher)    //指定圖片加載失敗顯示的圖片
                .override(300, 300)     //指定圖片的尺寸
                .fitCenter()    //指定圖片縮放類型為
                .centerCrop()   //指定圖片縮放類型為
                .skipMemoryCache(true)  //跳過內(nèi)存緩存
                .crossFade(1000)    //設(shè)置漸變式顯示的時間
                .diskCacheStrategy(DiskCacheStrategy.NONE)  //跳過磁盤緩存
                .diskCacheStrategy(DiskCacheStrategy.SOURCE)    //僅僅只緩存原理的全分辨率的圖像
                .diskCacheStrategy(DiskCacheStrategy.RESULT)    //僅僅緩存最終的圖像
                .diskCacheStrategy(DiskCacheStrategy.ALL)   //緩存所有版本的圖像
                .priority(Priority.HIGH)    //指定優(yōu)先級.Glide將會為他們作為一個準(zhǔn)則突那,并盡可能的處理這些請求盏混,但是并不能保證100%實施
                .into(imageView);   //指定顯示圖片的ImageView

1肥矢、with(*)方法

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

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

    public static RequestManager with(Fragment fragment) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(fragment);
    }

1帐我、獲取RequestManagerRetriever.get()得到retriever
2、通過retriever.get(context)獲得RequestManager對象并返回
3菠净、with通過傳入不同的參數(shù)禁舷,Context彪杉、Activity、Fragment等等牵咙,主要是通過相應(yīng)的生命周期是管理加載圖片派近,避免了消耗多余的資源,也避免了在Activity銷毀之后加載圖片從而導(dǎo)致的空指針問題洁桌。

2渴丸、RequestManagerRetriever

image.png

上面這個方法主要是通過傳入context的不同類型來做不同的操作。context可以是Application战坤、FragmentActivity曙强、Activity或者是ContextWrapper。getApplicationManager(Context context)通過單例模式創(chuàng)建并返回了 applicationManager


image.png

如果不是主線程或者版本過低途茫,還是通過get(Application)方法碟嘴,否則通過fragmentGet(activity, fm)方法。


image.png

fragmentGet 在當(dāng)前的activity創(chuàng)建沒有界面的fragment并add進(jìn)activity囊卜,并將這個fragment與RequestManager進(jìn)行綁定娜扇,實現(xiàn)對activity生命周期的監(jiān)聽
總結(jié)
  • 通過RequestManagerRetriever的get獲取RequestManagerRetriever對象
  • 通過retriever.get(context)獲取RequestManager,在get(context)方法中通過對context類型的判斷做不同的處理:
    • context是Application栅组,通過getApplicationManager(Context context) 創(chuàng)建并返回一個RequestManager對象
    • context是Activity雀瓢,通過fragmentGet(activity, fm)在當(dāng)前activity創(chuàng)建并添加一個沒有界面的fragment,從而實現(xiàn)圖片加載與activity的生命周期相綁定玉掸,之后創(chuàng)建并返回一個RequestManager對象

3刃麸、load(url)

image.png

通過RequestManager調(diào)用load方法——>DrawableTypeRequest<String>) fromString().load(string)
先看fromString方法

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

創(chuàng)建并返回了一個DrawableTypeRequest。
接下來看load方法

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

返回DrawableTypeRequest<String>對象司浪。跟進(jìn)父類的load看看

* @param model The model to load data for, or null.
     * @return This request builder.
     */
    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
        this.model = model;
        isModelSet = true;
        return this;
    }

DrawableRequestBuilder是GenericRequestBuilder子類泊业,前者是Drawable請求的構(gòu)建者,后者是通用的請求構(gòu)建者啊易。這個load方法其實是把我們傳入的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è)置成功了租谈。

Glide.with(getApplicationContext()) //指定Context
                .load(url)  //指定圖片的URL
                .placeholder(R.mipmap.ic_launcher)  //指定圖片未成功加載前顯示的圖片
                .error(R.mipmap.ic_launcher)    //指定圖片加載失敗顯示的圖片
                .override(300, 300)     //指定圖片的尺寸
                .fitCenter()    //指定圖片縮放類型為
                .centerCrop()   //指定圖片縮放類型為
                .skipMemoryCache(true)  //跳過內(nèi)存緩存
                .crossFade(1000)    //設(shè)置漸變式顯示的時間
                .diskCacheStrategy(DiskCacheStrategy.NONE)  //跳過磁盤緩存
                .diskCacheStrategy(DiskCacheStrategy.SOURCE)    //僅僅只緩存原理的全分辨率的圖像
                .diskCacheStrategy(DiskCacheStrategy.RESULT)    //僅僅緩存最終的圖像
                .diskCacheStrategy(DiskCacheStrategy.ALL)   //緩存所有版本的圖像
                .priority(Priority.HIGH)    //指定優(yōu)先級.Glide將會為他們作為一個準(zhǔn)則篮奄,并盡可能的處理這些請求,但是并不能保證100%實施
                .into(imageView);   //指定顯示圖片的ImageView

4割去、placeholder

DrawableRequestBuilder.class
/**
     * {@inheritDoc}
     */
    @Override
    public DrawableRequestBuilder<ModelType> placeholder(int resourceId) {
        super.placeholder(resourceId);
        return this;
    }
GenericRequestBuilder.class
/**
     * Sets an Android resource id for a {@link android.graphics.drawable.Drawable} resource to display while a resource
     * is loading.
     *
     * @param resourceId The id of the resource to use as a placeholder
     * @return This request builder.
     */
    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> placeholder(
            int resourceId) {
        this.placeholderId = resourceId;

        return this;
    }

GenericRequestBuilder通過builder模式為成員屬性進(jìn)行賦值

5窟却、into(imageView)方法

into之前的步驟其實就是創(chuàng)建了一個Request(加載圖片的配置請求)。info方法便是真正的執(zhí)行劫拗。

DrawableRequestBuilder.class
@Override
    public Target<GlideDrawable> into(ImageView view) {
        return super.into(view);
    }
GenericRequestBuilder.class
/**
     * Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into the view, and frees
     * any resources Glide may have previously loaded into the view so they may be reused.
     *
     * @see Glide#clear(android.view.View)
     *
     * @param view The view to cancel previous loads for and load the new resource into.
     * @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link ImageView}.
     */
    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));
    }

最終還是走到了GenericRequestBuilder.class中的into方法中间校。重點看

return into(glide.buildImageViewTarget(view, transcodeClass));

返回了Target<TranscodeType>對象。

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

/**
 * A factory responsible for producing the correct type of {@link com.bumptech.glide.request.target.Target} for a given
 * {@link android.view.View} subclass.
 */
public class ImageViewTargetFactory {

    @SuppressWarnings("unchecked")
    public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
        if (GlideDrawable.class.isAssignableFrom(clazz)) {
            return (Target<Z>) new GlideDrawableImageViewTarget(view);
        } else if (Bitmap.class.equals(clazz)) {
            return (Target<Z>) new BitmapImageViewTarget(view);
        } else if (Drawable.class.isAssignableFrom(clazz)) {
            return (Target<Z>) new DrawableImageViewTarget(view);
        } else {
            throw new IllegalArgumentException("Unhandled class: " + clazz
                    + ", try .as*(Class).transcode(ResourceTranscoder)");
        }
    }
}

通過對圖片來源類型的判斷页慷,創(chuàng)建并返回與圖片來源對應(yīng)的imageViewTarget憔足。

GenericRequestBuilder.class
/**
     * Set the target the resource will be loaded into.
     *
     * @see Glide#clear(com.bumptech.glide.request.target.Target)
     *
     * @param target The target to load the resource into.
     * @return The given target.
     */
    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");
        }
//確保數(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對象酒繁,則清空這個Request對象
        if (previous != null) {
            previous.clear();
//停止綁定到當(dāng)前target的上一個Request的圖片請求處理
            requestTracker.removeRequest(previous);
            previous.recycle();
        }
//創(chuàng)建Request對象
        Request request = buildRequest(target);
        target.setRequest(request);
        lifecycle.addListener(target);
//執(zhí)行request
        requestTracker.runRequest(request);

        return target;
    }

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

  • 獲取當(dāng)前target中的Request對象滓彰,如果存在,則清空并終止這個Request對象的執(zhí)行
  • 創(chuàng)建新的Request對象并與當(dāng)前target綁定
  • 執(zhí)行新創(chuàng)建的圖片處理請求Request

在沒有Glide之前州袒,我們處理ListView中的圖片加載其實是一件比較麻煩的事情揭绑。由于ListView中Item的復(fù)用機制,會導(dǎo)致網(wǎng)絡(luò)圖片加載的錯位或者閃爍郎哭。那我們解決這個問題的辦法也很簡單他匪,就是給當(dāng)前的ImageView設(shè)置tag,這個tag可以是圖片的URL等等夸研。當(dāng)從網(wǎng)絡(luò)中獲取到圖片時判斷這個ImageVIew中的tag是否是這個圖片的URL邦蜜,如果是就加載圖片,如果不是則跳過亥至。

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

 @Override
    public void setRequest(Request request) {
        setTag(request);
    }

    @Override
    public Request getRequest() {
        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;
    }

    private void setTag(Object tag) {
        if (tagId == null) {
            isTagUsedAtLeastOnce = true;
            view.setTag(tag);
        } else {
            view.setTag(tagId, tag);
        }
    }

    private Object getTag() {
        if (tagId == null) {
            return view.getTag();
        } else {
            return view.getTag(tagId);
        }
    }

target.getRequest()target.setRequest(Request request) 本質(zhì)上還是通過setTag和getTag來做的處理茶敏,這也印證了我們上面所說壤靶。

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

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

request.begin() 實際調(diào)用的是Request的子類 GenericRequest 的begin方法

/**
     * {@inheritDoc}
     */
    @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 {
            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 這個方法中
/**
     * A callback method that should never be invoked directly.
     */
    @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));
        }
    }

重點看loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, priority, isMemoryCacheable, diskCacheStrategy, this);

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)部會從三個來源獲取圖片數(shù)據(jù)吆鹤,我們最熟悉的就是LruCache了。如何獲取數(shù)據(jù)過于復(fù)雜洲守,這里就不再展開分析疑务,我們這里主要關(guān)注圖片數(shù)據(jù)獲取到之后的操作沾凄。獲取到圖片數(shù)據(jù)之后,通過 cb.onResourceReady(cached) 來處理知允,我們來看一下這個回調(diào)的具體實現(xiàn):

/**
     * A callback method that should never be invoked directly.
     */
    @SuppressWarnings("unchecked")
    @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);
    }

跟進(jìn)onResourceReady(resource, (R) received);

/**
     * Internal {@link #onResourceReady(Resource)} where arguments are known to be safe.
     *
     * @param resource original {@link Resource}, never <code>null</code>
     * @param result object returned by {@link Resource#get()}, checked for type and never <code>null</code>
     */
    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(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);最終回調(diào)到

/**
 * A target for display {@link Drawable} objects in {@link ImageView}s.
 */
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) 來實現(xiàn)的绘闷,在這個方法的內(nèi)部調(diào)用了Android內(nèi)部最常用的加載圖片的方法 view.setImageDrawable(resource) 忽肛。

到此為止嚼隘,into方法基本已經(jīng)分析完了秉版,我們忽略了網(wǎng)絡(luò)圖片獲取的過程,專注于獲取圖片后的處理〉拥妫現(xiàn)在來對into方法做個總結(jié):

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

6切蟋、LruCache源碼分析

在Glide源碼分析的最后我們再來分析一下LruCache的源碼,這個LruCache來自于 android.support.v4.util 中:

public class LruCache<K, V> {
//存儲緩存容器
    private final LinkedHashMap<K, V> map;

    /** Size of this cache in units. Not necessarily the number of elements. */
//當(dāng)前緩存的總大小
    private int size;
//最大緩存大小
    private int maxSize;
//添加到緩存的個數(shù)
    private int putCount;
//創(chuàng)建的個數(shù)
    private int createCount;
//移除的個數(shù)
    private int evictionCount;
//命中個數(shù)
    private int hitCount;
//未命中個數(shù)
    private int missCount;

    /**
     * @param maxSize for caches that do not override {@link #sizeOf}, this is
     *     the maximum number of entries in the cache. For all other caches,
     *     this is the maximum sum of the sizes of the entries in this cache.
     */
    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);
    }

    /**
     * Sets the size of the cache.
     * @param maxSize The new maximum size.
     *
     * @hide
     */
//重新設(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);
    }

    /**
     * Returns the value for {@code key} if it exists in the cache or can be
     * created by {@code #create}. If a value was returned, it is moved to the
     * head of the queue. This returns null if a value is not cached and cannot
     * be created.
     */
//獲取緩存
    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
//如果可以獲取key對應(yīng)的value
            mapValue = map.get(key);
            if (mapValue != null) {
//命中數(shù)加一
                hitCount++;
                return mapValue;
            }
//如果根據(jù)key獲取的value為null吱雏,未命中數(shù)加一
            missCount++;
        }

        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.
         */

        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

    /**
     * Caches {@code value} for {@code key}. The value is moved to the head of
     * the queue.
     *
     * @return the previous value mapped by {@code key}.
     */
    public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
 //添加到緩存的個數(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會覆蓋上一個value,所以當(dāng)前緩存大小應(yīng)該再減去之前value的大小
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
//根據(jù)緩存最大值調(diào)整緩存
        trimToSize(maxSize);
        return previous;
    }

    /**
     * @param maxSize the maximum size of the cache before returning. May be -1
     *     to evict even 0-sized elements.
     */
//根據(jù)最大緩存大小對map中的緩存做調(diào)整
    private 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為空時跳出循環(huán)
                if (size <= maxSize) {
                    break;
                }

                // BEGIN LAYOUTLIB CHANGE
                // get the last item in the linked list.
                // This is not efficient, the goal here is to minimize the changes
                // compared to the platform version.
                Map.Entry<K, V> toEvict = null;
//遍歷LinkedHashMap,刪除頂部的(也就是最先添加的)元素镰惦,直到當(dāng)前緩存大小小于最大緩存,或LinkedHashMap為空
                for (Map.Entry<K, V> entry : map.entrySet()) {
                    toEvict = entry;
                }
                // END LAYOUTLIB CHANGE

                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }

    /**
     * Removes the entry for {@code key} if it exists.
     *
     * @return the previous value mapped by {@code key}.
     */
    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);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }

        return previous;
    }

    /**
     * Called for entries that have been evicted or removed. This method is
     * invoked when a value is evicted to make space, removed by a call to
     * {@link #remove}, or replaced by a call to {@link #put}. The default
     * implementation does nothing.
     *
     * <p>The method is called without synchronization: other threads may
     * access the cache while this method is executing.
     *
     * @param evicted true if the entry is being removed to make space, false
     *     if the removal was caused by a {@link #put} or {@link #remove}.
     * @param newValue the new value for {@code key}, if it exists. If non-null,
     *     this removal was caused by a {@link #put}. Otherwise it was caused by
     *     an eviction or a {@link #remove}.
     */
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

    /**
     * Called after a cache miss to compute a value for the corresponding key.
     * Returns the computed value or null if no value can be computed. The
     * default implementation returns null.
     *
     * <p>The method is called without synchronization: other threads may
     * access the cache while this method is executing.
     *
     * <p>If a value for {@code key} exists in the cache when this method
     * returns, the created value will be released with {@link #entryRemoved}
     * and discarded. This can occur when multiple threads request the same key
     * at the same time (causing multiple values to be created), or when one
     * thread calls {@link #put} while another is creating a value for the same
     * key.
     */
    protected V create(K key) {
        return null;
    }

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

    /**
     * Returns the size of the entry for {@code key} and {@code value} in
     * user-defined units.  The default implementation returns 1 so that size
     * is the number of entries and max size is the maximum number of entries.
     *
     * <p>An entry's size must not change while it is in the cache.
     */
    protected int sizeOf(K key, V value) {
        return 1;
    }

    /**
     * Clear the cache, calling {@link #entryRemoved} on each removed entry.
     */
    public final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

    /**
     * For caches that do not override {@link #sizeOf}, this returns the number
     * of entries in the cache. For all other caches, this returns the sum of
     * the sizes of the entries in this cache.
     */
    public synchronized final int size() {
        return size;
    }

    /**
     * For caches that do not override {@link #sizeOf}, this returns the maximum
     * number of entries in the cache. For all other caches, this returns the
     * maximum sum of the sizes of the entries in this cache.
     */
    public synchronized final int maxSize() {
        return maxSize;
    }

    /**
     * Returns the number of times {@link #get} returned a value that was
     * already present in the cache.
     */
    public synchronized final int hitCount() {
        return hitCount;
    }

    /**
     * Returns the number of times {@link #get} returned null or required a new
     * value to be created.
     */
    public synchronized final int missCount() {
        return missCount;
    }

    /**
     * Returns the number of times {@link #create(Object)} returned a value.
     */
    public synchronized final int createCount() {
        return createCount;
    }

    /**
     * Returns the number of times {@link #put} was called.
     */
    public synchronized final int putCount() {
        return putCount;
    }

    /**
     * Returns the number of values that have been evicted.
     */
    public synchronized final int evictionCount() {
        return evictionCount;
    }

    /**
     * Returns a copy of the current contents of the cache, ordered from least
     * recently accessed to most recently accessed.
     */
    public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }

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

image.png

LruCache(最近最少使用算法)內(nèi)部主要靠一個LinkedHashMap來存儲緩存犬绒,這里使用LinkedHashMap而不使用普通的HashMap正是看中了它的順序性旺入,即LinkedHashMap中元素的存儲順序就是我們存入的順序,而HashMap則無法保證這一點凯力。關(guān)鍵在于 trimToSize(int maxSize) 這個方法內(nèi)部茵瘾,在它的內(nèi)部開啟了一個循環(huán),遍歷LinkedHashMap,刪除頂部的(也就是最先添加的)元素咐鹤,直到當(dāng)前緩存大小小于最大緩存拗秘,或LinkedHashMap為空。這里需要注意的是由于LinkedHashMap的特點祈惶,它的存儲順序就是存放的順序雕旨,所以位于頂部的元素就是最近最少使用的元素,正是由于這個特點捧请,從而實現(xiàn)了當(dāng)緩存不足時優(yōu)先刪除最近最少使用的元素凡涩。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市疹蛉,隨后出現(xiàn)的幾起案子活箕,更是在濱河造成了極大的恐慌,老刑警劉巖可款,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件育韩,死亡現(xiàn)場離奇詭異克蚂,居然都是意外死亡,警方通過查閱死者的電腦和手機座慰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門陨舱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翠拣,“玉大人版仔,你說我怎么就攤上這事∥竽梗” “怎么了蛮粮?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谜慌。 經(jīng)常有香客問我然想,道長,這世上最難降的妖魔是什么欣范? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任变泄,我火速辦了婚禮,結(jié)果婚禮上恼琼,老公的妹妹穿的比我還像新娘妨蛹。我一直安慰自己,他們只是感情好晴竞,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布蛙卤。 她就那樣靜靜地躺著,像睡著了一般噩死。 火紅的嫁衣襯著肌膚如雪颤难。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天已维,我揣著相機與錄音行嗤,去河邊找鬼。 笑死垛耳,一個胖子當(dāng)著我的面吹牛栅屏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播艾扮,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼既琴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泡嘴?” 一聲冷哼從身側(cè)響起甫恩,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎酌予,沒想到半個月后磺箕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奖慌,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年松靡,在試婚紗的時候發(fā)現(xiàn)自己被綠了简僧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡雕欺,死狀恐怖岛马,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情屠列,我是刑警寧澤啦逆,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站笛洛,受9級特大地震影響夏志,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜苛让,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一沟蔑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狱杰,春花似錦瘦材、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至颁湖,卻和暖如春宣蠕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背甥捺。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工抢蚀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人镰禾。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓皿曲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吴侦。 傳聞我的和親對象是個殘疾皇子屋休,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

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