Glide分析

参考
作者:BlackFlag
链接:[https://www.jianshu.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)  //跳过内存缓存
                .crossFade(1000)    //设置渐变式显示的时间
                .diskCacheStrategy(DiskCacheStrategy.NONE)  //跳过磁盘缓存
                .diskCacheStrategy(DiskCacheStrategy.SOURCE)    //仅仅只缓存原理的全分辨率的图像
                .diskCacheStrategy(DiskCacheStrategy.RESULT)    //仅仅缓存最终的图像
                .diskCacheStrategy(DiskCacheStrategy.ALL)   //缓存所有版本的图像
                .priority(Priority.HIGH)    //指定优先级.Glide将会为他们作为一个准则,并尽可能的处理这些请求,但是并不能保证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通过传入不同的参数,Context、Activity、Fragment等等,主要是通过相应的生命周期是管理加载图片,避免了消耗多余的资源,也避免了在Activity销毁之后加载图片从而导致的空指针问题。

2、RequestManagerRetriever

image.png

上面这个方法主要是通过传入context的不同类型来做不同的操作。context可以是Application、FragmentActivity、Activity或者是ContextWrapper。getApplicationManager(Context context)通过单例模式创建并返回了 applicationManager


image.png

如果不是主线程或者版本过低,还是通过get(Application)方法,否则通过fragmentGet(activity, fm)方法。


image.png

fragmentGet 在当前的activity创建没有界面的fragment并add进activity,并将这个fragment与RequestManager进行绑定,实现对activity生命周期的监听
总结
  • 通过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对象

3、load(url)

image.png

通过RequestManager调用load方法——>DrawableTypeRequest<String>) fromString().load(string)
先看fromString方法

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

创建并返回了一个DrawableTypeRequest。
接下来看load方法

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

返回DrawableTypeRequest<String>对象。跟进父类的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请求的构建者,后者是通用的请求构建者。这个load方法其实是把我们传入的String类型的URL存入了内部的model成员变量中,再将数据来源是否已经设置的标志位 isModelSet 设置为true,意味着我们在调用 Glide.with(context).load(url) 之后数据来源已经设置成功了。

Glide.with(getApplicationContext()) //指定Context
                .load(url)  //指定图片的URL
                .placeholder(R.mipmap.ic_launcher)  //指定图片未成功加载前显示的图片
                .error(R.mipmap.ic_launcher)    //指定图片加载失败显示的图片
                .override(300, 300)     //指定图片的尺寸
                .fitCenter()    //指定图片缩放类型为
                .centerCrop()   //指定图片缩放类型为
                .skipMemoryCache(true)  //跳过内存缓存
                .crossFade(1000)    //设置渐变式显示的时间
                .diskCacheStrategy(DiskCacheStrategy.NONE)  //跳过磁盘缓存
                .diskCacheStrategy(DiskCacheStrategy.SOURCE)    //仅仅只缓存原理的全分辨率的图像
                .diskCacheStrategy(DiskCacheStrategy.RESULT)    //仅仅缓存最终的图像
                .diskCacheStrategy(DiskCacheStrategy.ALL)   //缓存所有版本的图像
                .priority(Priority.HIGH)    //指定优先级.Glide将会为他们作为一个准则,并尽可能的处理这些请求,但是并不能保证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模式为成员属性进行赋值

5、into(imageView)方法

into之前的步骤其实就是创建了一个Request(加载图片的配置请求)。info方法便是真正的执行。

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

通过对图片来源类型的判断,创建并返回与图片来源对应的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");
        }
//确保数据来源已经确定,即已经调用了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.setRequest(request);
        lifecycle.addListener(target);
//执行request
        requestTracker.runRequest(request);

        return target;
    }

我们梳理一下方法中的逻辑:

  • 获取当前target中的Request对象,如果存在,则清空并终止这个Request对象的执行
  • 创建新的Request对象并与当前target绑定
  • 执行新创建的图片处理请求Request

