Android Glide图片加载框架(二)源码解析之with()

文章目录

  • 一、前言
  • 二、如何阅读源码
  • 三、源码解析
    • 1、with()


Android Glide图片加载框架系列文章

Android Glide图片加载框架(一)基本用法

Android Glide图片加载框架(二)源码解析之with()

Android Glide图片加载框架(二)源码解析之load()

Android Glide图片加载框架(二)源码解析之into()

Android Glide图片加载框架(三)缓存机制


一、前言


在本系列的上一篇文章中,我们学习了Glide的基本用法,体验了这个图片加载框架的强大功能,以及它非常简便的API。还没有看过上一篇文章的朋友,建议先去阅读 Android Glide图片加载框架(一)基本用法

在多数情况下,我们想要在界面上加载并展示一张图片只需要一行代码就能实现,如下所示:

Glide.with(this).load(url).into(img);

虽说只有这简简单单的一行代码,但大家可能不知道的是,Glide在背后帮我们默默执行了成吨的工作。这个形容词我想了很久,因为我觉得用非常多这个形容词不足以描述Glide背后的工作量,我查到的英文资料是用tons of work来进行形容的,因此我觉得这里使用成吨来形容更加贴切一些。

虽说我们在平时使用Glide的时候格外地简单和方便,但是知其然也要知其所以然。那么今天我们就来解析一下Glide的源码,看看它在这些简单用法的背后,到底执行了多么复杂的工作。


二、如何阅读源码


在开始解析Glide源码之前,我想先和大家谈一下该如何阅读源码,这个问题也是我平时被问得比较多的,因为很多人都觉得阅读源码是一件比较困难的事情。


那么阅读源码到底困难吗?


这个当然主要还是要视具体的源码而定。比如同样是图片加载框架,我读Volley的源码时就感觉酣畅淋漓,并且对Volley的架构设计和代码质量深感佩服。读Glide的源码时却让我相当痛苦,代码极其难懂。当然这里我并不是说Glide的代码写得不好,只是因为Glide和复杂程度和Volley完全不是在一个量级上的。

那么,虽然源码的复杂程度是外在的不可变条件,但我们却可以通过一些技巧来提升自己阅读源码的能力。这里我和大家分享一下我平时阅读源码时所使用的技巧,简单概括就是八个字:抽丝剥茧、点到即止

应该认准一个功能点,然后去分析这个功能点是如何实现的。但只要去追寻主体的实现逻辑即可,千万不要试图去搞懂每一行代码都是什么意思,那样很容易会陷入到思维黑洞当中,而且越陷越深。因为这些庞大的系统都不是由一个人写出来的,每一行代码都想搞明白,就会感觉自己是在盲人摸象,永远也研究不透。如果只是去分析主体的实现逻辑,那么就有比较明确的目的性,这样阅读源码会更加轻松,也更加有成效。

而今天带大家阅读的Glide源码就非常适合使用这个技巧,因为Glide的源码太复杂了,千万不要试图去搞明白它每行代码的作用,而是应该只分析它的主体实现逻辑。

那么我们本篇文章就先确立好一个目标,就是要通过阅读源码搞明白下面这行代码:

Glide.with(this).load(url).into(img);

到底是如何实现将一张网络图片展示到ImageView上面的。先将Glide的一整套图片加载机制的基本流程梳理清楚,然后我们再通过后面的几篇文章具体去了解Glide源码方方面面的细节。


准备好了吗?那么我们现在开始。


既然是要阅读Glide的源码,那么我们自然需要先将Glide的源码下载下来。其实如果你是使用在 build.gradle 中添加依赖的方式将Glide引入到项目中的,那么源码自动就已经下载下来了,在Android Studio中就可以直接进行查看。

不过,使用添加依赖的方式引入的Glide,我们只能看到它的源码,但不能做任何的修改,如果你还需要修改它的源码的话,可以到GitHub上面将它的完整源码下载下来。

Glide的GitHub主页的地址是:https://github.com/bumptech/glide

不过在这个地址下载到的永远都是最新的源码,有可能还正在处于开发当中。而我们整个系列都是使用Glide 4.8.0这个版本来进行讲解的,因此如果你需要专门去下载4.8.0版本的源码,可以到这个地址进行下载:https://github.com/bumptech/glide/tree/v4.8.0


三、源码解析


1、with()


