It's our wits that make us men.

Glide解析!

Posted on By yan zi jie

First POST build by Jekyll.github:https://github.com/nishibaiyang

Glide解析

参考:[郭霖Glide详解][2]{:target=”_blank”} [2]: http://blog.csdn.net/guolin_blog/article/details/53759439 “郭霖Glide详解”

使用方法

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

使用的方式相当的简单,相信大家都使用过。这也是部分人喜欢Glide的原因之一。说实话如果其他好资源都这样,那真是共产主义了。
这里对于使用方式就不多介绍了,相信看一遍就一目了然。常规的图片预加载、图片下载、图片缓存、图片加载回调、图片占位图以及一些图片的设置等等Glide都支持,功能可以说是很完善的。感兴趣的网上查查吧。 ### Glide解析 #### 一
那么先从 >Glide.with(this).load(url).into(imageView);

这段代码开始吧,with(context)字面的意思大家应该都懂。那么就开始从原理入手。 ···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);
}

这是with的重载方式,返回的都是RequestManager对象。很容易理解这是一个管理对象,RequestManagerRetriever.get()就轻易的拿到了这个对象,那么很容易想到get方法应该是一个单例。 实际上这里就是一个单例通过RequestManagerRetriever.get获取到RequestManager对象。with(context)方法中的context就和往常一样分两种,一种是application context另一种是非application的context(这两种简单理解就是生命周期不一样)。 * application的context周期比较长,Glide的操作可以贯彻整个application的周期 * 非application的context就没那么长了,例如activity和fragment的周期。如果图片还没加载出来activity就被销毁了,那么Glide肯定要取消接下来的操作了。否则不就内存泄漏了吗。 那么如何知道何时该取消Glide的操作呢?无非就是自己手动在结束时取消Glide的图片任务咯,但是作为一个优秀的设计这明显不合理,Glide已经自动完成了这操作。那无非就是监听了传入context的生命周期。bingo RequestManagerRetriever.get方法中就已经区分了application的context和非application的context。如果是非application的context就做监听生命周期的操作,通过创建一个隐藏的fragment,fragment的周期和activity周期一样并且可以知道activity的周期。这就达到了目的,来看看代码

    public RequestManager get(android.app.Fragment fragment) {
        if (fragment.getActivity() == null) {
            throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
        }
        if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            return get(fragment.getActivity().getApplicationContext());
        } else {
            android.app.FragmentManager fm = fragment.getChildFragmentManager();
            return fragmentGet(fragment.getActivity(), fm);
        }
    }
    
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
        RequestManagerFragment current = getRequestManagerFragment(fm);
        RequestManager requestManager = current.getRequestManager();
        if (requestManager == null) {
            requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
            current.setRequestManager(requestManager);
        }
        return requestManager;
    }

