概述
主要总结单例模式的特点和几种使用方式,以及在Android源码中单例模式的使用,由于单例模式在android中的广泛使用,所以主要在剖析Android中getSystemService流程(即获取系统服务的流程)中讲述单例模式的使用。
单例模式:
- 单例模式在内存中只有一个实例,减少了内存开销
- 单例模式可以避免对资源的多重占用,例如一个写文件时,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问
- 使用单例模式时,考虑较多的就是多线程的情况下如何防止被多线程同时创建等问题
关键点:
- 构造函数不对外开放,一般为private
- 通过静态方法或者枚举返回单例对象
- 确保单例类的对象有且只有一个,尤其在多线程的环境下
- 确保单例类对象在反序列化时不会重新构建对象
单例模式的使用方式
饿汉模式
|
|
这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。这种方式基于类加载机制避免了多线程的同步问题
懒汉模式
|
|
缺点:效率有点低,每次调用实例都要判断同步锁
双重检验锁(Double CheckLock)
|
|
优点:既能够在需要时初始化单例,又能够保证线程安全,且在初始化后调用getInstance不进行同步锁。
缺点:不同平台编译过程中可能会存在严重安全隐患。
在执行到instance = newSingleton()语句时,最终会编译成多条汇编指令,大致做了3件事:
- 给Singleton实例分配内存
- 调用Singleton()的构造函数,初始化成员字段
- 将instance对象指向分配的内存空间(此时instance就不是null)
在JVM编译的过程中会出现指令重排的优化过程既可能执行顺序为1-3-2,这就会导致当 instance实际上还没初始化,就可能被分配了内存空间,也就是说会出现 instance !=null 但是又没初始化的情况,这样就会导致当切换到其他线程时返回的instance不完整
解决方案:只需将instance定义为 private volatile static Singleton instance = null; 这样就保证instance对象每次都是从主存中读取,就可以使用DCL的写法来完成单例模式。当然,volatile或多或少也会影响到性能。
静态内部类
|
|
调用:
优点:延迟加载,线程安全(java中class加载时互斥的),也减少了内存消耗
枚举单例
上面的几种情况,在反序列化时会出现重新创建对象的情况。通过序列化将一个单例的实例写入到磁盘,然后再读回来,但反序列化时会通过一个特别的钩子函数readResolve()去创建类的一个新的实例,但开发人员可以通过重写该方法杜绝反序列化时重新生成对象,如下所示:
在readResolve方法中instance对象返回,而不是重新创建一个新的对象。但对于枚举,即使反序列化时也不会出现生成新的实例。
枚举单例的使用如下:
调用:
使用容器实现单例
|
|
用SingletonManager 将多种的单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
Android中getSystemService流程
在平时的Android开发中,我们经常会通过Context来获取系统服务,比如ActivityManagerService,AccountManagerService等系统服务,今天我们就来看下getSystemService(String name)的整个调用流程。
Context和ContextImpl
打开Context类,可以看到Context是一个抽象类,那么getSystemService一定是在其实现类来调用的,具体我们都猜得到ContextImpl
当我们启动一个Activity的时候,其实是在ActivityThread#performLaunchActivity方法中启动的,一个Activity的入口是ActivityThread的main方法,看下main方法
可以看到上面在main方法中,调用了自身的attach方法
在main方法中,通过调用activityThread的attach方法,并且参数为false,表示非系统应用,会通过binder与ActivityManagerService通信,并且最后会调用performLaunchActivity方法
可以看到创建Context的具体实现是在ActivityThread#createBaseContextForActivity方法中完成的
可以看到Context的实现类就是ContextImpl,那么关于getSystemService的具体实现也应该在ContextImpl里面了。
getSystemService的具体实现
ContextImpl#getSystemService的实现如下
可以看到实际上ContextImpl也是通过SystemServiceRegistry.getSystemService来获取具体的服务,SystemServiceRegistry是个final类型的类。那么下面看看SystemServiceRegistry类:
可看到实际上获取系统服务是通过ServiceFetcher的getService来获取的,并且SYSTEM_SERVICE_FETCHERS实际上就是一个HashMap实例,通过put方法为它赋值的。registerService方法是用来注册系统服务的,当注册系统服务的时候,就会调用到SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);将当前的服务put到SYSTEM_SERVICE_NAMES集合中
ServiceFetcher是一个接口,又三个实现类:CachedServiceFetcher,StaticServiceFetcher,StaticApplicationContextServiceFetcher,具体不同的服务可能对应不同的实现类。看一下CachedServiceFetcher的实现:
这些系统服务都是在SystemServiceRegistry的static静态代码块中进行注册的,即上面代码中的static代码块。
这里,我们以ActivityManagerService为例来进行学习。在注册该服务的时候,将Context.ACTIVITY_SERVICE作为键,将CachedServiceFetcher作为值,并且重写了createService方法,createService是在当前ServiceFetcher的getService里执行的,这里会返回一个ActivityManager的实例,ctx.mMainThread.getHandler()就是ActivityThread中的H,启动activity最终就会通过ActivityThread$H的handleMessage进行处理,实际上,包括service的启动以及Broadcast的处理都是在H里处理的。
单例模式的应用:
这些服务以键值对的形式存储在一个HashMap(即SYSTEM_SERVICE_FETCHERS)中,用户使用时只需要根据key值获取对应的ServiceFetcher,然后通过ServiceFetcher对象的getService函数来获取具体的服务对象。当第一次获取时,会调用ServiceFetcher的createService函数创建服务对象,然后缓存在一个列表HashMap中,下次再取时直接从缓存中获取,避免重复创建对象,从而达到单例的效果,这里使用单例的方式是使用容器的单例形式。