今天要和大家一起分析的是图片加载开源库Glide,目前图片加载比较流行的开源库有Picasso,Glide,Fresco等,我用过的只有Picasso和Glide,Picasso给我的唯一感觉就是代码精简,功能强大。而Glide 相对而言代码要比Picasso复杂得多。之前项目中一直用Picasso但是后来项目中需要支持Gif才切换到Glide.Glide 特点也很明显,就是它能够响应生命周期事件以及网络状态监听事件,会随着这两类事件启动,暂停,恢复网络请求。两者的相同点就是接口相近,所以从Picasso切换到Glide不会有任何的困难。
好了我们先来分析下源码:
首先是Glide对象的创建: 这里的设计思想比较值得借鉴: 首先使用单例+建造者模式来创建出Glide,而将GlideMode相当于一个插件从AndroidMenifest解析出来,每个插件可以往Glide里面添加配置以及对应的组件实例. 这里 applyOption 以及 registerComponents 分别将 GlideBuilder 以及glide.registry 丢进去,在两个方法中将当前插件的配置信息以及插件本身注册到Glide中。
public static Glide get(Context context) { if (glide == null) { synchronized (Glide .class ) { if (glide == null) { Context applicationContext = context.getApplicationContext() ; List<GlideModule> modules = new ManifestParser(applicationContext ) .parse() ; GlideBuilder builder = new GlideBuilder(applicationContext ) ; for (GlideModule module : modules) { module .applyOptions(applicationContext , builder ) ; } glide = builder.createGlide() ; for (GlideModule module : modules) { module .registerComponents(applicationContext , glide .registry ) ; } } } } return glide; }
实际的Glide是通过GlideBuilder的createGlide中创建的,这个套路在Picasso中也应用过,在这里主要判断加载图片所需的部件是否都进行了设置如果没有设置那么就赋给它默认的对象。 Glide中的主要可设置的组件有如下:
1. 线程池2. 设备缓存计算器3. Bitmap 对象池4. 字节数组对象池5. 内存缓存6. 磁盘缓存7. Glide图片加载引擎8. 解码格式
这些会在涉及到的环节进行展开介绍。需要注意的是在解码格式的设置上,如果图片不支持透明度那么就使用565的格式,如果有透明度那么就使用8888的格式。这两种在保存的图片大小上会有影响。很显然8888的格式保存下来文件会相对大。但是效果会好很多。
Glide createGlide() { if (sourceService == null) { final int cores = Math . max(1 , Runtime . getRuntime() .availableProcessors() ); sourceService = new FifoPriorityThreadPoolExecutor("source" , cores ) ; } if (diskCacheService == null) { diskCacheService = new FifoPriorityThreadPoolExecutor("disk-cache" , 1) ; } if (memorySizeCalculator == null) { memorySizeCalculator = new MemorySizeCalculator.Builder(context ) .build() ; } if (bitmapPool == null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { int size = memorySizeCalculator.getBitmapPoolSize() ; if (DecodeFormat.REQUIRE_ARGB_8888) { bitmapPool = new LruBitmapPool(size , Collections.singleton (Bitmap.Config.ARGB_8888) ); } else { bitmapPool = new LruBitmapPool(size ) ; } } else { bitmapPool = new BitmapPoolAdapter() ; } } if (byteArrayPool == null) { byteArrayPool = new LruByteArrayPool() ; } if (memoryCache == null) { memoryCache = new LruResourceCache(memorySizeCalculator .getMemoryCacheSize () ); } if (diskCacheFactory == null) { diskCacheFactory = new InternalCacheDiskCacheFactory(context , Glide.DEFAULT_DISK_CACHE_SIZE) ; } if (engine == null) { engine = new Engine(memoryCache , diskCacheFactory , diskCacheService , sourceService ) ; } if (decodeFormat == null) { decodeFormat = DecodeFormat.DEFAULT; } return new Glide(engine , memoryCache , bitmapPool , byteArrayPool , context , decodeFormat ) ; }
紧接着就是调用Glide 构造方法来创建对象。在Glide 构造方法中最主要的任务就是注册一系列内置的图片资源编解码器,创建ImageViewTargetFactory,新建RequestOptions,并为其设置图片编码格式。最后将全局中比较常用的Glide,Register,RequestOption,Engine存放到GlideContext 中供后续使用,这个也是比较常用的方式。注册器会在后面进行进一步介绍。这里先主要看下整个流程。
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) Glide(Engine engine , MemoryCache memoryCache , BitmapPool bitmapPool , ByteArrayPool byteArrayPool , Context context , DecodeFormat decodeFormat ) { this.engine = engine; this.bitmapPool = bitmapPool; this.byteArrayPool = byteArrayPool; this.memoryCache = memoryCache; bitmapPreFiller = new BitmapPreFiller(memoryCache , bitmapPool , decodeFormat ) ; Resources resources = context.getResources() ; Downsampler downsampler = new Downsampler(resources .getDisplayMetrics () , bitmapPool, byteArrayPool); ByteBufferGifDecoder byteBufferGifDecoder = new ByteBufferGifDecoder(context , bitmapPool , byteArrayPool ) ; registry = new Registry(context ) .register(ByteBuffer .class , new ByteBufferEncoder() ) .register(InputStream .class , new StreamEncoder(byteArrayPool ) ) .register(Bitmap .class , BitmapDrawable .class , new BitmapDrawableTranscoder(resources , bitmapPool ) ) .register(Bitmap .class , byte[] .class , new BitmapBytesTranscoder() ) .register(GifDrawable .class , byte[] .class , new GifDrawableBytesTranscoder() ); ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory() ; RequestOptions options = new RequestOptions() .format(decodeFormat); glideContext = new GlideContext(context , registry , imageViewTargetFactory , options , engine , this ) ; }
到目前为止,Glide对象创建结束,我们回顾下整个过程:
首先是从AndroidManifest查找是否有用户自定义的GlideModule,如果有的话将其解析出来,每个GlideModule一般有两个方法,applyOptions和registerComponents ,applyOptions负责将自定义GlideModule中的配置添加到GlideBuilder中,而registerComponents 负责将当前GlideModule添加到Register中。
然后在GlideBuilder 的createGlide方法中为没有设置的Glide重要组件设置默认状态。
最后调用Glide构造方法创建对象。在构造方法中主要是完成各种资源编解码器的注册,以及Target的构建工厂对象的创建。以及设置解码格式和Glide图片加载引擎。
通过 GlideContext将后续处理常用的组件暴露出来。
Glide创建出来后就进入了with阶段: 之前提到的Glide 图片加载过程中会响应Activity/Fragment生命周期 以及网络状态而启动,恢复,暂停图片加载请求都是在这个环节中体现的。这个环节中主要的两个对象是RequestManagerFragment 以及 ConnectivityMonitor。 好了我们接下来就来带着这两个问题进一步阅读源码:
大家可以发现Glide.java 中有很多with方法,为什么需要重载这么多个呢?它们之间的区别是什么呢?
使用context启动的请求将只会拥有应用等级的options ,不会根据生命周期事件来自动开始或者停止图片的加载。通常而言,如果资源在一个子Fragment中的一个View 中使用,那么load 就应该使用子Fragment作为参数的with 方法。如果资源应用在父Fragment中的View 中,那么load 就应该使用父Fragment作为参数的with 方法。同样的道理如果资源在Activity中使用那么就应该使用Activity作为参数的with 方法,使用Context作为参数的一般用于不在常用的fragment或者activity生命周期的资源,比如services或者notification中资源
我们以最常用的Activty的情况作为分析情景:
public static RequestManager with (Activity activity ) { RequestManagerRetriever retriever = RequestManagerRetriever.get (); return retriever.get (activity); }
第一阶段我们先看下怎么获取到RequestManagerFragment,首先我们应该明白RequestManagerFragment和我们开发过程中常见的Fragment的区别是它是一个没有界面不可见的一个Fragment,但是它有Fragment所拥有的生命周期。我们可以借助这一点来实现前面提到的图片加载请求状态随着生命周期的改变而改变。
public RequestManager get (Activity activity) { if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { return get (activity.getApplicationContext()); } else { android.app.FragmentManager fm = activity.getFragmentManager(); return fragmentGet(activity, fm); } }
那么对于Activity而言这个Fragment藏在哪里呢?它和Activty所拥有的一般Fragment一样都归到Activity FragmentManager中统一管理。我们看下这个过程:
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; }
从上面代码可以看出,这个阶段首先会先从FragmentManger 中获取,如果没获取到就会新建一个并添加到FragmentManager,获取完RequestManagerFragment后就从中获取RequestManager,下面是获取RequestManagerFragment的过程代码:
RequestManagerFragment getRequestManagerFragment(final android .app .FragmentManager fm ) { RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG) ; if (current == null) { current = pendingRequestManagerFragments.get(fm); if (current == null) { current = new RequestManagerFragment() ; pendingRequestManagerFragments.put(fm, current); fm.begin Transaction() .add(current, FRAGMENT_TAG).commitAllowingStateLoss() ; handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm ) .sendToTarget() ; } } return current; }
我们上面介绍了RequestManagerFragment的获取方式,那么RequestManagerFragment又是怎么将请求和生命周期给对应起来的呢?在回答这个问题之前我们先来看下RequestManagerFragment的结构,RequestManagerFragment中有两个重要对象ActivityFragmentLifecycle,RequestManager 前者是生命周期的触发者,需要响应生命周期的部件都需要监听它,而RequstManger负责请求的管理,它里面有个RequestTracker 用于实行请求的管理。RequstManaer是生命周期事件的响应者,RequestManagerFragment触发对应生命周期后通过ActivityFragmentLifecycle传出来,ActivityFragmentLifecycle再通知监听它的监听者。对应的监听者在不同的生命周期作出不同的响应。
我们来看下这部分的代码 ActivityFragmentLifecycle 是在RequestManagerFragment构造方法中创建的,如下所示:
public RequestManagerFragment () { this (new ActivityFragmentLifecycle()); }
RequestManager是通过setRequestManager进行注入的,这个可以看之前的代码介绍。
public void setRequestManager (RequestManager requestManager ) { this .requestManager = requestManager; }
我们再看下RequestManagerFragment的生命周期中做了什么,和之前说的一样它在对应的生命周期中触发lifecycle事件,
@Override public void onStart ( ) { super .onStart (); lifecycle.onStart (); } @Override public void onStop ( ) { super .onStop (); lifecycle.onStop (); } @Override public void onDestroy ( ) { super .onDestroy (); lifecycle.onDestroy (); }
ActivityFragmentLifecycle 很简单就是注册对应生命周期事件的观察者,一旦ActivtyFragmentLifeCyble被RequestManagerFragment 生命周期所触发,那么ActivtyFragmentLifeCycle也会将对应的事件通知到各个观察者。
@Override public void addListener(LifecycleListener listener ) { lifecycleListeners.add(listener); if (isDestroyed) { listener.onDestroy() ; } else if (isStarted) { listener.onStart() ; } else { listener.onStop() ; } } @Override public void removeListener(LifecycleListener listener ) { lifecycleListeners.remove(listener); } void onStart() { isStarted = true ; for (LifecycleListener lifecycleListener : Util . getSnapshot(lifecycleListeners ) ) { lifecycleListener.onStart() ; } } void onStop() { isStarted = false ; for (LifecycleListener lifecycleListener : Util . getSnapshot(lifecycleListeners ) ) { lifecycleListener.onStop() ; } } void onDestroy() { isDestroyed = true ; for (LifecycleListener lifecycleListener : Util . getSnapshot(lifecycleListeners ) ) { lifecycleListener.onDestroy() ; } }
ActivtyFragmentLifeCycle 有两个重要的观察者:RequestManager 和 ConnectivityMonitor。所以这个阶段的整个过程如下图所示:
到此为止with阶段代码分析完毕,我们来简单回顾下: 这个阶段主要是完成请求随着生命周期的状态改变而改变状态的工作。主要涉及到四个重要对象一个是RequestManagerFragment 一个是 ActivityFragmentLifeCycle ,还有两个是RequestManger,以及ConnectivityMonitor。三者之间的关系以及如何实现请求随着生命周期的状态改变而改变状态这个在上面已经详细介绍过了。这个需要重点了解下。
接下来是as阶段:
load 有多种实现,现将这些实现都罗列如下:
public RequestBuilder<Bitmap> asBitmap () { return as (Bitmap.class ).transition(new BitmapTransitionOptions()).apply(DECODE_TYPE_BITMAP); } public RequestBuilder<GifDrawable> asGif () { return as (GifDrawable.class ).transition(new DrawableTransitionOptions()).apply(DECODE_TYPE_GIF); } public RequestBuilder<Drawable> asDrawable () { return as (Drawable.class ).transition(new DrawableTransitionOptions()); }
上面最终都是调用如下方法:
public <ResourceType> RequestBuilder <ResourceType > as (Class<ResourceType> resourceClass ) { return new RequestBuilder<>(context, this , resourceClass); }
从这里可以看出load阶段的工作就是创建一个RequestBuilder。
创建了一个RequestBuilder后就可以调用load开始加载了。我们来看下load阶段:
和as以及with阶段一样load也有各种重载,但是最终还是调用:
private RequestBuilder<TranscodeType> loadGeneric(Object model) { this .model = model; isModelSet = true ; return this ; }
这里只是简单得设置了model并将isModelSet标识为设为true后返回。很简单吧,其实Picasso这个阶段也很简单,不信可以看下我之前写的博客。
Glide对象创建了,请求状态随生命周期改变而改变实现了,RequestBuilder创建出来了,model设置了,接下来就是最重要的图片加载流程了,也就是into阶段:
into阶段实际上就是开始加载图片数据,并将图片数据设置到Target上,我们这里以ImageView为Target来看下这个流程: public Target<TranscodeType> into (ImageView view ) { if (!requestOptions.isTransformationSet() && view.getScaleType() != null ) { if (requestOptions.isLocked()) { requestOptions = requestOptions.clone(); } switch (view.getScaleType()) { case CENTER_CROP: requestOptions.optionalCenterCrop(context); break ; case FIT_CENTER: case FIT_START: case FIT_END: requestOptions.optionalFitCenter(context); break ; default : } } return into (context.buildImageViewTarget(view, transcodeClass)); }
在这个阶段最重要的是最后一行代码into(context.buildImageViewTarget(view, transcodeClass))
public <X> Target<X> buildImageViewTarget(ImageView imageView , Class<X> transcodeClass ) { return imageViewTargetFactory.buildTarget(imageView , transcodeClass ) ; }
这里使用Glide对象构造阶段创建的ImageViewTargetFactory来创建出我们需要的Target,具体需要创建什么Target需要根据transcodeClass来决定。
public <Z> Target<Z> buildTarget(ImageView view , Class <Z> clazz) { 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)" ) } }
而transcodeClass 是在创建RequestBuilder的时候传入的,而RequestBuilder是在as方法中创建的,所以这里的resourceClass就是buildTarget所对应的transcodeClass。所以我们调用asBitmap 的时候这里传入的就是Bitmap.class,调用asGif的时候这里传入的就是GifDrawable.class,如果我们调用asDrawable的时候,这里传入的就是Drawable.class。
public <ResourceType> RequestBuilder <ResourceType > as (Class<ResourceType> resourceClass ) { return new RequestBuilder<>(context, this , resourceClass); }
不管怎样buildTarget 返回的就两个对象要么是BitmapImageViewTarget要么是DrawableImageViewTarget
BitmapImageViewTarget –> ImageViewTarget DrawableImageViewTarget –> ImageViewTarget
但是我们前面已经提到asDrawable 和asGif两个都是对应的DrawableImageViewTarget那么这两个会有什么区别呢?我们看下DrawableImageViewTarget,两种区别在于是否继承自Animatable,GifDrawable是继承自Animatable 所以在资源获取结束后会调用start方法让它动起来。
@Override public void onResourceReady (Drawable resource, Transition<? super Drawable> transition ) { ViewGroup .LayoutParams layoutParams = view.getLayoutParams (); if (!(resource instanceof Animatable ) && layoutParams != null && layoutParams.width > 0 && layoutParams.height > 0 ) { resource = new FixedSizeDrawable (resource, layoutParams.width , layoutParams.height ); } super .onResourceReady (resource, transition); if (resource instanceof Animatable ) { ((Animatable ) resource).start (); } } @Override public void onStart ( ) { if (resource instanceof Animatable ) { ((Animatable ) resource).start (); } } @Override public void onStop ( ) { if (resource instanceof Animatable ) { ((Animatable ) resource).stop (); } }
我们再继续顺着继承树看下去,
BitmapImageViewTarget –> ImageViewTarget –> ViewTarget –> BaseTarget DrawableImageViewTarget –> ImageViewTarget –> ViewTarget –> BaseTarget
ViewTarget 中有几个重要的回调函数onLoadStarted,onLoadFailed ,onLoadCleared ,onResourceReady 分别在启动加载,加载失败,清除加载,资源获取完毕的情况下回调,主要用于设置占位图片,错误图片以及加载完的图片
@Override public void onLoadStarted (Drawable placeholder ) { setResource (null ); setDrawable (placeholder); } @Override public void onLoadFailed (Drawable errorDrawable ) { setResource (null ); setDrawable (errorDrawable); } @Override public void onLoadCleared (Drawable placeholder ) { setResource (null ); setDrawable (placeholder); } @Override public void onResourceReady (Z resource, Transition<? super Z> transition ) { if (transition == null || !transition.transition (resource, this )) { setResource (resource); } }
这个继承树根真深,还得继续刨: ViewTarget有两个重要的任务就是获取这个view的Size以及设置tag: 这里有个重要的对象SizeDeterminer,获取View的尺寸就是通过这个类来完成的。它首先会先通过getWidth()和getHeight()来获取View的宽高,如果有一个为0的话,它会检查View的LayoutParams从中获取宽高大小,如果还是没有那么就会监听OnPreDrawListener回调从而等到在绘制之前进行测量的时候回调。
public void getSize(SizeReadyCallback cb ) { int currentWidth = getViewWidthOrParam() ; int currentHeight = getViewHeightOrParam() ; if (isSizeValid(currentWidth ) && isSizeValid(currentHeight ) ) { cb.onSizeReady(currentWidth , currentHeight ) ; } else { if (!cbs.contains(cb)) { cbs.add(cb); } if (layoutListener == null) { final ViewTreeObserver observer = view.getViewTreeObserver() ; layoutListener = new SizeDeterminerLayoutListener(this ) ; observer.addOnPreDrawListener(layoutListener ) ; } } }
在测量结束的时候会调用checkCurrentDimens来检查当前的宽高
@Override public boolean onPreDraw () { SizeDeterminer sizeDeterminer = sizeDeterminerRef.get(); if (sizeDeterminer != null ) { sizeDeterminer.checkCurrentDimens(); } return true ; }
在checkCurrentDimens 会重新获取并检查View的宽高如果获取到了就通过notifyCbs来通知对应的监听者。 并将其从OnPreDrawListener监听队列中返回。
private void checkCurrentDimens() { if (cbs.isEmpty() ) { return; } int currentWidth = getViewWidthOrParam() ; int currentHeight = getViewHeightOrParam() ; if (!isSizeValid(currentWidth ) || !isSizeValid(currentHeight ) ) { return; } notifyCbs(currentWidth , currentHeight ) ; ViewTreeObserver observer = view.getViewTreeObserver() ; if (observer.isAlive() ) { observer.removeOnPreDrawListener(layoutListener ) ; } layoutListener = null; }
所以整个继承树的完成工作如下: DrawableImageViewTarget –> ImageViewTarget –> ViewTarget –> BaseTarget 将加载的图片设置到Target,如果是Gif启动播放Gif –> 设置占位图,错误图缩率图等 –> 获取图片尺寸,设置Tag –> 存储Request 有了上述的了解我们再继续返回看into的流程:
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 put a model (try #load())" ); } Request previous = target .getRequest(); if (previous != null ) { requestManager.clear(target ); } requestOptions.lock(); Request request = buildRequest(target ); target .setRequest(request); requestManager.track(target , request); return target ; }
into方法中会首先检查当前target是否有请求正在进行如果有那么先清除原先的请求。调用buildRequest来创建该次请求,并调用setRequest将请求与target进行绑定(也就是为当前View打上request的标签,并将request保存下来)。然后通过ReqestManager对该请求进行track。
这里关注两点
buildRequest怎么创建出请求。
ReqestManager怎么使用创建出来的请求进行处理.private Request buildRequest (Target<TranscodeType> target ) { return buildRequestRecursive(target , null , transitionOptions, requestOptions.getPriority(), requestOptions.getOverrideWidth(), requestOptions.getOverrideHeight()); } private Request buildRequestRecursive (Target<TranscodeType> target , if (thumbnailBuilder != null ) { } else { return obtainRequest (target , requestOptions, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight) ; } } private Request obtainRequest (Target<TranscodeType> target , BaseRequestOptions<?> requestOptions, RequestCoordinator requestCoordinator, TransitionOptions<?, ? super TranscodeType> transitionOptions, Priority priority, int overrideWidth, int overrideHeight) { requestOptions.lock(); RequestContext<?, TranscodeType> requestContext = new RequestContext<>(context, model, transcodeClass, requestOptions, priority, overrideWidth, overrideHeight); return SingleRequest.obtain(requestContext, target , requestListener, requestCoordinator, context.getEngine(), transitionOptions.getTransitionFactory()); }
为了避免频繁创建和释放请求对象,这里使用了一个请求对象池,最多可以缓存150个请求。每次会从这个请求池中优先获取,只有在请求池为空的情况下才会新建一个请求,获取到一个请求对象后,就可以使用当前的请求参数初始化获取到的请求对象。这样就可以在这次请求中使用了。public static < R > SingleRequest <R > obtain(RequestContext <?, R > requestContext, Target <R > target, RequestListener <R > requestListener, RequestCoordinator requestCoordinator, Engine engine, TransitionFactory <? super R > animationFactory) { @SuppressWarnings ("unchecked" ) SingleRequest <R > request = (SingleRequest <R >) REQUEST_POOL .acquire(); if (request == null) { request = new SingleRequest <>(); } request.init (requestContext, target, requestListener, requestCoordinator, engine, animationFactory); return request; }
了解了请求的创建过程后我们继续看下如果使用获取的请求进行加载图片: 我们看下RequestManager track方法:
void track (Target<?> target , Request request) { lifecycle.addListener(target ); requestTracker.runRequest(request); }
track中处理很简单,就是让target监听生命周期后,调用requestTracker.runRequest。还记得上面介绍Target的时候有提到生命周期对Target的影响了吧– 在Target为GifDrawble的时候Target会随着生命周期启动和停止播放动画。
Ok 我们继续看runRequest,上面已经知道request是一个SingleRequest,SingleRequest中启动请求是通过begin方法来完成的。
public void runRequest (Request request ) { requests.add (request); if (!isPaused) { request.begin(); } else { pendingRequests.add (request); } }
begin 方法中会先获取View的尺寸,然后通过onSizeReady传出,实际的加载也是在onSizeReady中进行的。 在开始加载之前先调用onLoadStarted显示占位图片。
public void begin () { if (requestContext.getModel() == null) { onLoadFailed() ; return; } status = Status.WAITING_FOR_SIZE; int overrideWidth = requestContext.getOverrideWidth() ; int overrideHeight = requestContext.getOverrideHeight() ; if (Util . isValidDimensions(overrideWidth , overrideHeight ) ) { onSizeReady(overrideWidth , overrideHeight ) ; } else { target.getSize(this ) ; } if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE) && canNotifyStatusChanged() ) { target.onLoadStarted(requestContext .getPlaceholderDrawable () ); } }
看下onSizeReady,转了大半圈感觉终于走到了正道,之前都是各种做铺垫,在onSizeReady中会通过engine.load进行图片的加载。关于请求的信息都放在了requestContext中。
public void onSizeReady (int width, int height) { if (status != Status.WAITING_FOR_SIZE) { return ; } status = Status.RUNNING; loadedFromMemoryCache = true ; loadStatus = engine.load (requestContext, width, height, this ); loadedFromMemoryCache = resource != null; }
在看load代码之前我先给大家剧透下整个加载流程,大家可以结合下面的代码注释对细节进行查看: 整个流程如下:
检查内存缓存,如果内存缓存中有需要的数据那么就直接使用内存缓存中的数据
如果内存缓存中没有想要的图像数据,那么检查最近的活跃资源(ActiveResources)是否有我们想要的资源
如果ActiveResources也没有,我们就继续检查最近的加载任务,如果存在将回调添加到正在加载的任务中,
上面都没办法获取到我们需要的资源那么就,启动新的加载任务开始加载
可能大家都听说过两级缓存,内存缓存,磁盘缓存,但是Glide在这基础上添加了一层活跃资源缓存,那么什么是活跃资源呢? 活跃资源指的是那些不止一次被加载并没有进行过资源释放的图片,一旦被释放,那么该资源则会从近期活跃资源中删除并进入到内存缓存中,但是如果该资源再次从内存缓存中读取,则会重新添加到活跃资源中
public <Z, R> LoadStatus load(RequestContext<?, R> requestContext, int width, int height, ResourceCallback cb) { requestContext.setDimens(width , height ) ; EngineKey key = keyFactory.buildKey(requestContext , width , height ) ; EngineResource<?> cached = loadFromCache(key ,requestContext .isMemoryCacheable () ); if (cached != null) { cb.onResourceReady(cached ) ; return null; } EngineResource<?> active = loadFromActiveResources(key ,requestContext .isMemoryCacheable () ); if (active != null) { cb.onResourceReady(active ) ; return null; } EngineJob current = jobs.get(key); if (current != null) { current.addCallback(cb ) ; return new LoadStatus(cb , current ) ; } EngineJob<R> engineJob = engineJobFactory.build(key, requestContext.isMemoryCacheable() ); DecodeJob<R> decodeJob = new DecodeJob<>(requestContext, key, width, height, diskCacheProvider, engineJob); jobs.put(key, engineJob); engineJob.addCallback(cb ) ; engineJob.start(decodeJob); return new LoadStatus(cb , engineJob ) ; }
我们先看下内存缓存部分:
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; }
在内存缓存获取到我们所需要的数据后会将其添加到Active Resource中.
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 ); } return result; }
接着我们再看下Active Resource 缓存中获取数据的过程: Active Resource 的缓存数据存在activeResources中,使用弱引用来持有。在内存不足的时候这部分会被gc掉。
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; }
介绍完内存缓存以及Active Resource缓存后我们看下磁盘缓存: 在磁盘缓存开始前会先在当前获取数据的队列中查看是否先前已经启动,如果先前已经启动的话就不重新创建了。这样可以达到复用数据的目的。最后的最后才会启动从磁盘中获取缓存数据的任务。
DecodeJob是一个Runnable的实现类,主要负责从磁盘加载数据,调用start方法后,这个线程就会run起来。 它的作用如下:
确定数据的加载来源(Resource,Data,Source)
创建对应来源的DataFetcherGenerator
执行DataFetcherGenerator 获取数据
public void run () { try { runWrapped(); } catch (RuntimeException e) { callback.onLoadFailed(); throw e; } } private void runWrapped () { switch (runReason) { case INITIALIZE: stage = getNextStage(Stage.INITIALIZE); generator = getNextGenerator(); runGenerators(); break ; case SWITCH_TO_SOURCE_SERVICE: runGenerators(); break ; case DECODE_DATA: decodeFromRetrievedData(); break ; default : throw new IllegalStateException("Unrecognized run reason: " + runReason); } }
我们第一次进入的时候runReason为INITIALIZE,这时候会先通过getNextStage来获取当前阶段的下一阶段。首先在看代码之前我们需要先明白Data和Resource的区别: Resource:原始的图片(或gif)数据 Data:经过处理(旋转,缩放)后的数据
该方法的大致逻辑如下:
如果是初始状态,则判断是否解码已缓存的Resource,true是解码Resource。false的话则会通过递归进入第二个判断分支
判断是否解码已缓存的Data,true是解码Data,false的话则会通过递归进入第三个判断分支
该阶段则需要从数据源去解码。
简单的来说,就是根据Resource—>Data—>source的顺序去解码加载数据,该阶段Stage的确定,影响着下一阶段DataFetcherGenerator相应子类的实例创建
private Stage getNextStage (Stage current) { if (current == null ) { return null ; } DiskCacheStrategy strategy = requestContext.getDiskCacheStrategy(); switch (current) { case INITIALIZE: return strategy.decodeCachedResource() ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE); case RESOURCE_CACHE: return strategy.decodeCachedData() ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE); case DATA_CACHE: return Stage.SOURCE; default : return null ; } }
通过getNextStage已经获取到适当的状态后紧接着就是通过getNextGenerateor生成获取数据的DataFetcherGenerator
private DataFetcherGenerator getNextGenerator () { if (stage == null) { return null; } switch (stage) { case RESOURCE_CACHE: return new ResourceCacheGenerator (width, height, diskCacheProvider.getDiskCache (), requestContext, this ); case DATA_CACHE: return new DataCacheGenerator (requestContext.getCacheKeys (), width, height, diskCacheProvider.getDiskCache (), requestContext, this ); case SOURCE: return new SourceGenerator<>(width, height, requestContext, diskCacheProvider.getDiskCache (), this ); default : throw new IllegalStateException ("Unrecognized stage: " + stage); } }
DataFetcherGenerator使用已注册的ModelLoaders和Model来生成一系列的DataFetcher。有如下实现类 ResourceCacheGenerator:经过处理的资源数据缓存文件(采样转换等处理) DataCacheGenerator:未经处理的资源数据缓存文件 SourceGenerator:源数据的生成器,包含了根据来源创建的ModelLoader和Model(文件路径,URL…)
下面我们一一来看下这些DataFetcherGenerator
####### ResourceCacheGenerator
public boolean startNext() { List<Key> sourceIds = requestContext.getCacheKeys() ; List<Class<?>> resourceClasses = requestContext.getRegisteredResourceClasses() ; while (modelLoaders == null || !hasNextModelLoader() ) { resourceClassIndex++; if (resourceClassIndex >= resourceClasses.size() ) { sourceIdIndex++; if (sourceIdIndex >= sourceIds.size() ) { return false ; } resourceClassIndex = 0 ; } Key sourceId = sourceIds.get(sourceIdIndex); Class<?> resourceClass = resourceClasses.get(resourceClassIndex); Transformation<?> transformation = requestContext.getTransformation(resourceClass ) ; Key key = new ResourceCacheKey(sourceId , requestContext .getSignature () , width, height,transformation,resourceClass, requestContext.getOptions() ); cacheFile = diskCache.get(key); if (cacheFile != null) { this.sourceKey = sourceId; modelLoaders = requestContext.getModelLoaders(cacheFile ) ; modelLoaderIndex = 0 ; } } fetcher = null; while (fetcher == null && hasNextModelLoader() ) { ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++); fetcher = modelLoader.buildLoadData(cacheFile , width , height , requestContext .getOptions () ).fetcher; if (fetcher != null) { fetcher.loadData(requestContext .getPriority () , this); } } return fetcher != null; }
DataCacheGenerator public boolean startNext() { while (modelLoaders == null || !hasNextModelLoader() ) { sourceIdIndex++; if (sourceIdIndex >= sourceIds.size() ) { return false ; } Key sourceId = sourceIds.get(sourceIdIndex); Key originalKey = new DataCacheKey(sourceId , requestContext .getSignature () ); cacheFile = diskCache.get(originalKey); if (cacheFile != null) { this.sourceKey = sourceId; modelLoaders = requestContext.getModelLoaders(cacheFile ) ; modelLoaderIndex = 0 ; } } fetcher = null; while (fetcher == null && hasNextModelLoader() ) { ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++); fetcher = modelLoader.buildLoadData(cacheFile , width , height , requestContext .getOptions () ).fetcher; if (fetcher != null) { fetcher.loadData(requestContext .getPriority () , this); } } return fetcher != null; }
SourceGenerator public boolean startNext() { if (dataToCache != null ) { cacheData(); dataToCache = null ; } if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) { return true ; } sourceCacheGenerator = null ; loadData = null ; while (loadData == null && hasNextModelLoader()) { loadData = loadDataList.get (loadDataListIndex++); if (loadData != null ) { loadData.fetcher.loadData(requestContext.getPriority(), this ); } } return loadData != null ; }
private void cacheData() { try { Encoder<Object> encoder = requestContext.getSourceEncoder(dataToCache ) ; DataCacheWriter<Object> writer = new DataCacheWriter<>(encoder, dataToCache, requestContext.getOptions() ); Key originalKey = new DataCacheKey(loadData .sourceKey , requestContext .getSignature () ); diskCache.put(originalKey, writer); } finally { loadData.fetcher.cleanup() ; } sourceCacheGenerator = new DataCacheGenerator(Collections.singletonList (loadData .sourceKey ) , width, height, diskCache, requestContext, this); }
这里需要注意下,SourceGenerator可以根据磁盘缓存策略选择是直接返回还是先写到磁盘再从缓存文件中加载。
public void onDataReady(Object data ) { DiskCacheStrategy diskCacheStrategy = requestContext.getDiskCacheStrategy(); if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) { dataToCache = data ; cb.reschedule(); } else { cb.onDataFetcherReady(loadData.sourceKey, data , loadData.fetcher, loadData.fetcher.getDataSource()); } }
看完上面的三个DataFetcher后我们看下在DecodeJob中怎么通过这些Fetcher进行获取数据,这就涉及到runGenerators这个方法: 如果获取成功则直接回调onDataFetcherReady,如果失败则通过reschedule重新调度
private void runGenerators() { currentThread = Thread . currentThread() ; while (!isCancelled && generator != null && !generator.startNext() ) { stage = getNextStage(stage ) ; generator = getNextGenerator() ; if (stage == Stage.SOURCE) { runReason = RunReason.SWITCH_TO_SOURCE_SERVICE; callback.reschedule(this); return; } } if (stage == null) { callback.onLoadFailed() ; } }
看完上面代码密密麻麻的,但是最主要的代码就一句generator.startNext()也就是上面介绍DataFetcher的时候重点注解的那个方法,在那个方法中会调用对应的Fetcher来获取数据。后续的部分会着重介绍一个从网络上获取数据的Fetcher,这里先着重介绍流程。好了我们继续: 不论是哪种Fetcher,获取完数据后都会回调DecodeJob里面的onDataFetcherReady
public void onDataFetcherReady(Key sourceKey, Object data , DataFetcher<?> fetcher, DataSource dataSource) { this .currentSourceKey = sourceKey; this .currentData = data ; this .currentFetcher = fetcher; this .currentDataSource = dataSource; if (Thread.currentThread() != currentThread) { runReason = RunReason.DECODE_DATA; callback.reschedule(this ); } else { decodeFromRetrievedData(); } }
private void decodeFromRetrievedData() { //对原始数据进行解码 Resource resource = decodeFromData(currentFetcher, currentData, currentDataSource); if (resource != null) { //通过回调进行返回 callback.onResourceReady(resource); cleanup(); } else { runGenerators(); } }
private Resource decodeFromData(DataFetcher<?> fetcher, Data data, DataSource dataSource) { try { if (data == null) { return null; } Resource result = decodeFromFetcher(data, dataSource); return result; } finally { fetcher.cleanup(); } }
private <Data > Resource<R> decodeFromFetcher(Data data , DataSource dataSource) { LoadPath<Data , ?, R> path = requestContext.getLoadPath((Class<Data >) data .getClass()); if (path != null ) { return runLoadPath(data , dataSource, path); } else { return null ; } }
getLoadPath的任务是从注册表中获取特定数据类型,转换类型的图像解码器:
<Data> LoadPath<Data, ?, TranscodeClass> getLoadPath(Class<Data> dataClass ) { return glideContext.getRegistry() .getLoadPath(dataClass , getResourceClass () , transcodeClass); }
这里调用getLoadPath了从loadPathCache中获取对应数据类型的解码器,loadPathCache 是一个缓存,这个大家见怪不怪了:
public <Data , TResource, Transcode> LoadPath<Data , TResource, Transcode> getLoadPath( Class<Data > dataClass, Class<TResource> resourceClass, Class<Transcode> transcodeClass) { LoadPath<Data , TResource, Transcode> result = loadPathCache.get(dataClass, resourceClass, transcodeClass); if (result == null && !loadPathCache.contains(dataClass, resourceClass, transcodeClass)) { List <DecodePath<Data , TResource, Transcode>> decodePaths = getDecodePaths(dataClass, resourceClass,transcodeClass); if (decodePaths.isEmpty()) { result = null ; } else { result = new LoadPath<>(dataClass, decodePaths); } loadPathCache.put(dataClass, resourceClass, transcodeClass, result); } return result; }
下面是从注册表中获取符合要求的解码器列表的实现:
private < Data , TResource , Transcode > List <DecodePath <Data , TResource , Transcode >> getDecodePaths( Class <Data > dataClass, Class <TResource > resourceClass, Class <Transcode > transcodeClass) { List <DecodePath <Data , TResource , Transcode >> decodePaths = new ArrayList <>(); List <Class <TResource >> registeredResourceClasses = decoderRegistry.getResourceClasses(dataClass, resourceClass); for (Class <TResource > registeredResourceClass : registeredResourceClasses) { List <Class <Transcode >> registeredTranscodeClasses = transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass); for (Class <Transcode > registeredTranscodeClass : registeredTranscodeClasses) { List <ResourceDecoder <Data , TResource >> decoders = decoderRegistry.getDecoders(dataClass, registeredResourceClass); ResourceTranscoder <TResource , Transcode > transcoder = transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass); decodePaths.add(new DecodePath <>(dataClass, decoders, transcoder)); } } return decodePaths; }
经过层层调用后我们就获取到了当前数据的解码器,拿到解码器后不用说要做的事情就是对数据进行解码了,我们看下这部分内容:
private <Data , ResourceType> Resource<R> runLoadPath(Data data , DataSource dataSource, LoadPath<Data , ResourceType, R> path) { return path.load(data , requestContext, width, height, new DecodeCallback<ResourceType>(dataSource)); }
我们接下来看下LoadPath的load方法,这里最关键的部分就是path.decode这个方法,它就是调用从注册表中获取到的解码器到decode方法对数据进行解码的。
public Resource<Transcode> load(Data data, RequestContext<?, Transcode> context, int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) { Preconditions.checkNotNull(data); Resource<Transcode> result = null ; Options options = context.getOptions(); DataRewinder<Data> rewinder = context.getRewinder(data); try { int size = decodePaths.size (); for (int i = 0 ; i < size ; i++) { DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i); result = path.decode(rewinder, width, height, options , decodeCallback); if (result != null ) { break ; } } } finally { rewinder.cleanup(); } return result; }
到这里为止整个数据获取,解码都完成了,那么我们接下来顺着原路返回,看下怎么将这些经过解码后的图片设置到对应的Target上,我们先回到Engine类: 它有一个叫做onEngineJobComplete的回调,是在上面加载数据,解码数据之后对调的。
public void onEngineJobComplete(Key key , EngineResource<?> resource ) { Util .assert MainThread() ; if (resource != null) { resource.setResourceListener(key , this ) ; if (resource.isCacheable() ) { activeResources.put(key, new ResourceWeakReference(key , resource , getReferenceQueue () )); } } jobs.remove(key); }
这里处理很简单就是将加载后的数据添加到activeResources,下一次的时候就可以从activeResources中获取了。
还记得前面介绍Engine.load的时候如果从内存缓存以及Active Resource缓存中获取到数据后是怎么处理的吧。是的就是调用:
cb.onResourceReady(cached ) ;
cb 是啥,看代码可以看出SingleRequest,所以我们看下SingleRequest的onResourceReady
@Override public void onResourceReady(Resource<?> resource ) { Class<R> transcodeClass = requestContext.getTranscodeClass() ; if (resource == null) { onLoadFailed() ; return; } Object received = resource.get() ; if (received == null || !transcodeClass.isAssignableFrom(received .getClass () )) { releaseResource(resource ) ; onLoadFailed() ; return; } if (!canSetResource() ) { releaseResource(resource ) ; status = Status.COMPLETE; return; } onResourceReady((Resource<R>) resource, (R) received); } private void onResourceReady(Resource<R> resource , R result ) { boolean isFirstResource = isFirstReadyResource() ; status = Status.COMPLETE; this.resource = resource; if (requestListener == null || !requestListener.onResourceReady(result , requestContext .getModel () , target,loadedFromMemoryCache, isFirstResource)) { Transition<? super R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource); target.onResourceReady(result , animation ) ; } notifyLoadSuccess() ; }
上面终于看到target了。既然都讲到这了我就再回头看下吧,比如我们当前是一个GifDrawable,那么Target就是DrawableImageTarget 我们就再来看下它的onResourceReady
public void onResourceReady(Drawable resource , Transition<? super Drawable> transition ) { ViewGroup.LayoutParams layoutParams = view.getLayoutParams() ; if (!(resource instanceof Animatable) && layoutParams != null && layoutParams.width > 0 && layoutParams.height > 0 ) { resource = new FixedSizeDrawable(resource , layoutParams .width , layoutParams .height ) ; } super.onResourceReady(resource , transition ) ; if (resource instanceof Animatable) { ((Animatable) resource).start() ; } }
它调用了父类也就是ImageViewTarget的onResourceReady,在这里会调用传入的Transition对图像进行一次转换,然后调用setResource设置到对应的Target上。
public void onResourceReady (Z resource, Transition<? super Z> transition ) { if (transition == null || !transition.transition (resource, this )) { setResource (resource); } }
所以总的就是先将图像转换,设置到ImageView上,然后如果是gif就调用start方法开始播放。整个流程结束了,真他妈累。对了好像还忘记给大家介绍HttpUrlFetcher了。实在讲不动了就贴个标有注释的代码给大家吧。
public class HttpUrlFetcher implements DataFetcher <InputStream> { HttpUrlFetcher(GlideUrl glideUrl, int timeout, HttpUrlConnectionFactory connectionFactory) { this .glideUrl = glideUrl; this .timeout = timeout; this .connectionFactory = connectionFactory; } @Override public void loadData (Priority priority, DataCallback<? super InputStream> callback) { long startTime = LogTime.getLogTime(); InputStream result = null ; try { result = loadDataWithRedirects(glideUrl.toURL(), 0 , null , glideUrl.getHeaders()); } catch (IOException e) { } callback.onDataReady(result); } 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 { try { if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) { throw new IOException ("In re-direct loop" ); } } catch (URISyntaxException e) { } } urlConnection = connectionFactory.build(url); for (Map.Entry<String, String> headerEntry : headers.entrySet()) { urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue()); } urlConnection.setConnectTimeout(timeout); urlConnection.setReadTimeout(timeout); urlConnection.setUseCaches(false ); urlConnection.setDoInput(true ); urlConnection.connect(); if (isCancelled) { return null ; } final int statusCode = urlConnection.getResponseCode(); if (statusCode / 100 == 2 ) { String contentLength = urlConnection.getHeaderField(CONTENT_LENGTH_HEADER); stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength); return stream; } 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()); } } interface HttpUrlConnectionFactory { HttpURLConnection build (URL url) throws IOException; } private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory { @Override public HttpURLConnection build (URL url) throws IOException { return (HttpURLConnection) url.openConnection(); } } }