美文网首页Glide学习
Android Glide4 源码解析--框架初始化

Android Glide4 源码解析--框架初始化

作者: vb12 | 来源:发表于2018-06-28 18:53 被阅读30次

    原文: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2018/0403/9555.html

    这里添加了一些自己的阅读笔记.

    版本 4.x

    一、前言

    在众多的图片加载框架中,Glide是Google推荐的,并在自家的项目中大量使用的一个非常强大的框架,专注于平滑滚动,并且还提供Gif,本地Vedio首帧的解码和显示。Glide提供了非常便捷的链式调用接口,以及丰富的拓展和自定义功能,开发者可以非常简单地对框架进行配置和图片再加工。

    如今Gilde已经更新到4.x,了解其源码对更好的使用Glide,以及学习相关的图片处理技术,学习更优雅的编码会有很大的帮助。

    再github上放了一个最简单的工程, 可以直接拿来研究代码
    https://github.com/shaopx/GlideApp

    当然你要是不嫌麻烦可以去官方地址: 要想正常编译还是要费点劲的.
    https://github.com/bumptech/glide

    不得不说,Glide整个框架的极其复杂的,特别是在对资源的转换和解码过程中,涉及了许多的嵌套循环,同时也使用了大量的工厂模式用于生产转换模块,编码模块,解码模块等,笔者在阅读过程中,多次迷失在茫茫的代码流中。

    为此,萌生了将对Glide的理解记录成文的想法,借以理清思路,也希望这一系列的文章可以帮助到无论是了解,还是准备阅读Glide源码的你,稍微理清一些思路。如有不对的地方,欢迎指正~

    那么接下来,我们就先看看Glide是如何进行框架初始化的。

    注意:本文源码版本为v4.6.1,不同版本可能存在一些差异!

    二、Glide.with发生了什么?

    1. Glide单例的加载

    使用过Glide的都知道,调用Glide加载一张图片时,第一句代码便是Glide.with(this),这里肯定就是Glide的入口了,通过这句代码,Glide开始了“漫漫的”初始化之路。

    Glide重载了多个with的方法,分别用于不同的情境下使用,我们看其中最常用的在Activity中调用的方法,即

    image

    首先,跟进getRetriever(activity)

    image

    这里首先检查了context是否为空,如果为null,抛出异常。

    我们重点来看Glide.get(context)

    image

    这里是一个典型的双检锁单例模式。

    继续跟进checkAndInitialzeGlide(context)

    image image

    注意这里,在最后注入了一个GlideBuilder,这个就是Glide的建造器,用于构建Glide的一些参数和配置。

    最后,来到真正初始化Glide的方法(代码去除了一些Log打印)。

    image

    留意最后将初始化得到的glide赋值给了Glide.glide的单例。

    接下里就来看看在这初始化方法中,Glide都加载了哪些配置。

    2. GlideModule配置加载

    在使用Glide的时候,我们都会有一些想要设置的系统级配置,如设置缓存的存储位置,缓存区的大小,网络加载模块等等,那么我们通常就是使用GldieModule进行配置。在Glide3.x中,我们首先会定义一个继承于GlideModule的类,然后在项目的AndroidMenifest.xml中进行指定:

    1.  `<meta-data  android:name="com.test.GlideConfiguration"`
    2.  `android:value="GlideModule"/>`
    

    而在Glide4中,提供另外一个配置的模式,那就是注解,并且不再继承GlideModule,而是继承AppGlideModule和LibraryGlideModule,分别对应Application和Library,使用@GlideModule注解进行标记。而Glide3.x中的配置方式已经建议放弃使用。

    
        @GlideModule
        public class GlideConfiguration extends AppGlideModule {
            @Override
            public void applyOptions(Context context, GlideBuilder builder) {
                //设置缓存到外部存储器
                builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context)); 
            }
        }
    
    

    那么,Glide是如何对GlideModule的配置进行初始化的呢?

    image

    第二行代码中,getAnnotationGeneratedGlideModules()会获取Glide注解自动生产的一个Glide的Module配置器。如下:

    image

    其中‘com.bumptech.glide.GeneratedAppGlideModuleImpl’是在编译时由Glide生成的一个类,主要用于过滤不必要的GlideModule,以及提供一个请求检索器工厂,这个后面会讲到。

    接下生成一个Manifest解析器ManifestParser,用于获取配置的GlideModule,并存放在manifestModules中。然后是一个判断

    
        if (annotationGeneratedModule != null
                && !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
            ......
        }
    
    

    如果条件成立,即编译时自动生成的类中,包含了需要排除的GlideModule,逐个将其移除。

    接着以上代码,Glide将逐个调用剩下的GlideModule,并回调applyOptions和registerComponents接口,这时,用户配置的GlideModule就会被调用,同时用户设置的参数也就被配置到Glide中。

    image

    在以上代码中,发现一句代码,在回调registerComponents前,首先构建了glide的实例。

    这是一句非常重要的代码,整个Glide框架最重要的初始化内容都在其中实现。

    
    1.  `Glide glide = builder.build(applicationContext);`
    
    
    3. GlideBuilder构建Glide单例

    跳转到GlideBuilder中,看build方法做了哪些事情。代码并不复杂,直接看代码中的注释。

    image

    通过以上一系列工具的新建,Glide建立了资源请求线程池,本地缓存加载线程池,动画线程池,内存缓存器,磁盘缓存工具等等,接着构造了Engine数据加载引擎,最后再将Engine注入Glide,构建Glide。

    其中还建立了一个请求器索引器,用于索引RequestManger,后面我们再详细讲。

    我们进入最后, 构建Glide。

    4. 构建Glide,配置数据转换器/解码器/转码器/编码器

    回到Glide中,看看Glide的构造函数,这是一个长得变态的构造函数(有200行),但是不必被它吓倒(好吧,其实第一次看到这里,我是被吓倒了,直接略过去了,限于文章篇幅,这里只截取了部分源码,仔细的话可以直接看源码),仔细分析一下,其实整个构造过程并没那么复杂。

    image

    其中最重要的是步骤3和步骤4,分别为Glide初始化了模型转换加载器,解码器,转码器,编码器,并将对各种类型进行一一注册,将其列成表格如下:

    • 模型转换器
    转换器 功能
    ResourceLoader.StreamFactory 将Android资源ID转换为Uri,在加载成为InputStream
    ResourceLoader.UriFactory 将资源ID转换为Uri
    ResourceLoader.FileDescriptorFactory 将资源ID转化为ParcelFileDescriptor
    ResourceLoader.AssetFileDescriptorFactory 将资源ID转化为AssetFileDescriptor
    UnitModelLoader.Factory 不做任何转换,返回源数据
    ByteBufferFileLoader.Factory 将File转换为ByteBuffer
    FileLoader.StreamFactory 将File转换为InputStream
    FileLoader.FileDescriptorFactory 将File转化为ParcelFileDescriptor
    DataUrlLoader.StreamFactory 将Url转化为InputStream
    StringLoader.StreamFactory 将String转换为InputStream
    StringLoader.AssetFileDescriptorFactory 将String转换为AssetFileDescriptor
    HttpUriLoader.Factory 将http/https Uri转换为InputStream
    UriLoader.StreamFactory 将Uri转换为InputStream
    UriLoader.FileDescriptorFactory 将Uri转换为ParcelFileDescriptor
    UriLoader.AssetFileDescriptorFactory 将Uri转换为AssetFileDescriptor
    UrlUriLoader.StreamFactory 将将http/https的Uri转换为InputStream
    UrlLoader.StreamFactory 将Url转换为InputStream
    HttpGlideUrlLoader.Factory 将HttpGlide转换为InputStream
    ...... ......
    • 解码器
    解码器 功能
    ByteBufferGifDecoder 将ByteBuffer解码为GifDrawable
    ByteBufferBitmapDecoder 将ByteBuffer解码为Bitmap
    ResourceDrawableDecoder 将资源Uri解码为Drawable
    ResourceBitmapDecoder 将资源ID解码为Bitmap
    BitmapDrawableDecoder 将数据解码为BitmapDrawable
    StreamBitmapDecoder 将InputStreams解码为Bitmap
    StreamGifDecoder 将InputStream数据转换为BtyeBuffer,再解码为GifDrawable
    GifFrameResourceDecoder 解码gif帧
    FileDecoder 包装File成为FileResource
    UnitDrawableDecoder 将Drawable包装为DrawableResource
    UnitBitmapDecoder 包装Bitmap成为BitmapResource
    VideoDecoder 将本地视频文件解码为Bitmap
    • 转码器
    转码器 功能
    BitmapDrawableTranscoder 将Bitmap转码为BitmapDrawable
    BitmapBytesTranscoder 将Bitmap转码为Byte arrays
    DrawableBytesTranscoder 将BitmapDrawable转码为Byte arrays
    GifDrawableBytesTranscoder 将GifDrawable转码为Byte arrays
    • 编码器
    编码器 功能
    ByteBufferEncoder 将Byte数据缓存为File
    StreamEncoder InputStream缓存为File
    BitmapEncoder 将Bitmap数据缓存为File
    BitmapDrawableEncoder 将BitmapDrawable数据缓存为File
    GifDrawableEncoder 将GifDrawable数据缓存为File
    • 模型转换注册表(实在太多,只列出了部分)
    源数据 转换数据 转换器
    Integer.class InputStream.class ResourceLoader.StreamFactory
    Integer.class ParcelFileDescriptor.class ResourceLoader.FileDescriptorFactory
    ...... ...... ......
    String.class InputStream.class DataUrlLoader.StreamFactory
    String.class InputStream.class StringLoader.StreamFactory
    ...... ...... ......
    Uri.class InputStream.class DataUrlLoader.StreamFactory
    Uri.class InputStream.class HttpUriLoader.Factory
    Uri.class InputStream.class UriLoader.StreamFactory
    URL.class InputStream.class UrlLoader.StreamFactory
    ...... ...... ......

    以上模型转换注册表非常重要,在Glide进入解码流程时,将会遍历这里注册的所有可能转换的情形,尝试进行数据转换。

    这里只列出部分情形,其它还包括File/Bitmap/Drawable/Byte等等几乎涵括了日常使用的情况。

    Glide的加载流程可以概括为以下流程:

    model(数据源)-->data(转换数据)-->decode(解码)-->transformed(缩放)-->transcoded(转码)-->encoded(编码保存到本地)

    其中,transformed为对解码得到的图片数据进行缩放,如FitCenter、CropCenter等。

    到这里,Glide单例就构建完成了,让我们返回到Glide#with中

    image

    在构建好Glide后,通过getRequestManagerRetriever()将会得到一个RequestManagerRetriever,即RequestManager的检索器,RequestManagerRetriever#get()将为每个请求页面创建一个RequestManager。

    还记得GlideBuilder#build提到的一句代码吗?

    
    1.  `RequestManagerRetriever requestManagerRetriever =`
    2.  `new  RequestManagerRetriever(requestManagerFactory);`
    
    

    没错,这里获取的就是它。这里就必须要讲到Glide数据请求的生命周期了。

    我们都知道Glide会根据页面的生命周期来自动的开启和结束数据的请求,那么Glide是怎么做到的呢?

    5. 生命周期管理

    我们进入RequestManagerRetriever#get(Activity)方法中。

    image

    首先,判断是否为后台线程,如果是,则使用ApplicationContext重新获取。
    重点来看else代码块。先断言请求的页面是否已经销毁。否则获取当前页面的FragmentManager,并传给fragmentGet方法。

    image

    在fragmentGet中首先通过getRequestManagerFragment()来获取一个命名为FRAGMENT_TAG的fragment,如不存在,则新建一个RequestManagerFragment,并添加到当前页面中。

    这里我们就可以猜到了,Glide是通过在页面中添加一个Fragment来动态监听页面的创建和销毁,从而达到依赖页面生命周期,动态管理请求的目的。

    image

    在RequestManagerFragment构造函数中,注入了一个生命周期监听器ActivityFragmentLifecycle,并在Fragment各个生命周期回调中,调用了对应的方法。

    而ActivityFragmentLifecycle也紧接着会调用lifecycleListener监听器,而这个监听器其实就是RequestManger。如下:

    image

    最后,RequestManagerRetriever#fragmentGet,判断这个Fragment的RequestManager是否存在,否则创建一个RequestManager,并将生命周期注入,同时RquestManager构建时,将会通过addListener注入生命周期回调(具体可以查看RequestManger构造函数)。

    最后,Glide#with终将得到一个RequestManager。

    至此,Glide的加载过程就解析完毕了。总结一下整个流程:

    • 通过AndroidManifest和@GlideModule注解获取用户自定义配置GlideModule,并调用其对应的方法
    • 通过GlideBuilder构建Glide:
      1.新建线程池
      2.新建图片缓存池和缓存池
      3.新建内存缓存管理器
      4.新建默认本地缓存管理器
      5.新建请求引擎Engine
      6.新建RequestManger检索器
      7.新建Glide
    • Glide构造方法中,新建模型转换器,解码器,转码器,编码器,以及生成Glide上下文GlideContext
    • 通过RequestManager检索器,建立生命周期监听,并建立一个RequestManager
    • 完成!

    三、 Glide与GlideApp

    如果在项目中已经使用了Glide3.x,并且想要升级到Glide4.x,那么你会发现,原来使用链式调用进行参数配置的方法已经被修改了,同一个封装到了RequesOptions中,如下:

        RequestOptions options = new RequestOptions()
                .centerCrop()
                .placeholder(R.mipmap.ic_launcher_round)
                .error(R.mipmap.ic_launcher)
                .priority(Priority.HIGH)
                .diskCacheStrategy(DiskCacheStrategy.NONE);
        Glide.with(this)
                .load(ImageConfig.URL_GIF)
                .apply(options)
                .into(iv);
    
    

    这样的话升级后将导致大量的修改,当然你也可以自己封装一下,但是Glide已经为我们做好了兼容方案。

    还记得初始化是通过@GlideModule注解来注册自定义配置吗?只要在项目中定义这么一个配置,那么Glide将会自动帮我们生成一个GlideApp模块,封装了Glide3.x中的调用方式。

    
        public class GlideConfiguration extends AppGlideModule {
            @Override
            public void applyOptions(Context context, GlideBuilder builder) {
            }
        }
    
    

    调用如下,还是原来的配方,还是熟悉的味道~

    
        GlideApp.with(this)
                .load(ImageConfig.URL_WEBP)
                .sizeMultiplier(0.5f)
                .centerCrop()
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .error(R.mipmap.ic_launcher)
                .into(iv);
    
    

    如果你还觉得不爽,那么你甚至可以把GlideApp直接修改为Glide,实现几乎“无缝对接”。当然,你还是要修改引用路径的。

    
        @GlideModule(glideName="Glide")
        public class GlideConfiguration extends AppGlideModule {
            @Override
            public void applyOptions(Context context, GlideBuilder builder) {
            }
        }
    

    以上,就是Glide4初始化的源码解析了

    相关文章

      网友评论

        本文标题:Android Glide4 源码解析--框架初始化

        本文链接:https://www.haomeiwen.com/subject/smdnyftx.html