with() 方法是Glide类中的一组静态方法,它有好几个方法重载,我们来看一下Glide类中所有 with() 方法的方法重载:

在这里插入图片描述

public class Glide{...@NonNullpublic static RequestManager with(@NonNull Context context) {return getRetriever(context).get(context);}@NonNullpublic static RequestManager with(@NonNull Activity activity) {return getRetriever(activity).get(activity);}@NonNullpublic static RequestManager with(@NonNull FragmentActivity activity) {return getRetriever(activity).get(activity);}@NonNullpublic static RequestManager with(@NonNull Fragment fragment) {return getRetriever(fragment.getActivity()).get(fragment);}@SuppressWarnings("deprecation")@Deprecated@NonNullpublic static RequestManager with(@NonNull android.app.Fragment fragment) {return getRetriever(fragment.getActivity()).get(fragment);}@NonNullpublic static RequestManager with(@NonNull View view) {return getRetriever(view.getContext()).get(view);}@NonNullprivate static RequestManagerRetriever getRetriever(@Nullable Context context) {// Context could be null for other reasons (ie the user passes in null), but in practice it will// only occur due to errors with the Fragment lifecycle.Preconditions.checkNotNull(context,"You cannot start a load on a not yet attached View or a Fragment where getActivity() "+ "returns null (which usually occurs when getActivity() is called before the Fragment "+ "is attached or after the Fragment is destroyed).");return Glide.get(context).getRequestManagerRetriever();}...
}

可以看到,with() 方法的重载种类非常多,既可以传入 Activity ,也可以传入 Fragment 或者是 Context 。每一个 with() 方法重载的代码都非常简单,都是先调用 getRetriever() 方法获取 RequestManagerRetriever 对象,然后再调用 RequestManagerRetriever 的实例 get() 方法,去获取 RequestManager 对象。

RequestManagerRetriever 的实例 get() 方法中的逻辑是什么样的呢?我们一起来看一看:

public class RequestManagerRetriever implements Handler.Callback {/*** The top application level RequestManager.*/private volatile RequestManager applicationManager;@NonNullprivate RequestManager getApplicationManager(@NonNull Context context) {// Either an application context or we're on a background thread.if (applicationManager == null) {synchronized (this) {if (applicationManager == null) {// Normally pause/resume is taken care of by the fragment we add to the fragment or// activity. However, in this case since the manager attached to the application will not// receive lifecycle events, we must force the manager to start resumed using// ApplicationLifecycle.// TODO(b/27524013): Factor out this Glide.get() call.Glide glide = Glide.get(context.getApplicationContext());applicationManager =factory.build(glide,new ApplicationLifecycle(),new EmptyRequestManagerTreeNode(),context.getApplicationContext());}}}return applicationManager;}@NonNullpublic RequestManager get(@NonNull 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);}@NonNullpublic RequestManager get(@NonNull FragmentActivity activity) {if (Util.isOnBackgroundThread()) {return get(activity.getApplicationContext());} else {assertNotDestroyed(activity);FragmentManager fm = activity.getSupportFragmentManager();return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));}}@NonNullpublic RequestManager get(@NonNull Fragment fragment) {Preconditions.checkNotNull(fragment.getActivity(),"You cannot start a load on a fragment before it is attached or after it is destroyed");if (Util.isOnBackgroundThread()) {return get(fragment.getActivity().getApplicationContext());} else {FragmentManager fm = fragment.getChildFragmentManager();return supportFragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible());}}@SuppressWarnings("deprecation")@NonNullpublic RequestManager get(@NonNull Activity activity) {if (Util.isOnBackgroundThread()) {return get(activity.getApplicationContext());} else {assertNotDestroyed(activity);android.app.FragmentManager fm = activity.getFragmentManager();return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));}}@SuppressWarnings("deprecation")@NonNullpublic RequestManager get(@NonNull View view) {if (Util.isOnBackgroundThread()) {return get(view.getContext().getApplicationContext());}Preconditions.checkNotNull(view);Preconditions.checkNotNull(view.getContext(),"Unable to obtain a request manager for a view without a Context");Activity activity = findActivity(view.getContext());// The view might be somewhere else, like a service.if (activity == null) {return get(view.getContext().getApplicationContext());}// Support Fragments.// Although the user might have non-support Fragments attached to FragmentActivity, searching// for non-support Fragments is so expensive pre O and that should be rare enough that we// prefer to just fall back to the Activity directly.if (activity instanceof FragmentActivity) {Fragment fragment = findSupportFragment(view, (FragmentActivity) activity);return fragment != null ? get(fragment) : get(activity);}// Standard Fragments.android.app.Fragment fragment = findFragment(view, activity);if (fragment == null) {return get(activity);}return get(fragment);}@SuppressWarnings("deprecation")@Deprecated@NonNull@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)public RequestManager get(@NonNull android.app.Fragment fragment) {if (fragment.getActivity() == null) {throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");}if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {return get(fragment.getActivity().getApplicationContext());} else {android.app.FragmentManager fm = fragment.getChildFragmentManager();return fragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible());}}@SuppressWarnings("deprecation")@Deprecated@NonNullRequestManagerFragment getRequestManagerFragment(Activity activity) {return getRequestManagerFragment(activity.getFragmentManager(), /*parentHint=*/ null, isActivityVisible(activity));}@SuppressWarnings("deprecation")@NonNullprivate RequestManagerFragment getRequestManagerFragment(@NonNull final android.app.FragmentManager fm,@Nullable android.app.Fragment parentHint,boolean isParentVisible) {RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);if (current == null) {current = pendingRequestManagerFragments.get(fm);if (current == null) {current = new RequestManagerFragment();current.setParentFragmentHint(parentHint);if (isParentVisible) {current.getGlideLifecycle().onStart();}pendingRequestManagerFragments.put(fm, current);fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();}}return current;}@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})@Deprecated@NonNullprivate RequestManager fragmentGet(@NonNull Context context,@NonNull android.app.FragmentManager fm,@Nullable android.app.Fragment parentHint,boolean isParentVisible) {RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);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.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);current.setRequestManager(requestManager);}return requestManager;}@NonNullprivate SupportRequestManagerFragment getSupportRequestManagerFragment(@NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {SupportRequestManagerFragment current =(SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);if (current == null) {current = pendingSupportRequestManagerFragments.get(fm);if (current == null) {current = new SupportRequestManagerFragment();current.setParentFragmentHint(parentHint);if (isParentVisible) {current.getGlideLifecycle().onStart();}pendingSupportRequestManagerFragments.put(fm, current);fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();}}return current;}@NonNullprivate RequestManager supportFragmentGet(@NonNull Context context,@NonNull FragmentManager fm,@Nullable Fragment parentHint,boolean isParentVisible) {SupportRequestManagerFragment current =getSupportRequestManagerFragment(fm, parentHint, isParentVisible);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.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);current.setRequestManager(requestManager);}return requestManager;}
}

上述代码虽然看上去逻辑有点复杂,但是将它们梳理清楚后还是很简单的。 RequestManagerRetriever 类中看似有很多个 get() 方法的重载,什么Context参数,Activity参数,Fragment参数等等,实际上只有两种情况而已,即传入Application类型的参数,和传入非Application类型的参数

