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 “变换神器”