Glide分析

作者: Kevin_Lv | 来源:发表于2019-10-21 18:21 被阅读0次

    参考
    作者: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的特点,它的存储顺序就是存放的顺序,所以位于顶部的元素就是最近最少使用的元素,正是由于这个特点,从而实现了当缓存不足时优先删除最近最少使用的元素。

    相关文章

      网友评论

        本文标题:Glide分析

        本文链接:https://www.haomeiwen.com/subject/kietvctx.html