  • Application类型参数: 如果在 Glide.with() 方法中传入的是一个 Application对象 ,那么这里就会调用带有Context参数的get()方法重载,然后会在第48行调用 getApplicationManager() 方法来获取一个 RequestManager 对象。其实这是最简单的一种情况,因为Application对象的生命周期即应用程序的生命周期,因此Glide并不需要做什么特殊的处理,它自动就是和应用程序的生命周期是同步的,如果应用程序关闭的话,Glide的加载也会同时终止。

  • 非Application类型参数: 不管你在 Glide.with() 方法中传入的是Activity、FragmentActivity、v4包下的Fragment、还是app包下的Fragment,最终的流程都是一样的,那就是 会向当前的Activity当中添加一个隐藏的Fragment 。具体添加的逻辑是在上述代码的第176行和第215行,分别对应的app包和v4包下的两种Fragment的情况。


这里为什么要添加一个隐藏的Fragment呢?


因为Glide需要知道加载的生命周期。很简单的一个道理,如果你在某个Activity上正在加载着一张图片,结果图片还没加载出来,Activity就被用户关掉了,那么图片还应该继续加载吗?当然不应该。可是 Glide并没有办法知道Activity的生命周期,于是Glide就使用了添加隐藏Fragment的这种小技巧,因为Fragment的生命周期和Activity是同步的,如果Activity被销毁了,Fragment是可以监听到的,这样Glide就可以捕获这个事件并停止图片加载了

这里额外再提一句,从第54行代码可以看出,如果我们是在非主线程当中使用的Glide,那么不管你是传入的Activity还是Fragment,都会被强制当成Application来处理 。不过其实这就属于是在分析代码的细节了,本篇文章我们将会把目光主要放在Glide的主线工作流程上面,后面不会过多去分析这些细节方面的内容。

总体来说,第一个with()方法的源码还是比较好理解的。其实就是为了得到一个RequestManager对象而已,然后 Glide会根据我们传入with()方法的参数来确定图片加载的生命周期 ,并没有什么特别复杂的逻辑。不过复杂的逻辑还在后面等着我们呢,接下来我们开始分析第二步,load()方法。




本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/446764.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机操作系统生产者和消费者模型的简单介绍

同步互斥小口诀 画图理解题目判断题目类型分析进程数目 填写进程模板补充基本代码(伪代码)补充PV代码检查调整代码 注意事项 代码是一步一步写出来的&#xff0c;代码是反复调整写出来的60%是生产者和消费者模型30%是读者和写者的模型 生产者和消费者 例子1 妈妈每次放放一…

Android Glide图片加载框架(二)源码解析之load()

文章目录一、前言二、源码分析1、load()Android Glide图片加载框架系列文章 Android Glide图片加载框架&#xff08;一&#xff09;基本用法 Android Glide图片加载框架&#xff08;二&#xff09;源码解析之with() Android Glide图片加载框架&#xff08;二&#xff09;源码…

计算机操作系统读者和写者模型的简单介绍以及思考

读者和写者 读写两组进程&#xff0c;共享一个文件&#xff0c;多个读者可以同时访问文件&#xff0c;多个写者不可以同时访问文件&#xff0c;写者和读者也不可以同时访问文件共享读&#xff1b;独占写特征:1,资源被谁占有&#xff1b;2&#xff0c;写者改变资源&#xff0c;…

Android Glide图片加载框架(二)源码解析之into()

文章目录一、前言二、源码解析1、into(ImageView)2、GlideContext.buildImageViewTarget()3、RequestBuilder.into(Target,RequestListener,RequestOptions);4、RequestBuilder.buildRequest()5、SingleRequest.obtain()6、isEquivalentTo()、isSkipMemoryCacheWithCompletePre…

2014年考研英语一翻译知识点

题目讲解网址 总结 1.做翻译题,不用看句子前后的地方,直接看要翻译的部分 2.多根据语境去翻译 3.如果是不认识的单词,一般都是我们平常经常使用/说的词的代替高级词 题目句子 It is also the reason why when we try to describe music with words, all wecan do is articul…

计算机操作系统 死锁问题

概念 条件是基础&#xff0c;在一定的原因下&#xff0c;产生结果死锁三胞胎 死锁 僵持&#xff0c;消耗时间&#xff0c;双方都占用了部分资源&#xff0c;不释放活锁 双方互相谦让&#xff0c;都不占用资源饥饿 谦让的一方一直等待&#xff0c;无法占有资源&#xff0c;导致…

武忠祥.高等数学.基础课-第一章函数 极限 连续P10

sin(1/x) 详细解析网址 1.图像 2.极限 x–>0时,函数极限不存在 sin2x 详细作图网址 1.图像 2.周期为Π f(x)周期为T,f(axb)周期为T/|a| 所以sinx周期为2Π,sin2x周期为2Π/2Π |sinx| 详细讲解网址 1.图像 2.周期:Π 3.绝对值 &#xff08;1&#xff09;y|sinx|的图…

算法章节 递归、排序、⼆分查找

递归 概念与特性函数调⽤函数⾃身的编程⽅式叫做递归&#xff0c;调⽤为”递“&#xff0c;返回为”归“三个条件1. ⼀个问题的解可以分解为多个⼦问题的解&#xff1b; 2. 分解之后的⼦问题&#xff0c;除了数据规模不同&#xff0c;求解思路跟原问题相同&#xff1b; 3. 存在…

codeforces 50A-C语言解题报告

50A题目网址 解题报告-others 题目解析 1.输入n x m大小的木板,使用21大小的多米诺去填满,求最多的多米诺数目 2.通过分析把木板分为奇数和偶数的情况 1)有一边是偶数的情况: 使用2去填满 2)两个边都是奇数 奇数-1偶数 还是让木板的(奇数-1)边去和2平行,再加上 (m-1)/2(n/1)…

