Android源码设计模式解析与实战笔记

字数 4801阅读 2078

本文首发于微信公众号——世界上有意思的事,搬运转载请注明出处,否则将追究版权责任。微信号:a1018998632,交流qq群:859640274

  • 1.单一职责原则:比如说一个ImageLoader,需要加载图片的缓存图片,此时如果将这两个功能都放在一个类中,就违反了这个原则,
    我们需要将不同的功能用类精细组织起来,然后通过成员变量的形式将功能组合起来。
  • 2.开闭原则:如果我们要在1的基础上增加更多的硬件缓存或者双缓存,此时如果只是在原来的类中使用if进行判断那么就违反了这个原则,因为对于一个类我们需要的是对于修改是关闭的,对于扩展是开发的,此时我们就可以将缓存类定义成抽象的接口,然后将各个缓存的实现,以多态的形式设置在ImageLoader之中,此时我们只需要为ImageLoader设置一个具体ImageCache实现类,而不需要对ImageLoader进行修改。
  • 3.里氏替换原则:在2的基础上,接口的实现需要符合这个原则,这个原则是基于继承和多态这两个性质,而这个原则就是:如果将接口的实现
    或者基类的子类设置进入ImageLoader之中,不会出现任何问题。
  • 4.依赖倒置原则:模块之间的依赖关系通过抽象发送,实现类之间不发生直接的依赖关系,依赖关系是通过接口或者抽象产生的。
  • 5.接口隔离原则:类之间的依赖关系应该建立在最小的接口之上,如果一个接口非常臃肿那么需要将这个接口细分。
  • 6.迪米特原则:一个类应该对自己需要耦合或调用的类知道的越少越好,因为一个类对另一个类了解的越多,在重构的时候另一个类需要改的就越多。
  • 1.单例模式:

    • 1.两次判断法:在getInstance()中先判断是否为null,不为null就不进入加锁区,进入加锁区之后再判断一次。

    • 2.静态成员变量法:使用类的构造方法时初始化静态变量,创建唯一的静态实例

    • 3.按需创建:使用内部类,在不调用getInstance()的情况下,不会加载内部类

    • 4.枚举单例:以上的几个方式在反序列化的时候都会出现重新创建对象的问题,而枚举单例不会,其默认是线程安全的。、

    • 5.例子:LayoutInflater用的就是单例,其是通过ContextImpl的getService()获得的,在该类被第一次加载的时候会使用HashMap注册各种Service用ServiceFetcher包裹,其中就包括LayoutInflater。

    • 6.深入理解LayoutInflater:ServiceFetcher需要实现createService()返回具体的Service,LayoutInflater是通过PolicyManger的makeNewLayoutInflater()创建的,这个类是一个代理类,具体的实现在Policy中,在Policy中返回了一个PhoneLayoutInflater。这个类覆写了LayoutInflater的onCreateView(),为传入的View找到具体路径。rInflate()是解析xml最核心的方法:其通过深度优先遍历的方式来构建传入的xml文件中的视图树,每解析到一个View就递归调用rInflater(),直到这个路径下的最后一个元素然后再回溯将该View添加到parent中。

    • 7.运用单例模式:在ImageLoader中使用该模式

    • 8.优缺点:1.减少内存开支 2.减少性能开销 3.避免对一个西苑的多重占用 4.便于全局访问1.没有接口扩展困难 2.单例持有Context容易造成内存泄露,所以推荐使用Application的Context。

  • 2.Builder模式:适用于:对象初始化复杂、产品类复杂不同调用顺序产生最终类型不同、方法不同调用顺序产生最终结果不同。链式调用直观。

    • 1.例子:AlterDialog中使用到
    • 2.运用Builder:当ImageLoader中要设置的参数多了之后就会出现一个问题就是:比如我已经初始化了线程池了,但是我后面又通过set方法改变了线程数,此时就可以使用Builder方法将所有的字段都隐藏起来,只有第一次初始化的时候使用Builder来进行初始化。
  • 3.原型模式:用户通过从一个样板对象中克隆出一个内部属性一致的对象。(跳过)

  • 4.工厂方法模式:定义一个用于创建对象的接口。让其子类决定实例化哪个对象。复杂的对象时候用这个模式,直接可new的对象不需要用。

    • 1.例子:Activity的onCreate()
    • 2.实践:我们对数据进行持久化存储的时候可能会用到好几种方式:xml文件、普通文件、SQLite。此时我们就可以通过定义一个增删改查的接口,然后实现刚刚那三种方式的类最后使用工厂模式来对这些类进行创建。
  • 5.抽象工厂模式:(跳过)

  • 6.策略模式:策略模式定义了一堆算法并封装起来,使得他们可以互相替换,让算法独立于客户端变化。适用于:一类问题有多种处理方式、同一抽象类有多个子类需要使用if或switch来选择。

    • 1.源码中的实现:动画中的插值器和估值器就使用了这个模式(源码具体分析跳过)
    • 2.实战:比如我们使用ImageLoader加载图片的时候一般是顺序加载,但是如果我们要逆序加载的时候就可以通过策略模式来让用户自己选择加载的策略,然后设置。
    • 3.总结:策略模式主要是用来分离不同的算法,很好演绎的开闭原则。
  • 7.状态模式:结构和策略模式基本一致,但是状态模式是不可内替换的,其只是将对象不同的行为包装在不同的对象中,让状态对象有个抽象基类。当一个对象的行为取决于状态或者有许多开关就可以用。

    • 1.例子:比如遥控电视,此时遥控器就是一个状态的组合,我们用基本类型来标记状态,当状态多起来的时候就会有大量的if代码而难以维护。此时我们可以给各种状态定义一个基类,在遥控器中设置一个这样的基类,扩展基类,在遥控器的各个函数中通过多态来设置当前的状态。这样一来减少了if代码,代码也变得可维护起来。
    • 2.源码例子:WifiManager中使用了这个。(跳过)
    • 3.实战:微博登陆就是一个例子,主界面(有转发和注销功能),登陆界面。当用户没登陆的时候点击转发会跳到登陆否则直接转发。此时一般的方式是使用if的方式判断如何进行跳转。但是如果这里用户的状态多了起来的话就会变得复杂起来。所以我们可以建立一个UserState的抽象类设置在LoginContext(负责设置UserState,维持单例)中,将主界面的操作交给LoginContext,然后扩展出已经登陆和未登陆的子类,最后由多态实现无if的可维护的代码。
  • 8.责任链模式:许多对象都有机会处理某些请求,并且有先后顺序,此时为了避免请求者和接收者的耦合,将接收者对象按照一定的顺序连成一条链式结构,并将请求沿着链传递。

    • 1.例子:现实中公司里报销,当经理报销的钱不够时就会去找总经理然后是大总经理,报销就是一个请求,每一层的经理就是处理者。所以我们可以定义一个请求的基类和一个处理者的基类
      请求的基类中设置目的的行为,处理基类判断是否能处理和将事件转交给下一个处理基类。
    • 2.源码例子:android的事件分发就使用了这个模式,当一个View不能处理本事件的时候就将该事件交给下一个View进行处理。
    • 3.实战:对于有序广播就是类似于责任链模式,只有有接受者将其终止或者所有接受者都不终止。所以我们可以直接实现一个有序广播。
  • 9.解释器模式:用的少跳过

  • 10.命令模式:就是将函数对象化形成一个伪闭包,实现函数式编程。
    1.源码例子:Android事件输入系统
  • 11.观察者模式:将观察者和被观察者解耦,定义对象之间的一对多关系,当一发生变化的所有依赖于这个类的对象会得到通知并被自动更新。
    • 1.例子:Java中提供了这种模式的支持,观察者需要继承Observer,被观察者需要继承Observable。创建一个被观察者,然后将所有观察者在被观察者中注册,然后调用被观察者的消息更新。
    • 2.源码例子:ListView中使用的BaseAdapter就使用了观察者模式,当ListView更新数据之后会调用Adapter的notifyDataSetChanged()BaseAdapter中维持着一个数据观察者DataSetObservable。在这个方法中会调用notifyChanged()。而这里面就是遍历所有的观察者,进行onChanged()的调用。此时DataObservable就是被观察者。而观察者AdapterDataSetObserver在ListView的setAdapter()中创建,其是AdapterView的内部类,其中的onChanged()代码中是调用ListView的requestLayout()对界面进行更新。
    • 3.深入:广播使用了观察者模式,我们注册的广播接收器就是观察者,我们可以在全局进行广播的发送,此时就是被观察者。(粗略看了一下)
  • 12.备忘录模式:保存对象当前状态,在之后恢复状态
    1.源码例子:Activity非正常销毁的时候会使用这个模式。

  • 13.迭代器模式:Java的迭代器

  • 14.模版方法模式: 在某些算法中,算法的步骤是一定的但是某些步骤的方式有所不同,我们将基类定义成模版然后由子类去实现。
    • 1.例子:AsyncTask中使用到了这个模式
    • 2.拓展:Activity的生命周期也实现了这个模式,Android系统启动时候Linux内核会启动init进程--》fork一个zygote进程
      --》启动SystemService进行,里面有各种各样的服务如ActivityManagerService
      --》当启动一个应用时候zygote会fork一个进程然后调用ActivityThread的static main()进行默认Activity的加载
      --》main()中会 创建UI线程的Lopper、创建ActivityThread、调用AT的attach()(Activity和Application的创建入口)、调用Looper的loop()让主线程开始循环
      --》AT的attach()会创建一个ApplicationThread,然后调用远端的AMS,之后就是Activity的创建过程了。
    • 3.实战:上面的ImageLoader,因为加载图片的流程是固定的所以我们可以将这些流程固定在基类中的一个final方法中,此时子类就不能覆盖这些固定的流程,但是各个流程对应的方法可以由子类来自由实现。
  • 15.访问者模式:使用不多跳过
  • 16.中介者模式:通过将一系列需要相互作用的类包装成一个对象,使得他们能够松散耦合,当一个类发生改变时,不会影响到其他对象之间的操作。
    • 1.例子:比如说一台电脑,主板就是中介者,cpu等原件就是同事,此时如果不用中介者模式,所有原件都要互相建立联系,而了之后,可以由主板分配联系。当cpu和某个原件关系改变时不会影响到和其他类的关系。
    • 2.源码例子:Keyguard锁屏功能,KeyguardViewMediator类中有许许多多XXXManager,此时这个类就充当了中介者。
  • 17.代理模式:为其他对象提供一个代理类,来控制这个对象的访问,代理类和被控制对象实现了同样的接口保证了客户端的透明使用。
    • 1.源码例子:ActivityManagerProxy代理了ActivityManagerNative的子类ActivityManagerService。
  • 18.组合模式:就是将一些性质相同的类组合成一个类。简单跳过
  • 19.适配器模式:这种模式使用继承和实现接口或者组合和实现接口的方式,将两个因为接口无法匹配的类一起工作。
    • 1.源码例子:ListView中的Adapter,ListView为了将不同的ItemView展现出来,使用了Adapter。Adapter在ListView的父类AbsListView中。ListView等控件通过Adapter来获取ItemView的数量布局数据等信息,然后getView()返回每个View,ListView将这些View布局在对应位置上,然后再加上复用机制使得ListView运转起来。
    • 2.扩展:RecycleView的设计与实现,与ListView最大的不同在于ListView的布局是通过直接的layoutChildren()来实现,而RecycleView是将布局的职责交给LayoutManager,在设置了LayoutManager之后会调用requestLayout()--》onLayout()--》dispatchLayout()--》LayoutManager#onLayoutChildren()
      --》LayoutManager#fill()其中会计算当前可用空间、循环调用layoutChunk()进行布局、每次子View布局完毕会计算布局偏移量和剩下的可用空间
      --》LayoutManager#layoutChunk()其中会获取当前ItemView(使用了Recycler(其中有几个ViewHolder列表用于缓存ViewHolder)的getViewForPosition()(从几个ViewHolder的缓存列表中看看有没有缓存,没有的话就调用Adapter#onCreateViewHolder()来创建新ViewHolder,最后调用Adapter#onBindViewHolder()将数据绑定到View上))、获取ItemView布局参数、计算ItemView大小、计算ItemView上下左右坐标--》LayoutManager#layoutDecorated()--》调用了ItemView的layout()。
  • 20.装饰模式:动态得给一个对象增加额外的功能,比子类更加灵活,比如java中的IO。
    1.原理:创建一个基本装饰器在里面持有被装饰者的引用,用来调用被装饰者的行为,然后扩展装饰器在调用被装饰者行为的同时调用装饰器的行为,这样就起到了为被装饰者添加额外功能的作用,而且比继承更加灵活。
  • 21.享元模式:系统中存在大量相似的对象、需要缓冲池的场景、对象内部与外部环境无关
    • 1.春运时大量的人买火车票,如果每个人都创建一个车票对象,那么这些对象创建销毁就会很频繁,此时我们可以用HashSet将每个唯一的车票对象构成一个对象池,如果有大量相同的车票我们就不用重复创建。
    • 2.Message中会创建一个链表来维护被回收的Message对象,如果不这样做由于事件驱动会产生大量对象,通过这个模式创建一个50的消息池就避免了上面的问题。
  • 22.外观模式:一个子系统与外部通信的时候以一个统一的对象进行。
    • 1.源码例子:Context是一个抽象类,很多方法都是由其他子系统实现的。Activity是ContextImpl的代理。ContextImpl实现了Activity的跳转、发送广播、启动服务等作用,但是里面的具体实现都是通过子系统来实现的。
    • 2.深度扩展:插件化APK加上动态加载Resource
    • 3.实践:将ImageLoader的功能集成在一个类中,只暴露出一个函数,将内部的细节实现隐藏起来。
  • 23:桥接模式:将抽象部分与实现部分分离,或者一个类需要从多个维度扩展但是又不希望使用继承和实现。
    • 1.例子:一杯咖啡加不加糖和大杯小杯能组成4种类型,但是这里的是两个维度,当然可以用多接口实现来解决这个问题。当我们不想用接口解决的话,就可以使用这种模式。我们可以将杯子的大小作为咖啡的继承标准,然后将加不加糖作为独立的类,在咖啡中引用加不加糖。或者将加不加糖和杯子的大小都当做独立的类被咖啡类引用。而这里的引用就可以被成为桥接模式。
      2.源码例子:如View视图层级,每一种子View描述着本控件,但是每个View的绘制又是由Canvas等类实现,这就是两种不同的维度的扩展。还有就是Adapter和AdapterView,而比较典型的就是Window和WindowManager之间的关系。
    • 3.实战:我们可以定义一系列不同的ProgressBar,继承一个View,作为绘制这个维度的类。然后定义一个ProgressBarImpl作为不同系列(圆形,条形等等)这一维度,最后让继承View的引用Impl。

不贩卖焦虑,也不标题党。分享一些这个世界上有意思的事情。题材包括且不限于:科幻、科学、科技、互联网、程序员、计算机编程。下面是我的微信公众号:世界上有意思的事,干货多多等你来看。

世界上有意思的事

推荐阅读更多精彩内容