单例设计模式
单例设计模式的要点是单例,也就是说整个系统中只有一个实例对象,用于协调整个系统的资源和行为。适用于单例模式的场景有:
1、一个对象的创建需要消耗巨大的资源,如ImageLoader。一个系统中创建多个这样的实例,整个系统都会吃不消了。
2、用于控制系统全局的对象,用于协调整个提供的行为和资源。一个系统只需要一个控制者,正所谓一山不容二虎。
如何创建单例模式
1、饿汉式
public class A {
private static final A sInstance = new A();
public static A getInstance() {
return sInstance;
}
}
饿汉式在类加载的时候就已经实例化了对象,即便是根本没有用到这个对象。只要import了这个类,那么这个对象就在系统中存在了。所以,这在某些场景下会存在一定程度的资源浪费。但是,如果设计了一个单例类,然而却没用到,这恐怕也是非常不合理的设计。饿汉式有个极大的优点,那就是对象实例化非常的简单直接,由于是通过ClassLoader加载类的时候实例化对象,同时又避免了多线程问题。
2、懒汉式
public class A {
private static A sInstance;
private A() {}
public static A getInstance() {
if (sInstance == null) {
sInstance = new A();
}
return sInstance;
}
}
懒汉式遵循的原则是用到时再实例化,与饿汉式刚好相反。秉承不浪费资源的原则,懒汉式似乎要优于饿汉式。其实不然,懒汉式的这种写法也存在弊端,那就是多线程问题。在复杂的并发环境下,sInstance很可能会被实例化多次。
3、DCL模式
public class A {
private static A sInstance;
private A() {}
public static A getInstance() {
if (sInstance == null) {
synchronized (A.class) {
if (sInstance == null) {
sInstance = new A();
}
}
}
return sInstance;
}
}
DCL模式是专门用来解决懒汉式的多线程问题的,通过加锁的方式避免多线程的重入问题。这种方式在早期的JDK版本上也有可能存在问题,原因就是sInstance = new A()这行代码,编译器会先给sInstance赋值,然后再实例化A对象。这种情况下,另外一个线程可能检测到sInstance不为空进而直接使用这个对象,实际上这个对象还没有实例化完成。但是在高版本JDK上,SUN公司进行了优化,就不存在这个问题了.DCL模式是日常编码常用的一种实现单例的方式。
好了,接下来看下Android源码中对单例模式的使用吧。
Android源码中单例模式的使用
Android源码中的单例模式实际上是很多的,Context有一个方法:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
这个方法肯定很常用了,作用是获取系统服务。系统服务,顾名思义就是用来服务整个系统的,这刚好符合前面说的单例应用场景2。Wms、Pms这些服务在Android系统中都是以单例的形式存在的,这可以很方便的供系统中的App调用,前提是只要你有Context。
接下来,我们来梳理一下Andorid中这些服务单例的初始化和使用,以LayoutInflater为例:
1、服务的创建
Context的实现是ContextImpl,我们先看ContextImpl的getSystemService方法:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
逻辑很简单有没有,就是从一个Map数组里把相应的服务给拿出来了。那我们看下服务是怎么放到HashMap里面的。
static {
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
}
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
简单的理解,SystemServiceRegistry类在加载的时候会调用registerService方法将服务放进了HashMap中,后面等用到的时候再从数组中取出。
2、LayoutInflater原理分析
LayoutInflater的实现是PhoneLayoutInflater:
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
/** Override onCreateView to instantiate names that correspond to the
widgets known to the Widget factory. If we don't find a match,
call through to our super class.
*/
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
这个类继承自LayoutInflater,重点是重写了onCreateView方法,作用是为每个View的路径添加前缀,类似android.widget.Button。这种添加前缀的方式很像装饰器设计模式,为LayoutInflater添加装饰功能。