Glide 源码初探(3.7.0)

1 缘起

近期新的项目中用到了广受好评的的图片加载库 Glide,着实提升了不少开发效率。以文档中最常见的代码为例

Glide.with(this).load("http://goo.gl/gEgYUd").into(imageView);

一行就能解决图片的自动加载,缓存,显示的问题。

2 核心类

Glide : 以单例模式提供了一种简单的静态接口,采用 RequestBuilder 来构建相关的请求。
GlideContext : Glide中所有负载(all loads)的全局上下文,包含以及对外提供多样化注册机制,以及加载资源所需的类类型。
Engine : 负载启动加载以及管理活动的,缓存的资源。
RequestManager : 管理以及发起请求(request)。
RequestTracker : 跟踪,取消,重启进行中的,已完成的,以及失败的请求。
SingleRequest : 为一个Target加载一种Resource的请求(request)。

3 分析

3.1 Glide的初始化

加载图片有着不同的使用场景,Glide利用 Facade 的模式做出了良好的封装。

Glide.with(X) 提供的统一接口

让我们随即进入任一实现的代码中,最后发现了Glide的初始化逻辑:

private static volatile Glide glide;
public static Glide get(Context context) {
    if (glide == null) {
      synchronized (Glide.class) {
        if (glide == null) {
          initGlide(context);
        }
      }
    }
    return glide;
}

是的,“未能免俗的” Glide也采用了双重检查锁定(Double-Check Locking)实现了单例模式。显而易见的是,对于整个APP来说图片加载库有一个实例就够了。

3.2 加载相关的View dimension 的确定

如其官方文档所述 :

"Glide is smart enough to figure out the sizes of Views that use layout weights or match_parent as well as the sizes of those that have concrete dimensions."

它是如何做到的?让我们追踪下本文初始位置给出的第一行示例代码的具体调用过程:

Glide
.with(Fragment/Activity/View)
 + RequestManager -> LifecycleListener
   + load(model)
     RequestBuilder<Drawable>.load(model)
     + loadGeneric(model)
     + into(Y target) // Y extends Target<TranscodeType>
     + into(ImageView view)
         into (a DrawableImageViewTarget ) // Y target
           buildRequest(target)
             -buildRequestRecursive()
               -obtainRequest()
                 -SingleRequest.obtain
           target.setRequest(request); // set as Tag for ViewTarget
           requestManager.track(target, request);
             requestTracker.runRequest(request);
               request.begin();
                 #SingleRequest.begin()
                     target.getSize(this);
                       // view.addPreDrawListener
                       // callback callled onSizeReady async
                     target.onLoadStarted() -> apply your placeholder

原来正在发起图片请求前,有一个获取target size的异步调用。继续来看相关的ViewTargetgetSize()方法:

ViewTarget.getSize(cb)
    sizeDeterminer.getSize(cb);
        ViewTreeObserver observer = view.getViewTreeObserver();
        layoutListener = new SizeDeterminerLayoutListener(this);
        observer.addOnPreDrawListener(layoutListener);
            onPreDraw()
                sizeDeterminer.checkCurrentDimens();
                    // consider padding 
                    int currentWidth = getTargetWidth();
                int currentHeight = getTargetHeight();
                    notifyCbs(currentWidth, currentHeight);
cb.onSizeReady(width, height);

此处可以发现,由于View自身会经历不同的阶段(measure - layout - draw),在当次调用时,其大小可能有不确定性。加载过程如果在view大小未确定前,会对目标View的PreDraw事件加入监听,以得到最终确定的大小。而这也为后续支持的scale type: center_crop 和 fit_center 做好了铺垫。

3.3 对视图层生命周期的智能处理

由于Android View层组件Fragment,Activity 有自身的生命周期,Glide如何封装生命周期的变化,动态管理请求。我们可以从 RequestManager 创建之初的设置看出一二:

private RequestManager supportFragmentGet(Context context, FragmentManager fm,
      Fragment parentHint) {
    SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      // TODO(b/27524013): Factor out this Glide.get() call.
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(glide, current.getLifecycle(), current.getRequestManagerTreeNode());
      current.setRequestManager(requestManager);
    }
    return requestManager;
}

其中 SupportRequestManagerFragment为UI 无关的Fragment,它被创建并且添加到当前的Activity或是主Fragment之下。它与RequestManager建立关联关系并能安全的存储它。
requestManager = factory.build(glide, current.getLifecycle(), current.getRequestManagerTreeNode());
即将Non-UI Fragment的生命周期关联到了当前的requestManager上。所对应的是,View层上的变化onStart,onStop,onDestroy最终会映射到对request的启动,暂停以及清除。

3.4 对于组件扩展的支持

让我们重新关注到Glide初始化时,所做的具体事宜。

