Glide 用到的设计模式,你知道多少?

前言

Glide 作为一个像Android SDK 一样的第三方图片框架,久经各大项目的考验,read the fuck glide source code ,深入其架构思想,设计模式,对于提升我们的编程能力是无可厚非的 。

创造型设计模式

单例模式

volatile + 双重校验方式 ,确保Glide 在整个应用只有一个实例 。使用volatile 防止指令重排序(new 对象包含分配内存,初始化,引用赋值 三条指令) ,因为可能获得一个未初始化完成的Glide , Glide 引用虽然不为空 ,但是里面的变量因为没有初始化所以可能为null ,这种情况使用就会出问题。双重校验方式确保glide 只能 new 一次。

工厂模式

将实例的构建和使用分隔开,降低代码耦合度,可以实现对象的灵活配置 , 封装对象的创建逻辑,简化对象的使用,提供对象的复用和管理,说了这么多,我们来看下Glide怎么用的。

  • 简单工厂

glide 通过工厂构建不同类型的线程池,很好理解。

  • 工厂方法

每一个具体实例对应一个具体工厂

例子:用来感知网络连接状态的接口ConnectivityMonitorFactory和 ConnectivityMonitor,和高层RequestManager。

ConnectivityMonitorFactory在Glide 初始化被构建,因为ConnectivityMonitorFactory生产的ConnectivityMonitor构建需要依赖 android 中的Context 上下文,context在 Glide 初始化中传入。 ConnectivityMonitor在RequestManager中使用,我们不可能把context 传到RequestManager中去吧?RequestManager只用于管理请求,不需要耦合android context 相关的,这样才符合单一职责原则,所以我们需要一个ConnectivityMonitorFactory工厂,RequestManager 作为Client 要使用ConnectivityMonitor, 只需要从ConnectivityMonitorFactory工厂中获得就行了。

从依赖倒置原则来看高层RequestManager 只依赖ConnectivityMonitorFactory和 ConnectivityMonitor抽象,不依赖具体实现细节,也是符合依赖倒置原则的。从接口隔离原则来看,RequestManager 需要感知网络状态只依赖ConnectivityMonitor和其工厂类,没有多余的接口依赖,也符合接口隔离原则。从里氏替换原则来看,ConnectivityMonitor 和ConnectivityMonitorFactory所有实现子类都可以出现在RequestManager,也符合里氏替换原则。从迪米特法则来看,RequestManager需要感知网络变化,需要用到context ,但又不是直接和context耦合,借助ConnectivityMonitor 来实现,也符合迪米特法则。

构建者设计模式

为什么要使用构建者设计模式 ?支持链式调用,避免构造参数列表过长,提高构造对象的灵活性和可配置性,使代码更加可维护

如GlideBuilder

使用Builder 模式,简化Glide 的创建,好处显而易见不多解释了。

原型模式

通过原型模式,我们可以根据一个现有对象创建出新的对象,并且可以灵活地修改新对象的属性,而不会影响原始对象。这样可以避免重复创建相似的对象,提高性能和代码复用性。克隆细分又分深拷贝和浅拷贝。听君一席话,如听一席话,我们看下Glide怎么做的。

Glide 哪里用到了原型模式呢? 在构建请求对象的请求参数就用到了,如下。

为什么要用clone,对应什么场景?其实就这个问题我也思考良久,让我们自己写图片加载框架估计就直接new 请求参数,然后把请求参数都塞进去,又不是不能用,这样确实是能用并且一点问题都没有。但是作为一个有梦想的程序员肯定是代码能优化地方就要优化的。例如下面这个场景

把一张图片加载到多个imageView 上面去,这种需求还是很常见的。这种情况下请求参数大部分信息都是可以共用的,就imageView不一样而已,我clone 一个出来复用大部分相同的请求参数信息,性能和内存不都优化了?何乐而不为,然后不同的部分 deep copy一下就行了。

行为型设计模式

观察者设计模式

glide 通过添加透明Fragment来监听activity /framgent 生命周期,这里提一下Glide为什么在子线程使用不会去监听生命周期,因为可能会导致内存泄露。在生命周期onDestroy 清理加载请求、onStop加载暂停、onStart恢复图片加载请求,用到了观察者设计模式。与之相关两个接口定义如下

