本文主要是介绍(源码分析)Glide(图片异步加载缓存库)发起request获取resource,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
工作流程:
既然Glide的功能这么强大,那么就一定要学习下源码,看看内部是怎样工作的。
Glide.with((Fragment) t).load(url).into(imageView);
为了好理解,将上面一行代码进行分解:
RequestManager requestManager = Glide.with((Fragment) t);DrawableTypeRequest drawableTypeRequest = requestManager.load(url);//class DrawableTypeRequest<ModelType> extends DrawableRequestBuilder<ModelType> Target<GlideDrawable> target = DrawableRequestBuilder.into(imageView);
这几个类,我们一起了解下。
Glide:单例,提供使用(BitmapRequestBuilder)构建请求、包含engine,BitmapPool,DiskCache,MemoryCache的简单的静态方法。
Engine:启动下载和管理当前正在使用或者缓存的资源。
BitmapPool:接口类型,允许用户重用bitmap 对象。实现类 LruBitmapPool :复用bitmap,使用最近最少使用策略保证池子的大小。
RequestManager:用于管理和启动Glide请求的类,可以使用activity,fragment连接其生命周期方法来智能的中止、启动和重新发起请求。通过实例化一个新对象,或者利用Activity和Fragment的生命周期处理,在Fragment或者Activity中,使用Glide的静态load方法。
DrawableTypeRequest : 创建一个下载请求来下载drawable或者Bitmap drawable。
Target:接口类型,将加载的资源填充到target对象身上。实现类 ImageViewTarget ViewTarget GlideDrawableImageViewTarget等等。
那就从Glide的with()方法开始看。with方法可以接收context、activity、fragment类型。
/*** Begin a load with Glide by passing in a context.* 通过传递一个上下文对象来开始Glide。* 传入的如果是fragment/Activity,那么这个请求的生命周期就跟fragment/Activity生命周期一样* 如果是上下文是application,那么请求的声明周期就是应用级别的。* @see #with(android.app.Activity)* @see #with(android.app.Fragment)* @see #with(android.support.v4.app.Fragment)* @see #with(android.support.v4.app.FragmentActivity)** @param context Any context, will not be retained.* @return A RequestManager for the top level application that can be used to start a load.*/public static RequestManager with(Context context) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(context);}@TargetApi(Build.VERSION_CODES.HONEYCOMB)public static RequestManager with(Activity activity) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(activity);}public static RequestManager with(Fragment fragment) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(fragment);}
如果传入的是Activity或者Fragment,那么是怎么保证生命周期的?点进去看一下
public RequestManager get(Context context) {if (context == null) {throw new IllegalArgumentException("You cannot start a load on a null Context");} else if (Util.isOnMainThread() && !(context instanceof Application)) {if (context instanceof FragmentActivity) {return get((FragmentActivity) context);} else if (context instanceof Activity) {return get((Activity) context);} else if (context instanceof ContextWrapper) {return get(((ContextWrapper) context).getBaseContext());}}return getApplicationManager(context);
}
先判断如果不是在主线程,或者是application类型那么直接return getApplicationManager(context); 以activity类型为例,那么会调到下面的方法。
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public RequestManager get(Activity activity) {if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {return get(activity.getApplicationContext());} else {assertNotDestroyed(activity);android.app.FragmentManager fm = activity.getFragmentManager();return fragmentGet(activity, fm);}
}
如果是后台线程的话,继续创建 application级别的 requestmanager则会执行到fragmentGet(activity, 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;}
方法内部会创建一个RequestManagerFragment对象 current,是一个fragment对象。
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
将fragment的ActivityFragmentLifecycle 给requestManager。这样在RequestManager内部,通过 添加一个
addListener(LifecycleListener listener)
就可以保持与fragment的生命周期保持同步。
/*** 接口监听Fragment和Activity的生命周期事件。*/
public interface LifecycleListener {/***当Activity或者fragment的onStart方法执行的时候调用。*/void onStart();/*** 当Activity或者fragment的onStop方法执行的时候调用。*/void onStop();/***当Activity或者fragment的onDestroy方法执行的时候调用。*/void onDestroy();
}
在RequestManagManager中
/***重新开始失败或者暂停的requests.*/@Overridepublic void onStart() {resumeRequests();}/*** 停止request*/@Overridepublic void onStop() {pauseRequests();}/***取消请求,清楚回收资源*/@Overridepublic void onDestroy() {requestTracker.clearRequests();}
以上过程保证了Glide发起的request,与Activity/Fragment的生命周期保持一致,当Activity/Fragment销毁时,取消请求,这样能及时回收资源。
接下来再看下load方法。
/*** 返回一个来下载drawable或者Bitmap drawable的request对象。** @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);}
在我们介绍Glide 的功能时,设置的占位图,加载失败展示图,动画效果等都是由DrawableRequestBuilder来完成的。并且包含了具体的发起请求步骤。
Glide.with((Activity) t).load(url).placeholder(holderId).error(errorId).dontAnimate().dontTransform().into(imageView);
继承自GenericRequestBuilder,它是一个泛型类,可以处理设置选项和初始加载。可以看下它的成员变量
public class GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> implements Cloneable {protected final Class<ModelType> modelClass;protected final Context context;protected final Glide glide;protected final Class<TranscodeType> transcodeClass;//用于跟踪、取消和重新启动正在进行的、已完成的和失败的请求的类。protected final RequestTracker requestTracker;//生命周期protected final Lifecycle lifecycle;//磁盘缓存策略,只保存处理后的结果。private DiskCacheStrategy diskCacheStrategy = DiskCacheStrategy.RESULT;//设置的transformationprivate Transformation<ResourceType> transformation = UnitTransformation.get();//占位图idprivate int placeholderId;//失败图idprivate int errorId;...
以上通过load()方法,将网络,或者本地的资源,封装成一个GenericRequestBuilder 实例。
接下来是最后一个方法into(), 是绑定target对象。将前面要展示的资源,与要显示的载体绑定。这一步也是不可或缺的,一起分析下绑定流程。
@Overridepublic Target<GlideDrawable> into(ImageView view) {return super.into(view);}
直接看下父类是怎么生成target对象的。
/*** 设置资源加载的ImageView,会取消一切当前存在的加载,释放任何先前已经加载到View上的资源,以便可以复用。* @see Glide#clear(android.view.View)** @param view 取消view身上之前设置的的下载,加载新的下载。* @return Target 是传入的view的封装。*/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) {//如果设置过ImageView的scaleTypeswitch (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));}
执行到最下面一行,继续跟进去。
return into(glide.buildImageViewTarget(view, transcodeClass));
glide.buildImageViewTarget(view, transcodeClass) 对View做了一些封装,比如 在开始下载的时候,先展示默认图。这里就不一起看了,可以自己点进去看看,继续主流程。
/*** 设置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");}if (!isModelSet) {throw new IllegalArgumentException("You must first set a model (try #load())");}//先判断当前target有没有被设置过requestRequest previous = target.getRequest();if (previous != null) {//复用的target,释放资源。previous.clear();requestTracker.removeRequest(previous);previous.recycle();}//给target重新设置requestRequest request = buildRequest(target);target.setRequest(request);//添加声明周期方法,保证fragment声明周期回调时,request做出相应的处理。lifecycle.addListener(target);//开启请求requestTracker.runRequest(request);return target;}
这里要注意,对复用类型的 target首先判断之前有没有设置过request。设置的方式实际上是通过view.setTag()来实现的。
因此,在我们使用Glide的时候, 要注意,不要自己调用 view.setTag() 否则会报错 。
java.lang.IllegalArgumentException: You must not call setTag() on a view Glide is targeting
接下来就重点看下请求怎么发起的,request的发起,暂停,移除,恢复request都由RequestTrackerjia 间接管理。
public void runRequest(Request request) {requests.add(request);if (!isPaused) {request.begin();} else {pendingRequests.add(request);}}
实际上调用的就是request.begin()
@Overridepublic 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));}}
不论是重新指定过宽高还是使用target的默认宽高,最终都会回调到onSizeReady。
/*** 回调方法,永远不能直接调用。*/@Overridepublic 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;//交给Engine去获取资源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));}}
接下来执行到engine.laod() 方法。这个方法是根据传递的参数去开启资源加载。
流程如下:
1.从memorycache中检索资源,如果内存中有缓存资源,使用内存中的缓存。
2.检索当前使用的活跃资源(ActiveResources),如果检索到。使用当前活跃的。
ActiveResources也是内存缓存策略中的,只要是正在使用的缓存,都是加入到ActiveResource中的,ActiveResource核心是维护了一个弱引用的Map来储存对应的缓存数据
3.从当前正在进行加载的任务集合中查找,如果前面已经添加进加载队列,那么直接使用。
4.开始一个新的下载。
内存缓存策略:使用了两级内存缓存,MemoryCache和ActiveResource,前者默认为一个LruResourceCache,
后者是一个Map弱引用,引用了从MemoryCache中读取过的资源和从网络、硬盘下载和转换出的资源。
加载图片时先使用MemoryCache,如果没有找到则尝试从ActiveResource中获取资源。如果还是没有再从磁盘、网络获取资源。
/*** Starts a load for the given arguments. Must be called on the main thread.*根据给定的参数去开启资源加载,这个方法工作在主线程。*/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();//根据签名、宽高、transformation等等信息对要加载的资源生成一个唯一的key。EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),transcoder, loadProvider.getSourceEncoder());//1.从内存中获取,isMemoryCacheable:是否允许内存缓存策略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;}//2.从activeResources中获取 isMemoryCacheable:是否允许内存缓存策略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;}//3.是否之前已经添加了该任务。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);}//4.执行到这里,说明要新建任务了。engineJobFactory.build()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);//将任务添加到集合中。 Map<Key, EngineJob> jobsjobs.put(key, engineJob);engineJob.addCallback(cb);//启动任务start();engineJob.start(runnable);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Started new load", startTime, key);}return new LoadStatus(cb, engineJob);}
/***1.从内存中获取。*/private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {//不允许内存缓存 直接返回if (!isMemoryCacheable) {return null;}//尝试从内存中获取EngineResource<?> cached = getEngineResourceFromCache(key);if (cached != null) {//从内存中获取到cached.acquire();//加入到activeResources集合。activeResources = new HashMap<Key, WeakReference<EngineResource<?>>>();activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));}return cached;}/***2.从activeResources中获取。*/private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {//不允许内存缓存 直接返回if (!isMemoryCacheable) {return null;}EngineResource<?> active = null;WeakReference<EngineResource<?>> activeRef = activeResources.get(key);if (activeRef != null) {//从weakreference中 获取active = activeRef.get();if (active != null) {//没有被回收。active.acquire();} else {activeResources.remove(key);}}return active;}
如果缓存中没有,接下来会创建下载任务,开启任务。engineJobFactory.build()创建1个 EngineJob。然后启动
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);
public void start(EngineRunnable engineRunnable) {this.engineRunnable = engineRunnable;future = diskCacheService.submit(engineRunnable);
}
磁盘线程通过submit提交,后最终执行EngineRunnable 的run方法
@Overridepublic void run() {if (isCancelled) {return;}Exception exception = null;Resource<?> resource = null;try {//获取资源resource = decode();} catch (Exception e) {if (Log.isLoggable(TAG, Log.VERBOSE)) {Log.v(TAG, "Exception decoding", e);}exception = e;}//如果已经取消任务,回收资源。if (isCancelled) {if (resource != null) {resource.recycle();}return;}//拿到资源后回调if (resource == null) {onLoadFailed(exception);} else {onLoadComplete(resource);}}
主要看下怎么获取资源的,即 decode()方法
private Resource<?> decode() throws Exception {if (isDecodingFromCache()) {//从缓存中return decodeFromCache();} else {//从数据源中return decodeFromSource();}}
这里是从数据源获取,我们直接看decodeFromSource();
private Resource<?> decodeFromSource() throws Exception {return decodeJob.decodeFromSource();}
/***对返回的resource 添加transformed 。* @throws Exception*/public Resource<Z> decodeFromSource() throws Exception {Resource<T> decoded = decodeSource();return transformEncodeAndTranscode(decoded);}
private Resource<T> decodeSource() throws Exception {Resource<T> decoded = null;try {long startTime = LogTime.getLogTime();//请求资源。final A data = fetcher.loadData(priority);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Fetched data", startTime);}if (isCancelled) {return null;}//decoded = decodeFromSourceData(data);} finally {fetcher.cleanup();}return decoded;}
@Overridepublic InputStream loadData(Priority priority) throws Exception {return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());}
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.}}//使用的是HttpUrlConnectionurlConnection = 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) {//200 请求成功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());}}
//返回inputStreamprivate InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)throws IOException {if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {int contentLength = urlConnection.getContentLength();stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);} else {if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());}stream = urlConnection.getInputStream();}return stream;}
//对InputStream封装成 Bitmap资源。private Resource<T> decodeFromSourceData(A data) throws IOException {final Resource<T> decoded;if (diskCacheStrategy.cacheSource()) {//缓存源数据decoded = cacheAndDecodeSourceData(data);} else {long startTime = LogTime.getLogTime();//包装数据。decoded = loadProvider.getSourceDecoder().decode(data, width, height);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Decoded from source", startTime);}}return decoded;}
再回到前面的decodeFromSource()方法,将 resource 进行缓存。private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {long startTime = LogTime.getLogTime();Resource<T> transformed = transform(decoded);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Transformed resource from source", startTime);}writeTransformedToCache(transformed);startTime = LogTime.getLogTime();Resource<Z> result = transcode(transformed);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Transcoded transformed from source", startTime);}return result;}
这样,从发起request到获取到resource的流程就完成了。整个流程就是对最开始一张图的分析。
再来总结一下:
1.with(context):传入上下文对象,Glide内部 根据上下文的类型,来创建RequestManager。request的生命周期,受传入的context参数影响,如果传递的是application类型的,那么request的生命周期跟随应用走,如果是activity,或者fragment,那么生命周期与传入activity,或者fragment的生命周期保持一致。如果Activity/Fragment 被销毁,request 也会被cancel。
2.load(url): 根据url资源封装一个来返回drawable或者Bitmap drawable的request对象。
3.into(target):传入要承载resource 的target对象。先判断target有没有被复用过,如果复用过,先回收target身上设置过的request info,然后重新设置新的request info.设置的方式是通过view.setTag()所以,使用Glide加载图片的时候,我们不要自己调用setTag()方法,否则会报错。java.lang.IllegalArgumentException: You must not call setTag() on a view Glide is targeting。
设置完request info 后,会启动request,获取resource,获取的策略优先级是memorycache->activeRearource->diskcache-> netWork。
或许你还有疑问,后面会继续看以下几个问题。
Glide 中线程池用大小?
Bitmap如何复用的? Bitmappool。
发起request 拿到的 resource 如何缓存的。?
Glide缓存机制 内存 磁盘?
Glide中的设计模式?
https://muyangmin.github.io/glide-docs-cn/
http://www.paincker.com/glide-study
https://blog.csdn.net/u012124438/article/details/73612492
https://www.cnblogs.com/android-blogs/p/5735655.html
https://blog.csdn.net/guolin_blog/article/details/53939176
https://segmentfault.com/a/1190000014853308
谢谢认真观读本文的每一位小伙伴,衷心欢迎小伙伴给我指出文中的错误,也欢迎小伙伴与我交流学习。
欢迎爱学习的小伙伴加群一起进步:230274309 。
这篇关于(源码分析)Glide(图片异步加载缓存库)发起request获取resource的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!