initGlide()
    // ...
    manifestModules = new ManifestParser(applicationContext).parse();

    GlideBuilder builder = new GlideBuilder()
            .setRequestManagerFactory(factory);
            
    module.applyOptions(applicationContext, builder);
    annotationGeneratedModule.applyOptions(applicationContext, builder);
    
    glide = builder.build(applicationContext);
        // initialize executors, memorySizeCalculator, bitmapPool, caches
        
        // Responsible for starting loads and managing active and cached resources
        engine = new Engine()

        requestManagerRetriever = new RequestManagerRetriever(
                requestManagerFactory);
        
        return new Glide(...);
           // Glide Constructor
            registry = new Registry();
            registry.register(ByteBuffer.class, new ByteBufferEncoder())
                    .register(InputStream.class, new StreamEncoder(arrayPool))
                    /* Bitmaps */
                    .append(ByteBuffer.class, Bitmap.class,
                        new ByteBufferBitmapDecoder(downsampler))
                    .append(InputStream.class, Bitmap.class,
                        new StreamBitmapDecoder(downsampler, arrayPool))
                    .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
                    //...
            glideContext = new GlideContext(...)
    
    // eg. OKHttp integration
    // registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
    for (GlideModule module : manifestModules) {
          module.applyOptions(applicationContext, builder);
    }
    
    module.registerComponents(applicationContext, glide.registry);
    annotationGeneratedModule.registerComponents(applicationContext, glide.registry);

代码中的manifestModules包含了从manifest中解析得到的模块,对于模块非空的情况,这里也提供了替换的逻辑:

module.registerComponents(applicationContext, glide.registry);

以OKHttp为例,如果设置了对应的module,这将会替换Glide默认的网络模块,而采用配置的 OKHttp Client。

4 典型的调用栈

Glide
.with(Fragment/Activity/View)
 + RequestManager -> LifecycleListener
   + load(model)
     RequestBuilder<Drawable>.load(model)
     + loadGeneric(model)
     + into(Y target) // Y extends Target<TranscodeType>
     + into(ImageView view)
         into (a DrawableImageViewTarget ) // Y target
           buildRequest(target)
             -buildRequestRecursive()
               -obtainRequest()
                 -SingleRequest.obtain
           target.setRequest(request); // set as Tag for ViewTarget
           requestManager.track(target, request);
             requestTracker.runRequest(request);
               request.begin();
                 # SingleRequest.begin()
                     target.getSize(this);
                       // view.addPreDrawListener
                       // callback callled onSizeReady async
                     target.onLoadStarted() -> apply your placeholder
                   
                   onSizeReady()  // req.status = Status.RUNNING;
                     Engine.load()
                       // check the cache 
                       // check the active resources
                       EngineJob<R> engineJob = engineJobFactory.build(...)
                       DecodeJob<R> decodeJob = decodeJobFactory.build(..., engineJob)

                       engineJob.start(decodeJob);
                         decodeJob.run() // with runReason = RunReason.INITIALIZE;
                           runWrapped()
                             // state-machine 
                             // INIT- {RES_CACHE} -{DATA_CACHE}-{SOURCE}|FINISHED
                             getNextGenerator()
                             runGenerators()
                                reschedule() // with Stage.SOURCE // reason = RunReason.SWITCH_TO_SOURCE_SERVICE

                             SourceGenerator.startNext() 
                               loadData = helper.getLoadData().get() // ModelLoader registable ?
                                 modelLoader.buildLoadData
                               loadData.fetcher.loadData(cb)// async
                             SourceGenerator.onDataReady()
                         decodeJob.onDataFetcherReady()
                           decodeFromRetrievedData
                             -decodeFromData(currentFetcher, currentData, currentDataSource);
                                decodeFromFetcher(data, dataSource)
                                 runLoadPath()
                                   LoadPath#Resource<Transcode> load(..., new DecodeCallback(...))
                                     loadWithExceptionList()
                                       DecodePath#decode()
                                         decodeResource()
                                           decodeResourceWithList
                                             decoder.decode(data, width, height, options);
                                       callback.onResourceDecoded
                                 onResourceDecoded()
                                   appliedTransformation.transform()
                             -notifyEncodeAndRelease
                                notifyComplete
                                  callback.onResourceReady(resource, dataSource)
                         engineJob.onResourceReady()
                           MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
                         handleResultOnMainThread()
                           cb.onResourceReady(engineResource, dataSource);

                 # SingleRequest.onResourceReady(Resource<?> resource, DataSource dataSource)
                     onResourceReady(Resource<R> resource, R result, DataSource dataSource)
                       if(!requestListener.onResourceReady())
                         target.onResourceReady(result, animation)
                           ImageViewTarget.onResourceReady()
                             setResourceInternal()
                               view.setImageDrawable(resource);

5 框架小结

  • 封装组件的具体实现

  • 支持组件的可配置化(registry)

  • 对Application view layer的高效运用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容