LifecycleListener 是抽象观察者,方法有onStart/Stop/Destory,Lifecycle是抽象被观察者,方法有add/removeListener。RequestManager 作为具体观察者,实现了LifecycleListener。除了RequestManager实现了观察者接口是观察者外,还有ConnectivityMonitor ,TargetTracker,Target 都继承/实现LifecycleListener都是观察者,观察者比较简单不多讲了。

接下来讲讲glide 核心代码所用的责任链设计模式,通过责任链设计模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。

责任链设计模式

如下 , ResourceCache(不需要解码和变换直接可以显示) ,DataCache(需要解码和变换) , Source 分别对应三个Generator,前两个从本地磁盘缓存中获取的图片数据,后面则直接从源(网络或者app本地资源文件中)Glide 按照配置依次从中获取图片加载数据。

这是责任链模式?怎么看都不像啊,责任链定义不是要将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。也就是说责任链模式都要有请求参数,就glide 而言这里给Generator的请求参数decodeHelper早在Generator初始化就传入了,就不用在startNext()方法中传入decodeHelper了。

代码都是死的,但是思想都是一样的,其实上面的代码也可以改成标准责任链模式。责任链设计模式有什么好处?责任链设计模式能够解耦请求发送者和接收者,简化对象之间的交互,提供灵活的处理顺序,使得系统更加灵活、可扩展和易于维护。它适用于需要动态组合和处理多个相关对象的场景,特别是在处理复杂的业务逻辑时,可以将复杂性分解成简单的处理步骤。

就Glide 而言,将解码请求发送者DecodeJob 高层对象和各种解码具体处理对象ResourceCacheGenerator等解耦,以及具体请求对象之间都通过责任链模式解耦了,DecodeJob作为高层对象无需关注解码细节,只需关注解码的整体流程就行。除了解耦之外还提供良好的扩展性,比如不需要从磁盘缓存中获取已解码的图片缓存, 只需要去掉return new ResourceCacheGenerator就行了,如下。

策略模式

glide 用于判断支不支持从磁盘缓存中获取图片就用到了策略模式

DiskCacheStrategy 有几个子类ALL 表示全部支持, NONE 什么都不支持。默认使用 AUTOMATIC 只有从网络才支持Data 缓存,并且从网络获取中如果使用了Data 缓存则不支持ResourceCache。我们要改缓存策略的话可以在请求参数中修改DiskCacheStrategy。

另外还有解码的时候向下采样也用到了策略模式,根据imageView 的scaleType 分别对应了几种策略。

结构型设计模式

装饰模式

通过装饰者模式我们可以在运行时动态地给对象添加额外的功能,而不需要改变原始对象的结构。这使得我们可以灵活地组合对象并添加所需的功能,从而满足不同的需求,接下来我们看下glide 中怎么使用的。

Glide 从moedl -> data 这个过程就用到了装饰者设计模式,与之相关的接口是 ModelLoader<Model, Data> 以及实现此接口的子类。假设model 为string情况 ,这种情况对应的有表示网络请求的url ,本地文件 uri ,asset uri ,图片文件路径等,此时ModelLoader 具体实现类为StringLoader。

StringLoader 为被装饰者,构造中传入装饰者uriLoader ,uriLoader装饰者给StringLoader动态扩展功能,但又不会修改和影响StringLoader整体结构。uriLoader给StringLoader 扩展了什么功能呢?比如支持从网络中获取图片的HttpGlideUrlLoader ,支持从ContentResolver 获取图片的UriLoader,以及支持从 Asset目录下获取图片的AssetUriLoader等等。如果不使用装饰者设计模式,把这些功能实现都写在StringLoader 中,可想而知StringLoader 中代码量有多大,有多难维护了。

除了装饰设计模式其实ModelLoader还用到了组合设计模式。

组合设计模式

前面说到StringLoader 要动态新增功能使用了装饰设计模式,思考一下如果新增那么多功能是不是要给每个功能都建立一个装饰类?如果一两个功能还好,但是类多的话就爆炸了,有没有更好的办法去解决呢?

glide使用组合模式来规避了这个问题,引入一个MultiModelLoader ,顾名思义这个里面可以放很多个ModelLoader,把那些装饰StringLoader的装饰者都放在MultiModelLoader里,MultiModelLoader 把这些装饰者组合起来,这样可以避免类爆炸的问题。