Java命令:jps — 查看进程信息

文章目录一、简介二、常用命令1、jps2、jps -l3、jps -q4、jps -m5、jps -v6、jps失效一、简介 JVM Process Status Tool&#xff0c;显示指定系统内所有的HotSpot虚拟机进程。 功能&#xff1a; 显示当前所有java进程pid的命令&#xff0c;我们可以通过这个命令来查看到底启…

操作系统概述 记录操作系统相关知识

操作系统 现代计算机系统由一个或多个处理器、主存、打印机、键盘、鼠标、显示器、网络接口以及各种输入/输出设备构成。上面提到的这些东西都属于硬件资源&#xff0c;用户不会直接和硬件进行交互&#xff0c;计算机安装了一层软件&#xff0c;这层软件能够通过响应用户输入的…

JDK工具使用大全

文章目录一、简介一、简介 在JDK的bin目录下有很多命令行工具&#xff1a; 常用工具使用详解如下&#xff1a; Java命令&#xff1a;jps — 查看进程信息 Java命令&#xff1a;jstack — 获取线程dump信息 Java命令&#xff1a;jmap — 打印指定进程的共享对象内存映射或…

Linux进程 excel族函数的用法

介绍 使用fork创建一个进程之后&#xff0c;经常会在新进程中调用exec函数执行别的程序当前进程调用exec函数之后&#xff0c;这个进程会被完全替代换成新的程序&#xff0c;即便如此仍然是同一个进程&#xff0c;进程ID不变函数族 execl execlp execle execvp execvpe头文件 …