>Glide.with(this).load(url).into(imageView); with方法结束之后得到RequestManager对象RequestManager.load(),那么这个load方法肯定也在RequestManager中。接下来就研究下load方法。 ``` /**
 * Returns a request builder to load the given {@link String}.
 * signature.
 *
 * @see #fromString()
 * @see #load(Object)
 *
 * @param string A file path, or a uri or url handled by {@link com.bumptech.glide.load.model.UriLoader}.
 */
public DrawableTypeRequest<String> load(String string) {
    return (DrawableTypeRequest<String>) fromString().load(string);
}

/**
 * Returns a request builder that loads data from {@link String}s using an empty signature.
 *
 * <p>
 *     Note - this method caches data using only the given String as the cache key. If the data is a Uri outside of
 *     your control, or you otherwise expect the data represented by the given String to change without the String
 *     identifier changing, Consider using
 *     {@link GenericRequestBuilder#signature(Key)} to mixin a signature
 *     you create that identifies the data currently at the given String that will invalidate the cache if that data
 *     changes. Alternatively, using {@link DiskCacheStrategy#NONE} and/or
 *     {@link DrawableRequestBuilder#skipMemoryCache(boolean)} may be appropriate.
 * </p>
 *
 * @see #from(Class)
 * @see #load(String)
 */
public DrawableTypeRequest<String> fromString() {
    return loadGeneric(String.class);
}

private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
    ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
    ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
            Glide.buildFileDescriptorModelLoader(modelClass, context);
    if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
        throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"
                + " which there is a registered ModelLoader, if you are using a custom model, you must first call"
                + " Glide#register with a ModelLoaderFactory for your custom model class");
    }
    return optionsApplier.apply(
            new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
                    glide, requestTracker, lifecycle, optionsApplier));
} 

通过代码中load()就知道先调用fromString然后就load,fromString也就中转调用了loadGeneric方法返回DrawableTypeRequest,通过modelClass传入的参数最终输出DrawableTypeRequest,看看DrawableTypeRequest里面的实现。

     * Attempts to always load the resource as a {@link android.graphics.Bitmap}, even if it could actually be animated.
     *
     * @return A new request builder for loading a {@link android.graphics.Bitmap}
     */
    public BitmapTypeRequest<ModelType> asBitmap() {
        return optionsApplier.apply(new BitmapTypeRequest<ModelType>(this, streamModelLoader,
                fileDescriptorModelLoader, optionsApplier));
    }

    /**
     * Attempts to always load the resource as a {@link com.bumptech.glide.load.resource.gif.GifDrawable}.
     * <p>
     *     If the underlying data is not a GIF, this will fail. As a result, this should only be used if the model
     *     represents an animated GIF and the caller wants to interact with the GIfDrawable directly. Normally using
     *     just an {@link DrawableTypeRequest} is sufficient because it will determine whether or
     *     not the given data represents an animated GIF and return the appropriate animated or not animated
     *     {@link android.graphics.drawable.Drawable} automatically.
     * </p>
     *
     * @return A new request builder for loading a {@link com.bumptech.glide.load.resource.gif.GifDrawable}.
     */
    public GifTypeRequest<ModelType> asGif() {
        return optionsApplier.apply(new GifTypeRequest<ModelType>(this, streamModelLoader, optionsApplier));
    }
 DrawableTypeRequest里面最主要的就是提供了asBitmap和asGif方法了。分别是用于强制指定加载静态图片和动态图片。咦,并没有load方法。那怎么会调用load方法呢,应该是父类的方法吧。父类方法是DrawableRequestBuilder,也来看看
 
```@Override
    public DrawableRequestBuilder<ModelType> load(ModelType model) {
        super.load(model);
        return this;
    }

通过代码可以了解到,返回的就是DrawableRequestBuilder本身。DrawableRequestBuilder 里面有很多方法调用,例如placeholder()方法、error()方法、diskCacheStrategy()方法、override()方法等。这里先不深究。知道返回的是这个builder本身就好了。

Glide.with(this).load(url).into(imageView); 终于到into了,感觉手握胜券。不过into方法比上面两个家伙都复杂。一点一点看吧。 ```public Target 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)); }

最后一行代码先是调用了glide.buildImageViewTarget()方法,这个方法会构建出一个Target对象,Target对象则是用来最终展示图片用的.
```<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}

```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)");
    }
} } ``` 构建不同的Target,例如BitmapImageViewTarget、DrawableImageViewTarget等。又回到最后一行代码既into(target),接下来看看into调用的是什么方法。 ```  ....省略 Request request = buildRequest(target);
target.setRequest(request);
lifecycle.addListener(target);
requestTracker.runRequest(request);
return target; ``` 最终into里面构造了request并执行这个request(即执行request请求)。构造request时建立了带了很多参数,这些参数之前也提到过。runQuest方法中有一个队列,如果已经在执行了,则进入等待队列待执行完成在执行此任务。当开始执行request时,有一个begin方法。 ```@Override public void begin() {
startTime = LogTime.getLogTime();
if (model == null) {
    onException(null);
    return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
    onSizeReady(overrideWidth, overrideHeight);
} else {
    target.getSize(this);
}
if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
    target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
    logV("finished run method in " + LogTime.getElapsedMillis(startTime));
} } ``` 这个begin方法里面就是一些基本设置,例如model为空就会执行onException方法,这个方法就会执行图片加载错误的占位图,还有设置图片大小即resize加载图片宽高。可以理解begin方法里面就执行了这些设置。设置完之后就执行request请求了。下面看如何执行的。 ```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());

    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方法首先通过获取很多个参数成唯一一个key,通过这个key从缓存中取。如果缓存中没有,则通过线程异步执行加载图片并编码inputstream。接下来重点来了:就是获取图片流和编码过程。但是这里并不会仔细说,里面的逻辑有点多。想要了解详细的可以深入研究。说说大致的过程:通过获取图片流(可以从网络或者缓存中获取)将图片流进行编码,编码过程将一些设置参数如图片大小、图片位置是否颠倒、图片压缩等一系列操作之后得到bitmap。但是并不是直接返回这个bitmap,而是将bitmap进行包装得到可用性更高的glideresource对象,之后回调resource ready接口告诉用户图片加载完成了。最后通过Target解析resource并显示。如果是gif就调用resource.start开始播放gif,如果是图片则让对应的view进行设置。

Glide缓存

既然是图片加载框架,肯定要说说缓存啦。 既然是缓存,那肯定有个缓存key标识。Glide的缓存key是由十几个参数构成的,例如图片大小、图片url链接等等,最后得到一个标识。因此这个标识很容易就变了,敏感和善变平衡点啊。 缓存主要两大类: *内存缓存 *磁盘缓存

内存缓存

Glide自动就是开启内存缓存的。也就是说,当我们使用Glide加载了一张图片之后,这张图片就会被缓存到内存当中,只要在它还没从内存中被清除之前,下次使用Glide再加载这张图片都会直接从内存当中读取,而不用重新从网络或硬盘上读取了,这样无疑就可以大幅度提升图片的加载效率。当然也可以跳过内存缓存,调用skipMemoryCache()方法就可以了。 Glide内存缓存也是使用的LruCache算法,只不过是Glide自己实现的。此外还应用了弱引用的机制共同组成内存缓存。 使用LruCache算法进行缓存就不多做介绍了,无非就是通过这个机制进行缓存。重点说说弱引用机制是如何使用的。 ```private EngineResource loadFromCache(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } EngineResource cached = getEngineResourceFromCache(key); if (cached != null) { cached.acquire(); activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue())); } return cached; }