有些人会认为你把这些装饰者都放一起这不是严重耦合?不符合六大法则啊,程序设计讲究高内聚低耦合,你这耦合这么严重以后怎么维护啊,你这代码不保熟啊。

看代码其实就知道并不是这样的,MultiModelLoader只是依赖了各种装饰者的抽象,并没有依赖各个装饰者的具体实现,具体装饰者的代码修改完全不影响MultiModelLoader 。从依赖倒置原则来看,MultiModelLoader 作为client 高层对象依赖抽象ModelLoader ,各个具体装饰者如HttpGlideUrlLoader 也依赖抽象ModelLoader ,MultiModelLoader和HttpGlideUrlLoader通过ModelLoader 抽象实现了依赖倒置。从里氏替换原则来,ModelLoader抽象可以出现的地方,ModelLoader的各种子类装饰者也可以出现在MultiModelLoader中。从迪米特法则和单一职责来看,MultiModelLoader只用于管理统筹多个ModelLoader,不耦合具体实现 。

享元设计模式

每次去加载图片都要一个DecodeJob 去执行图片加载任务,如果一段时间内执行成百上千个图片加载请求,有可能会造成内存抖动,有没有可以优化的地方呢?glide使用享元模式来复用DecodeJob对象,优化和节省内存的使用,如下。

在DecodeJob release的时候将其放入池子中回收。

glide 中不止DecodeJob对象用了享元模式来复用对象优化内存, 还有很多对象比如EngineJob,Bitmap等都是用池子来复用的。

这里提出个问题,为什么每次去加载图片基本都会新建一个Request ,为什么Request 没有用享元模式呢?

glide 中用到的设计模式还有优秀的设计思想远远不止此文所述,一篇文章终究是难以说清,但学习方法都是一样的,就是read the fuck glide source code ,把glide 的皮一层一层拨开。

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