C++primer 12章 动态内存和智能指针

C引入智能指针的目的 使用智能指针来管理动态分配的对象&#xff0c;当一个对象应该被释放的时候&#xff0c;指向他的智能指针确保自动释放它 内存分配 静态内存&#xff1a;局部static对象、类static数据成员、定义在任何函数之外的变量栈内存&#xff1a;定义在函数内的非…

Mac下iTerm2的安装与配置

目录一、iTerm2简介二、下载以及安装三、iTerm2主题配置四、配置Oh My Zsh1、安装方式&#xff08;1&#xff09;一键安装&#xff08;2&#xff09;手动安装3、切换zsh4、修改主题五、配置Meslo字体六、声明高亮七、自动建议填充八、iTerm2快速隐藏和显示九、iTerm2隐藏用户名…

Java命令:jinfo — 查看进程参数

目录一、简介二、常用命令1、jinfo -flags pid : 打印当前指定java进程中已经设定的所有JVM参数信息2、jinfo -flag pid : 打印指定名称的参数3、jinfo -flag [|-] pid : 打开或关闭参数4、jinfo -sysprops pid : 打印当前java进程中设定的系统环境参数一、简介 jinfo 是 JDK …

C++primer第八章 IO库 8.1 IO类

IO库设施 istream &#xff08;输入流&#xff09;类型&#xff0c;提供输入操作。ostream &#xff08;输出流&#xff09;类型&#xff0c;提供输出操作。cin,—个 istream对象&#xff0c;从标准输入读取数据。cout, 一个ostream对象&#xff0c;向标准输出写入数据。cerr…

2014年英语一作文partB漫画作文

题目 Write an essay of 160-200 words based on the following drawing.In your essay you should describe the drawing brieflyexplain its intended meaning,give your comments 做题点 1.使用三段式,第一段:图片内容;第二段:图片暗示;第三段:写自己的评论 2.描述图片…

Spring Cloud 系列之 Nacos 配置中心

目录一、Nacos简介二、Nacos安装及配置1、环境准备2、安装包下载&#xff08;1&#xff09;源码方式&#xff08;2&#xff09;发行包方式3、启动Nacos服务4、Nacos数据库配置&#xff08;1&#xff09;MySQL数据源&#xff08;2&#xff09;初始化 MySQL 数据库&#xff08;3&…

C++primer第八章 IO库 8.2 文件输入输出

8.2文件输入输出 头文件fstream定义了三个类型来支持文件IO&#xff1a;ifstream从一个给定文件读取数据&#xff0c;ofstream向一个给定文件写入数据&#xff0c;以及fstream可以读写给定文件。在17.5.3节中&#xff08;第676页&#xff09;我们将介绍如何对同一个文件流既读…