private EngineResource<?> getEngineResourceFromCache(Key key) {
    Resource<?> cached = cache.remove(key);
    final EngineResource result;
    if (cached == null) {
        result = null;
    } else if (cached instanceof EngineResource) {
        result = (EngineResource) cached;
    } else {
        result = new EngineResource(cached, true /*isCacheable*/);
    }
    return result;
}

private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
        return null;
    }
    EngineResource<?> active = null;
    WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
    if (activeRef != null) {
        active = activeRef.get();
        if (active != null) {
            active.acquire();
        } else {
            activeResources.remove(key);
        }
    }
    return active;
}

从LruResourceCache中获取到缓存图片之后会将它从缓存中移除,将这个缓存图片存储到activeResources当中。activeResources就是一个弱引用的HashMap,用来缓存正在使用中的图片,我们可以看到,loadFromActiveResources()方法就是从activeResources这个HashMap当中取值的。使用activeResources来缓存正在使用中的图片,可以保护这些图片不会被LruCache算法回收掉。即从LruResourceCache中移动到hashmap中保存,防止被算法误删。

小技巧

上面有提到过,Glide缓存一个很关键的参数就是图片的链接url。有时候计划赶不上变化呀,图片链接url会变,这个应该还蛮常见的。 那么通过分析该如何处理呢?当然是改url咯 ```public class MyGlideUrl extends GlideUrl {

private String mUrl;

public MyGlideUrl(String url) {
    super(url);
    mUrl = url;
}

@Override
public String getCacheKey() {
    return mUrl.replace(findTokenParam(), "");
}

private String findTokenParam() {
    String tokenParam = "";
    int tokenKeyIndex = mUrl.indexOf("?token=") >= 0 ? mUrl.indexOf("?token=") : mUrl.indexOf("&token=");
    if (tokenKeyIndex != -1) {
        int nextAndIndex = mUrl.indexOf("&", tokenKeyIndex + 1);
        if (nextAndIndex != -1) {
            tokenParam = mUrl.substring(tokenKeyIndex + 1, nextAndIndex + 1);
        } else {
            tokenParam = mUrl.substring(tokenKeyIndex);
        }
    }
    return tokenParam;
} }
Glide.with(this)
     .load(new MyGlideUrl(url))
     .into(imageView);

这样就可以很自由的修改url变成它该有的样子。

关于图片下载

public void downloadImage(View view) { new Thread(new Runnable() { @Override public void run() { try { String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg"; final Context context = getApplicationContext(); FutureTarget<File> target = Glide.with(context) .load(url) .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); final File imageFile = target.get(); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, imageFile.getPath(), Toast.LENGTH_LONG).show(); } }); } catch (Exception e) { e.printStackTrace(); } } }).start(); } downloadOnly必现在子线程中执行,通过get可以得到文件对象从而拿到path。

图片变换

说道Glide的图片变换自然要提到这个[神器][2]{:target=”_blank”} [2]: https://github.com/wasabeef/glide-transformations “变换神器”