1.Glide框架介绍
Glide框架是个图片加载框架,平时用的最多,功能最强大的图片加载框架,他对比universalimageloader 增加了Acitivyt和Fragment的生命周期的管理,也增加了一级缓存,Glide源码比ImageLoader的源码复杂很多,完全针对接口编程,导致很多方法很难找见对应实现类的入口,网上介绍Glide的代码个人感觉结构不是很清晰,分析的也不是重点,我对整个Glide做了一个详细的梳理,整理了一下大致脉络,下面大家跟我一起一点点剖析Glide的代码,Glide加载图片方法很简单也就一句话Glide.with().load(url).into(iv);,咱们一个个代码进行梳理
2.Glide.with()
很多博客或者网站对这个方法没有进行分析,个人认为这个方法很重要,因为他是联动Activity和Fragement的生命周期,当加载的Actvitiy和Fragment destroy的时候,会销毁这个加载图片的请求,Glide是怎么做到的呢,只能从源码中得到答案了。①Glide.with(FragmentActivity activity)②Glide.with(Fragment fragment) ;③Glide.with(Context context) 这个方法大概有这三种形式分别对应activity的生命周期和Fragement 和Application的生命周期,咱们主要分析第一种,其他的简略说一下就可以了,功能实现都很类似
//和Acitivity生命周期做绑定的 最后返回一个RequestManager的管理
public static RequestManager with(FragmentActivity activity) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}
//获取RequestManager的方法
public RequestManager get(FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
//判断不是主线程的话调用这个,为什么要判断主线程了,大家不用着急看下面代码就清楚了
return this.get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
//获取acitivy的FragmentManager,这个不是创建fragement时候用的吗?不错你猜对了,下面方法就要创建Fragement了
android.support.v4.app.FragmentManager fm = activity.getSupportFragmentManager();
return this.supportFragmentGet(activity, fm);
}
}
RequestManager supportFragmentGet(Context context, android.support.v4.app.FragmentManager fm) {
//看来真的添加了一个Fragement到activity里面,为什么要添加这个Fragement呢,下面有详细的代码大家可以
//看一下
SupportRequestManagerFragment current = this.getSupportRequestManagerFragment(fm);
//第一次走这个方法的时候肯定为空
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
//创建一个RequestManager然后这个Fragement关联这个RequestManager,第二次进来的话就能通过 //current.getRequestManager得到了,不用创建了
//从这大家就明白了一个Activity对应创建一个空白的Fragement关联唯一一个RequestManager
//获取fragment的lifecycle 添加一个lisenter 这样就能监听Fragement的生命周期了,在 ///RequestManager的onDestroy执行的就是
//this.requestTracker.clearRequests();清空网络请求
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
//创建Fragement添加到Activity里面 这个Fragement的生命周期就和Activity绑定了,Fragement里面有一//个lifecycle当生命周期变化的时候调用lifecycle的listenerSupportRequestManagerFragment
getSupportRequestManagerFragment(android.support.v4.app.FragmentManager fm) {
SupportRequestManagerFragment current = (SupportRequestManagerFragment)fm.findFragmentByTag("com.bumptech.glide.manager");
if (current == null) {
current = (SupportRequestManagerFragment)this.pendingSupportRequestManagerFragments.get(fm);
if (current == null) {
current = new SupportRequestManagerFragment();
this.pendingSupportRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, "com.bumptech.glide.manager").commitAllowingStateLoss();
this.handler.obtainMessage(2, fm).sendToTarget();
}
}
return current;
}
Glide.with(Activity activity)这个方法控制怎么控制生命周期的,我感觉他用的很巧妙,为每一个Acitivyt添加一个空的Fragement,通过Fragement关联activity的生命周期,在Fragement添加一个lifecycle可以添加监听生命周期的listener这是个简单的观察者模式的案例,一个Fragement 关联一个RequestManager(从名字上看出这个是请求管理类)保证一个Acitivty对应一个Fragement对应一个RequestManager,RequestManager关联了Fragement的lifecycle内部还添加了listener(观察者角色),Fragement执行onDestroy(),onStart()这些方法的时候都会循环遍历每一个listener进行回调(Fragement这里相当主题角色,当发生变化的时候通知某一个listener进行回调)RequestManager的回调方法就对网络请求进行处理执行listener的onDestoy(),清空网络请求,这个时候恍然大悟为什么要判断是不是其他线程了(Util.isOnBackgroundThread()) ,只有主线程才能添加Fragement,其他的线程统一交个Application的RequestManager进行管理,Glide.with(Fragment fragment) 与activity类似只不过是创建一个空白的子Fragement添加到Fragement而已监听Fragement的生命周期,不过Application没有监听生命周期,个人猜测是不是Application 挂掉之后被系统杀死了,不用监听生命周期,资源都会被回收Glide不用做单独的处理,大家有什么不同的意见,可以在下面评论给出来,多谢指教!
3.Glide.with().load(url)
从上面代码得知Glide.with生成一个RequestManager对象,那么Glide.with().load(url)调用的就是RequestManager.load()方法
//这个方法我没有发现对理解Glide 有用的方法所以不做过深的研究,最后生成一个DrawableRequestBuilder对象
public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest)this.fromString().load(string);
}
4.into()
调用的是DrawableRequestBuilder.into()方法
//这个方法会创建一个ImageVIewTraget 从名字就可以看出来是ImageView的一个封装的类,包装控件的一个东西
//生成的GlideDrawableImageViewTarget
public Target<TranscodeType> into(ImageView view) {
return this.into(this.glide.buildImageViewTarget(view, this.transcodeClass));
}
}
然后重点看一下into()
public <Y extends Target<TranscodeType>> Y into(Y target) {
//判断之前GlideDrawableImageViewTarget有没有绑定过请求,有的话取消这个请求GenericRequest
Request previous = target.getRequest();
if (previous != null) {
previous.clear();
this.requestTracker.removeRequest(previous);
previous.recycle();
}
//创建一个新的网络请求,并且GlideDrawableImageViewTarget绑定起来,request添加到Lfecycle(生命周 //期)的监听队列里面
//然后通过requestTracker把这个网络请求跑起来
Request request = this.buildRequest(target);
target.setRequest(request);
this.lifecycle.addListener(target);
this.requestTracker.runRequest(request);
return target;
}
this.requestTracker.runRequest(request);这个方法是核心重点他做了什么事情呢,这个一个RequestTracker(请求跟踪器)他是用来管理网络请求的
//判断一下请求的状态,不是暂停的话直接开始,暂停放到一个暂停队列里面,这个是RequestManager成员变量,
// 从上面中了解一个activity 拥有一个RequestManager,这样来说的说一个activity 下载对应一个跟踪器管理request
public void runRequest(Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
pendingRequests.add(request);
}
}
主要看一下request.begin()这个方法干了些什么GenericRequest.beigin()
//修改请求的状态,获取控件的高宽,根据获取的高宽进行回调
public void begin() {
status = GenericRequest.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));
}
}
这个request 怎么是GenericRequest呢,我是用断点打出来的,断点调试是个好办法,三方库源码都是针对接口编程,有很多定义的接口一点点找代码的根源进行分析太浪费时间,也不利于理解框架的整个脉络
//获取图片高宽之后回调这个方法GenericRequest.onSizeReady
//代码看执行请求是最终汇聚到engine(Engine 发动机)这个里面Engine进行处理的 所有页面用的是同一个Enginne
// 不要被Request里面的 private Engine engine;这种所误导,他只是个引用而已,一个共同的Enginner
public void onSizeReady(int width, int height) {
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
priority, isMemoryCacheable, diskCacheStrategy, this);
}
public <T, Z, R> Engine.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) {
//从缓存里面去
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 Engine.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 Engine.LoadStatus(cb, engineJob);
}
图片资源加载Glide 用的是四级缓存,先从内存缓存去,然后从活动资源里面取最后异步线程从硬盘取,最后才会从网络里面取出来
①内存缓存
//从内存里面取,先判断设置有没有内存缓存没有直接返回null,有的话,把资源假如活动资源(活动资源有一个引用数+1)使用LruCache 结构进行存储的
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
//活动资源引用数量+1
cached.acquire();
activeResources.put(key, new Engine.ResourceWeakReference(key, cached, getReferenceQueue()));
}
return cached;
}
只要在内存用过这个资源就会放到活动资源的里面,从内存列表里面删除这个资源,看到这里有个疑问为什么使用活动的资源呢,咱们说活动资源缓存的时候在讲解一下
②活动资源缓存
//活动资源使用弱引用存储的,这样来看的话不难发现是为了内存缓存使用强引用保存的,活动资源使用map保存弱引用的方式
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;
}
//EngineResource是用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1
void acquire() {
++acquired;
}
// 调用release()方法会让变量减1,代码如下所示:
void release() {
if (--acquired == 0) {
listener.onResourceReleased(key, this);
}
}
//acquired变量是用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1。
//当调用loadFromActiveResources()、loadFromCache()、EngineJob#handleResultOnMainThread()获取图片的时候都会执行acquire()方法;
//当暂停请求或者加载完毕或者清除资源时会调用release()方法。
③硬盘缓存和网络请求
硬盘缓存和网络下载都是用的线程池
//EngineJob
public void start(EngineRunnable engineRunnable) {
this.engineRunnable = engineRunnable;
future = diskCacheService.submit(engineRunnable);
}
执行EngineRunnable的run()方法,run()方法中调用到decode()方法,decode()方法的源码:
private Resource<?> decode() throws Exception {
if (isDecodingFromCache()) {
//从磁盘缓存读取图片
return decodeFromCache();
} else {
//从网络下载图片
return decodeFromSource();
}
}
硬盘缓存大致跟内存缓存差不多(说实话我没有细看)
网络请求最终用的也是HttPUrlConnection返回流,然后对流进行处理,生成图片
//HttpUrlFetcher 经过一拐八绕之后终于找见网络请求的源码,返回一个输入流,用的是HttPUrlConnection
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
throws IOException {
if (redirects >= MAXIMUM_REDIRECTS) {
throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new IOException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
urlConnection = connectionFactory.build(url);
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
urlConnection.setConnectTimeout(2500);
urlConnection.setReadTimeout(2500);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (statusCode / 100 == 2) {
return getStreamForSuccessfulRequest(urlConnection);
} else if (statusCode / 100 == 3) {
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new IOException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else {
if (statusCode == -1) {
throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
}
throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
}
}
总结:开启下载图片从新建Request 开始的然后把新建的Request 放到RequestTracker(请求跟踪器,里面有保存启动的线程队列和暂停的队列,当比如网络发生变化的时候,通过他控制request的请求的),单个的Request(GenericRequest)是当ImageVIewTarget(ImageView 的包装类)获取图片高宽之后,开始请求的,通过engine.load()方法开始请求,engine(Engine 发动机这个类,全局只有一个这个对象,真实的负责下载图片)他下载图片用了四级缓存,当一个从网络加载完之后先放到活动资源的列表中,这个是弱引用,这样保存不容易内存溢出,当图片资源被清空的时候比如页面关闭,图片资源不再使用,这个时候回放到内存缓存的队列里面,然后依次放到硬盘最后没有使用的内存或者硬盘缓存的情况只能网络请求了。只要图片比如从内存缓存或者硬盘缓存,网络取出的时候都会先放到活动资源的缓存里面,内存缓存和活动资源取图片的时候,直接从方法取出,不用异步线程,硬盘缓存和网络请求开启一个线程池,用新的线程进行操作(EngineRunnable 这个线程的run方法进行操作的)进行硬盘缓存和网络请求。
5.总结
到此我梳理的Glide 的整个框架脉络就是这些了,都是我认为的重点,有什么不对的地方或者大家有什么不同的意见,可以以评论的形式反馈给我,谢谢!