在没有Glide之前,我们处理ListView中的图片加载其实是一件比较麻烦的事情。由于ListView中Item的复用机制,会导致网络图片加载的错位或者闪烁。那我们解决这个问题的办法也很简单,就是给当前的ImageView设置tag,这个tag可以是图片的URL等等。当从网络中获取到图片时判断这个ImageVIew中的tag是否是这个图片的URL,如果是就加载图片,如果不是则跳过。

在有了Glide之后,我们处理ListView或者Recyclerview中的图片加载就很无脑了,根本不需要作任何多余的操作,直接正常使用就行了。这其中的原理是Glide给我们处理了这些判断,我们来看一下Glide内部是如何处理的:

 @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) 本质上还是通过setTag和getTag来做的处理,这也印证了我们上面所说。

继续回到into方法中,在创建并绑定了Request后,关键的就是 requestTracker.runRequest(request) 来执行我们创建的请求了。

/**
     * Starts tracking the given request.
     */
    public void runRequest(Request request) {
//将请求加入请求集合
        requests.add(request);
        if (!isPaused) {
//如果处于非暂停状态,开始执行请求
            request.begin();
        } else {
//如果处于暂停状态,将请求添加到等待集合
            pendingRequests.add(request);
        }
    }

request.begin() 实际调用的是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));
        }
    }
  • 获取图片的长宽尺寸,如果长宽已经确定,走 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;
        }
//开启线程从网络中加载图片......
        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) 来处理,我们来看一下这个回调的具体实现:

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

跟进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);最终回调到

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

本质是通过 setResource(Drawable resource) 来实现的,在这个方法的内部调用了Android内部最常用的加载图片的方法 view.setImageDrawable(resource)

到此为止,into方法基本已经分析完了,我们忽略了网络图片获取的过程,专注于获取图片后的处理。现在来对into方法做个总结:

  • 将imageview包装成imageViewTarget
  • 清除这个imageViewTarget之前绑定的请求,绑定新的请求
  • 执行新的请求
  • 获取图片数据之后,成功则会调用ImageViewTarget中的onResourceReady()方法,失败则会调用ImageViewTarget中的onLoadFailed();二者的本质都是通过调用Android中的imageView.setImageDrawable(drawable)来实现对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. */
//当前缓存的总大小
    private int size;
//最大缓存大小
    private int maxSize;
//添加到缓存的个数
    private int putCount;
//创建的个数
    private int createCount;
//移除的个数
    private int evictionCount;
//命中个数
    private int hitCount;
//未命中个数
    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
     */
//重新设置最大缓存
    public void resize(int maxSize) {
//确保最大缓存大于0
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

        synchronized (this) {
            this.maxSize = maxSize;
        }
//对当前的缓存做一些操作以适应新的最大缓存大小
        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对应的value
            mapValue = map.get(key);
            if (mapValue != null) {
//命中数加一
                hitCount++;
                return mapValue;
            }
//如果根据key获取的value为null,未命中数加一
            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) {
 //添加到缓存的个数加一
            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);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
//根据缓存最大值调整缓存
        trimToSize(maxSize);
        return previous;
    }

    /**
     * @param maxSize the maximum size of the cache before returning. May be -1
     *     to evict even 0-sized elements.
     */
//根据最大缓存大小对map中的缓存做调整
    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!");
                }
//当前缓存大小小于最大缓存,或LinkedHashMap为空时跳出循环
                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,删除顶部的(也就是最先添加的)元素,直到当前缓存大小小于最大缓存,或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(最近最少使用算法)内部主要靠一个LinkedHashMap来存储缓存,这里使用LinkedHashMap而不使用普通的HashMap正是看中了它的顺序性,即LinkedHashMap中元素的存储顺序就是我们存入的顺序,而HashMap则无法保证这一点。关键在于 trimToSize(int maxSize) 这个方法内部,在它的内部开启了一个循环,遍历LinkedHashMap,删除顶部的(也就是最先添加的)元素,直到当前缓存大小小于最大缓存,或LinkedHashMap为空。这里需要注意的是由于LinkedHashMap的特点,它的存储顺序就是存放的顺序,所以位于顶部的元素就是最近最少使用的元素,正是由于这个特点,从而实现了当缓存不足时优先删除最近最少使用的元素。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容