<think>好的,用户现在问的是Android常用的设计模式。首先,我需要回顾一下之前的历史对话,用户之前问过动态权限申请框架,可能是一个Android开发者,特别是新手或者中级开发者,想要提升自己的代码结构。现在他想了解设计模式,可能是因为在开发过程中遇到了代码维护或架构上的问题,想要学习如何更好地组织代码。 接下来,我要确定用户的需求。设计模式有很多种,但Android开发中常用的有哪些呢?需要分门别类,并且给出具体的例子,这样用户更容易理解。用户可能希望得到一个结构清晰、分类明确的回答,可能包括创建型、结构型、行为型模式,以及一些Android特有的模式,比如MVVM之类的架构模式。 然后,我需要考虑回答的结构。可能先概述设计模式的重要性,再分点介绍每种模式,每个模式给出定义、使用场景、Android中的例子,以及优缺点。还要注意使用中文,符合之前的格式要求,比如代码示例用正确的语法,行内公式用$...$,独立公式用$$...$$,不过在设计模式中可能公式较少,但如果有的话需要注意。 还要注意用户之前的问题中提到的动态权限框架,可能用户对实际应用场景比较感兴趣,所以在举例时要结合Android开发中的实际案例,比如Activity的启动用到了工厂模式,Retrofit使用建造者模式等,这样用户更容易理解。 另外,用户可能需要知道如何选择合适的设计模式,所以在最后给出选择建议,根据项目规模和团队经验推荐不同的模式。同时,注意事项部分要提醒不要过度设计,结合架构模式使用,保持代码简洁。 需要检查是否有遗漏的常用模式,比如单例模式在Android中的应用需要注意内存泄漏,观察者模式在LiveData中的使用,这些都很重要。还有MVVM虽然属于架构模式,但用户可能也会感兴趣,可以简要提及。 最后,确保回答符合用户要求的格式,使用标题、代码块、列表等,结构清晰,帮助用户逐步理解。避免使用过于专业的术语,保持解释通俗易懂,同时保证准确性。</think>以下是对Android开发中常用设计模式的系统介绍,按模式类型分类并附上典型应用场景及代码示例: \#\# 一、创建型模式 1. **单例模式 (Singleton)** - 应用场景:全局配置类、日志工具、数据库访问对象 - Android实现要点: ```kotlin class AppConfig private constructor() { companion object { @Volatile private var instance: AppConfig? = null fun getInstance(): AppConfig = instance ?: synchronized(this) { instance ?: AppConfig().also { instance = it } } } } // 注意:Android中需谨慎使用单例,避免持有Context导致内存泄漏 ``` 2. **建造者模式 (Builder)** - 典型应用:AlertDialog构建、Retrofit配置 - 示例: ```java AlertDialog dialog = new AlertDialog.Builder(context) .setTitle("提示") .setMessage("确认删除?") .setPositiveButton("确定", null) .create(); ``` 3. **工厂模式 (Factory)** - 应用场景:Fragment实例化、网络请求工厂 - 代码示例: ```kotlin interface ApiClient { fun request(): Response } class ApiFactory { fun create(type: String): ApiClient = when(type) { "retrofit" -> RetrofitClient() "volley" -> VolleyClient() else -> throw IllegalArgumentException() } } ``` \#\# 二、结构型模式 4. **适配器模式 (Adapter)** - Android典型实现:RecyclerView.Adapter - 代码结构: ```kotlin class MyAdapter(val dataList: List<Data>) : RecyclerView.Adapter<ViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { // 创建视图 } override fun onBindViewHolder(holder: ViewHolder, position: Int) { // 绑定数据 } } ``` 5. **装饰器模式 (Decorator)** - 应用场景:Java IO流处理、自定义View增强 - 示例: ```java InputStream input = new BufferedInputStream( new FileInputStream("test.jpg")); ``` 6. **代理模式 (Proxy)** - Android应用:Binder跨进程通信、Retrofit接口代理 - 动态代理示例: ```java public interface ApiService { @GET("user/info") Call<User> getUserInfo(); } Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL).build(); ApiService service = retrofit.create(ApiService.class); // 动态代理实现 ``` \#\# 三、行为型模式 7. **观察者模式 (Observer)** - Android实现:LiveData事件通知、RxJava - LiveData示例: ```kotlin val liveData = MutableLiveData<String>() liveData.observe(this, Observer { value -> updateUI(value) }) liveData.postValue("新数据") ``` 8. **策略模式 (Strategy)** - 应用场景:图片加载策略(Glide/Picasso切换)、支付方式选择 - 代码示例: ```java interface CompressionStrategy { void compress(File file); } class ImageProcessor { private CompressionStrategy strategy; public void setStrategy(CompressionStrategy strategy) { this.strategy = strategy; } public void processImage() { strategy.compress(imageFile); } } ``` 9. **模板方法模式 (Template Method)** - Android应用:AsyncTask执行流程、BaseActivity封装 - 示例: ```java abstract class BaseActivity extends Activity { // 模板方法 protected final void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initData(); setupViews(); bindEvents(); } abstract void initData(); abstract void setupViews(); abstract void bindEvents(); } ``` \#\# 四、Android特化模式 10. **依赖注入模式** - 实现框架:Dagger/Hilt、Koin - Hilt示例: ```kotlin @HiltViewModel class MyViewModel @Inject constructor( private val repository: DataRepository ) : ViewModel() { ... } ``` 11. **MVVM架构模式** - 核心组件:ViewModel + LiveData + DataBinding - 数据绑定示例: ```xml <layout> <data> <variable name="viewModel" type="com.example.MyViewModel"/> </data> <TextView android:text="@{viewModel.userName}"/> </layout> ``` \#\# 五、模式选择建议 - 基础功能开发:优先使用$$观察者模式$$、$$适配器模式$$等基础模式 - 复杂业务逻辑:结合$$策略模式$$、$$模板方法模式$$进行解耦 - 架构设计:采用$$MVVM$$+$$依赖注入$$等组合模式 - 性能敏感场景:慎用$$装饰器模式$$(可能产生对象嵌套) \#\# 六、注意事项 1. 避免过度设计:简单功能直接实现,不需要强制套用模式 2. 组合优于继承:优先使用对象组合(如$$策略模式$$)而非类继承 3. 关注Android特性:考虑生命周期管理(如ViewModel的自动保存) 4. 与架构模式配合:结合Clean Architecture、MVI等现代架构 建议通过Android官方架构组件(如ViewModel、LiveData、Room)来自然应用这些设计模式,新项目推荐采用MVVM+Repository模式作为基础架构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值