1. 单例模式介绍
单例模式是应用最广的模式之一,在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。如在一个应用中,应该只有一个 ImageLoader 实例,这个 ImageLoader 中又含有线程池、缓存系统、网络请求等,很消耗资源,因此,没有理由让它构造多个实例。这种不能自由构造对象的情况,就是单例模式的使用场景。
2. 单例模式的定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
3. 单例模式的使用场景
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如,创建一个对象需要消耗的资源过多,如要访问 IO 和数据库等资源,这时就要考虑使用单例模式。
4. 单例模式 UML 类图
UML 类图如图所示。
角色介绍如下:
(1)Client ------ 高层客户端
(2)Singleton ------ 单例类
实现单例模式主要有如下几个关键点:
(1)构造函数不对外开放,一般为 Private;
(2)通过一个静态方法或者枚举返回单例类对象;
(3)确保单例类的对象有且只有一个,尤其是在多线程环境下:
(4)确保单例类对象在反序列化时不会重新构建对象。
通过将单例类的构造函数私有化,使得客户端代码不能通过 new 的形式手动构造单例类的对象。单例类会暴露一个公有静态方法,客户端需要调用这个静态方法获取到单例类的唯一对象,在获取这个单例对象的过程中需要确保线程安全,即在多线程环境下构造单例类的对象也是有且只有一个,这也是单例模式实现中比较困难的地方。
5. 单例模式的简单示例
单例模式是设计模式中比较简单的,只有一个单例类,没有其他的层次结构与抽象。该模式需要确保该类只能生成一个对象,通常是该类需要消耗较多的资源或者没有多个实例的情况。例如,一个公司只有一个 CEO、一个应用只有一个 Application 对象等。下面以公司里的 CEO 为例来简单演示一下,一个公司可以有几个 VP、无数个员工,但是 CEO 只有一个,请看下面示例。
示例实现代码:
java
/**
* 普通员工
*/
class Staff {
public void work() {
// 干活
}
}
// 副总裁
class VP extends Staff {
@Override
public void work() {
// 管理下面的经理
}
}
// CEO, 饿汉单例模式
class CEO extends Staff {
private static final CEO mCeo = new CEO();
// 构造函数私有
private CEO() {
}
// 公有的静态函数,对外暴露获取单例对象的接口
public static CEO getCeo() {
return mCeo;
}
@Override
public void work() {
// 管理VP
}
}
// 公司类
class Company {
private List<Staff> allPersons = new ArrayList<Staff>();
public void addStaff(Staff per) {
allPersons.add(per);
}
public void showAllStaffs() {
for (Staff per : allPersons) {
System.out.println("Obj : " + per.toString());
}
}
}
public class Test {
public static void main(String[] args) {
Company cp = new Company();
// CEO对象只能通过getCeo函数获取
Staff ceo1 = CEO.getCeo();
Staff ceo2 = CEO.getCeo();
cp.addStaff(ceo1);
cp.addStaff(ceo2);
// 通过new创建VP对象
Staff vp1 = new VP();
Staff vp2 = new VP();
// 通过new创建Staff对象
Staff staff1 = new Staff();
Staff staff2 = new Staff();
Staff staff3 = new Staff();
cp.addStaff(vp1);
cp.addStaff(vp2);
cp.addStaff(staff1);
cp.addStaff(staff2);
cp.addStaff(staff3);
cp.showAllStaffs();
}
}
输出结果如下:
java
Obj:com.android.dp.book.chapter2.company.CE0@5e8fce95
Obj:com.android.dp.book.chapter2.company.CE0@5e8fce95
Obj:com.android.dp.book.chapter2.company.VP@3343c8b3
Obj:com.android.dp.book.chapter2.company.VP@222d2a10
Obj:com.android.dp.book.chapter2.company.Staff@laa8c488
Obj:com.android.dp.book.chapter2.company.Staff@@3dfeca64
Obj:com.android.dp.book.chapter2.company.Staff0@22998b08
从上述的代码中可以看到,CEO 类不能通过 new 的形式构造对象,只能通过 CEO.getCEO() 函数来获取,而这个 CEO 对象是静态对象,并且在声明的时候就已经初始化,这就保证了 CEO 对象的唯一性。从输出结果中发现,CEO 两次输出的 CEO 对象都是一样的,而 VP、Staff 等类型的对象都是不同的。这个实现的核心在于将 CEO 类的构造方法私有化,使得外部程序不能通过构造函数来构造 CEO 对象,而 CEO 类通过一个静态方法返回一个静态对象。
6. 单例模式的其他实现方式
6.1 懒汉模式
懒汉模式是声明一个静态对象,并且在用户第一次调用 getInstance 时进行初始化,而上述的饿汉模式(CEO 类)是在声明静态对象时就已经初始化。懒汉单例模式实现如下。
java
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
getInstance() 方法中添加了 synchronized 关键字,也就是 getInstance 是一个同步方法,这就是上面所说的在多线程情况下保证单例对象唯一性的手段。细想一下,大家可能会发现一个问题,即使 instance 已经被初始化(第一次调用时就会被初始化 instance),每次调用 getlnstance 方法都会进行同步,这样会消耗不必要的资源,这也是懒汉单例模式存在的最大问题。
最后总结一下,懒汉单例模式的优点是单例只有在使用时才会被实例化,在一定程度上节约了资源;缺点是第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用 getInstance 都进行同步,造成不必要的同步开销。这种模式一般不建议使用。
6.2 Double Check Lock(DCL) 实现单例
DCL 方式实现单例模式的优点是既能够在需要时才初始化单例,又能够保证线程安全,且单例对象初始化后调用 getInstance 不进行同步锁。代码如下所示:
java
public class Singleton {
private static Singleton sInstance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (sInstance == null) {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
本程序的亮点都在 getInstance 方法上,可以看到 getInstance 方法中对 instance 进行了两次判空:第一层判断主要是为了避免不必要的同步,第二层的判断则是为了在 null 的情况下创建实例。这是什么意思呢?
假设线程 A 执行到 sInstance = new Singleton() 语句,这里看起来是一句代码,但实际上它并不是一个原子操作,这句代码最终会被编译成多条汇编指令,它大致做了 3 件事情:
(1)给 Singleton 的实例分配内存;
(2)调用 Singleton() 的构造西数,初始化成员字段;
(3)将 sInstance 对象指向分配的内存空间(此时 sInstance 就不是 null 了)。
但是,由于 Java 编译器允许处理器乱序执行,以及 JDK1.5 之前 JMM(Java Memory Model,即 Java 内存模型)中 Cache、寄存器到主内存回写顺序的规定,上面的第二和第三的顺序是无法保证的。也就是说,执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,并且在 3 执行完毕、2 未执行之前,被切换到线程 B 上,这时候 sInstance 因为己经在线程 A 内执行过了第三点,sInstance 己经是非空了,所以,线程 B 直接取走 sInstance,再使用时就会出错,这就是 DCL 失效问题,而且这种难以跟踪难以重现的错误很可能会隐藏很久。
在 JDK1.5 之后,SUN 官方已经注意到这种问题,调整了 JVM,具体化了 volatile 关键字,因此,如果 JDK 是 1.5 或之后的版本,只需要将 sInstance 的定义改成 private volatile static Singleton sInstance = null
就可以保证 sInstance 对象每次都是从主内存中读取,就可以使用 DCL 的写法来完成单例模式。当然,volatile 或多或少也会影响到性能,但考虑到程序的正确性,牺牲这点性能还是值得的。
DCL 的优点:资源利用率高,第一次执行 getInstance 时单例对象才会被实例化,效率高。缺点:第一次加载时反应稍慢,也由于 Java 内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生概率很小。DCL 模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于 JDK6 版本下使用,否则,这种方式一般能够满足需求。
6.3 静态内部类单例模式
DCL 虽然在一定程度上解决了资源消耗、多余的同步、线程安全等问题,但是,它还是在某些情况下出现失效的问题。这个问题被称为双重检查锁定(DCL)失效,在《Java并发编程实践》一书的最后谈到了这个问题,并指出这种"优化"是丑陋的,不赞成使用。而建议使用如下的代码替代。
java
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
当第一次加载 Singleton 类时并不会初始化 sInstance,只有在第一次调用 Singleton 的 getInstance 方法时才会导致 sInstance 被初始化。因此,第一次调用 getInstance 方法会导致虚拟机加载 SingletonHolder 类,这种方式不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐使用的单例模式实现方式。
6.4 枚举单例
前面讲解了一些单例模式实现方式,但是,这些实现方式不是稍显麻烦就是会在某些情况下出现问题。还有没有更简单的实现方式呢?我们看看下面的实现。
java
public enum SingletonEnum {
INSTANCE;
}
写法简单是枚举单例最大的优点,枚举在 Java 中与普通的类是一样的,不仅能够有字段,还能够有自己的方法。最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。为什么这么说呢?在上述的几种单例模式实现中,在反序列化的情况下它们会出现重新创建对象。
我们知道通过序列化可以将一个单例的实例对象写到磁盘,然后再读回来,从而有效地获得一个实例。即使构造函数是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例, 相当于调用该类的构造函数。反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的 readResolve() 函数,这个函数可以让开发人员控制对象的反序列化。例如,上述几个示例中,如果要杜绝单例对象在被反序列化时重新生成对象,那么必须加入 readResolve 函数。
java
public class Singleton implements Serializable {
private static final long serialVersionUID = 0L;
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
}
也就是在 readResolve 方法中将单例对象返回,而不是重新生成一个新的对象。而对于枚举,并不存在这个问题,因为即使反序列化它也不会重新生成新的实例。另外还有两点需要注意:
(1)可序列化类中的字段类型不是 Java 的内置类型,那么该字段类型也需要实现 Serializable 接口;
(2)如果你调整了可序列化类的内部结构,例如新增、去除某个字段,但没有修改serialVersionUID,那么会引发 java.io.InvalidClassException 异常或者导致某个属性为 0 或者 null。此时最好的方案是我们直接将 serialVersionUID 设置为 0L,这样即使修改了类的内部结构,我们反序列化不会抛出 java.io.InvalidClassExceptio,只是那些新修改的字段会为 0 或者 null。
6.5 使用容器实现单例模式
在学习了上述各类单例模式的实现之后,再来看看一种另类的实现,具体代码如下:
java
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String, Object>();
private SingletonManager() {
}
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
在程序的初始,将多种单例类型注入到一个统一的管理类中,在使用时根据 key 获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接又进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
不管以哪种形式实现单例模式,它们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在这个获取的过程中必须保证线程安全、防止反序列化导致重新生成实例对象等问题。选择哪种实现方式取决于项目本身,如是否是复杂的并发环境、JDK 版本是否过低、单例对象的资源消耗等。
7. Android 源码中的单例模式
在 Android 系统中,我们经常会通过 Context 获取系统级别的服务,如 WindowsManagerService、ActivityManagerService 等,更常用的是一个 Layoutlnflater 的类,这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候就通过 Context 的 getSystemService(String name) 获取。我们以 Layoutinflater 为例来说明,平时我们使用 Layoutinflater 较为常见的地方是在 ListView 的 getView 方法中。
java
@Override
public View getView(int position, View convertView, ViewGroup container) {
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(mLayoutId, container, false);
}
// 代码省略
return convertView;
}
通常我们使用 LayoutInflater.from(Context) 来获取 LayoutInflater 服务,下面看看 Layoutinflater.from(Context) 的实现。
java
/**
* Obtains the LayoutInflater from the given context.
*/
public static LayoutInflater from(@UiContext Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
可以看到 from(Context) 两数内部调用的是 Context 类的 getSystemService(Stringkey) 方法,我们跟踪到 Context 类看到,该类是抽象类。
java
public abstract class Context {
// 省略
}
getView 中使用的 Context 对象的具体实现类是什么呢?其实在 Application、Activity、Service 中都会存在一个 Context 对象,即 Context 的总个数为 Activity 个数 + Service 个数 +1。而 ListView 通常都是显示在 Activity 中,那么我们就以 Activity 中的 Context 来分析。
我们知道,一个 Activity 的入口是 ActivityThread 的 main 函数,在 main 函数中创建一个新的 ActivityThread 对象,并且启动消息循环(UI 线程),创建新的 Activity、新的 Context 对象,然后将该 Context 对象传递给 Activity。下面看看 ActivityThread 源代码。
java
public static void main(String[] args) {
// 代码省略
Process.setArgV0("<pre-initialized>");
// 主线程消息循环
Looper.prepareMainLooper();
// 创建 ActivityThread 对象
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// 代码省略
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
java
@UnsupportedAppUsage
private void attach(boolean system, long startSeq) {
sCurrentActivityThread = this;
mConfigurationController = new ConfigurationController(this);
mSystemThread = system;
// 不是系统应用的情况
if (!system) {
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {
// 关联 mAppThread
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
// 代码省略
} else {
// 代码省略
}
// 代码省略
}
在 main 方法中,我们创建一个 ActivityThread 对象后,调用了其 attach 函数,并且参数为 false。在 attach 函数中,参数力 false 的情況下(即非系统应用),会通过 Binder 机制与 ActivityManagerService 通信,并且最终调用 handleLaunchActivity 函数,我们看看该函数的实现。
java
/**
* Extended implementation of activity launch. Used when server requests a launch or relaunch.
*/
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
...
final Activity a = performLaunchActivity(r, customIntent);
...
}
java
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
// 获取 context 对象
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 创建 Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
} catch (Exception e) {
...
}
try {
// 创建 Application 对象
Application app = r.packageInfo.makeApplicationInner(false, mInstrumentation);
...
if (activity != null) {
...
appContext.setOuterContext(activity);
// 将 appContext 等对象 attach 到 activity 中
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
r.assistToken, r.shareableActivityToken);
// 调用 Activity 的 onCreate 方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
...
}
return activity;
}
通过上面的代码我们可以知道,Context 的实现类为 Comtextlmpl。
SystemServiceRegistry 部分源码:
java
/**
* Manages all of the system services that can be returned by {@link Context#getSystemService}.
* Used by {@link ContextImpl}.
*
* @hide
*/
@SystemApi
public final class SystemServiceRegistry {
...
// 1. Service 容器
// Service registry information.
// This information is never changed once static initialization has completed.
private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES =
new ArrayMap<Class<?>, String>();
private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new ArrayMap<String, ServiceFetcher<?>>();
private static final Map<String, String> SYSTEM_SERVICE_CLASS_NAMES = new ArrayMap<>();
private static int sServiceCacheSize;
// 2. 注册服务器
/**
* Statically registers a system service with the context.
* This method must be called during static initialization only.
*/
private static <T> void registerService(@NonNull String serviceName,
@NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
}
// 3. 静态语句块,第一次加载该类时执行(只执行一次,保证实例的唯一性)
static {
...
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
...
}
/**
* Gets a system service from a given context.
* @hide
*/
public static Object getSystemService(ContextImpl ctx, String name) {
if (name == null) {
return null;
}
final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
if (fetcher == null) {
if (sEnableServiceNotFoundWtf) {
Slog.wtf(TAG, "Unknown manager requested: " + name);
}
return null;
}
final Object ret = fetcher.getService(ctx);
if (sEnableServiceNotFoundWtf && ret == null) {
// Some services do return null in certain situations, so don't do WTF for them.
switch (name) {
case Context.CONTENT_CAPTURE_MANAGER_SERVICE:
case Context.APP_PREDICTION_SERVICE:
case Context.INCREMENTAL_SERVICE:
case Context.ETHERNET_SERVICE:
return null;
}
Slog.wtf(TAG, "Manager wrapper not available: " + name);
return null;
}
return ret;
}
/**
* Base interface for classes that fetch services.
* These objects must only be created during static initialization.
*/
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}
/**
* Override this class when the system service constructor needs a
* ContextImpl and should be cached and retained by that context.
*/
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
private final int mCacheIndex;
CachedServiceFetcher() {
// Note this class must be instantiated only by the static initializer of the
// outer class (SystemServiceRegistry), which already does the synchronization,
// so bare access to sServiceCacheSize is okay here.
mCacheIndex = sServiceCacheSize++;
}
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
final int[] gates = ctx.mServiceInitializationStateArray;
boolean interrupted = false;
T ret = null;
for (;;) {
boolean doInitialize = false;
synchronized (cache) {
// Return it if we already have a cached instance.
T service = (T) cache[mCacheIndex];
if (service != null) {
ret = service;
break; // exit the for (;;)
}
// If we get here, there's no cached instance.
// Grr... if gate is STATE_READY, then this means we initialized the service
// once but someone cleared it.
// We start over from STATE_UNINITIALIZED.
// Similarly, if the previous attempt returned null, we'll retry again.
if (gates[mCacheIndex] == ContextImpl.STATE_READY
|| gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED;
}
// It's possible for multiple threads to get here at the same time, so
// use the "gate" to make sure only the first thread will call createService().
// At this point, the gate must be either UNINITIALIZED or INITIALIZING.
if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) {
doInitialize = true;
gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING;
}
}
if (doInitialize) {
// Only the first thread gets here.
T service = null;
@ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND;
try {
// This thread is the first one to get here. Instantiate the service
// *without* the cache lock held.
service = createService(ctx);
newState = ContextImpl.STATE_READY;
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
} finally {
synchronized (cache) {
cache[mCacheIndex] = service;
gates[mCacheIndex] = newState;
cache.notifyAll();
}
}
ret = service;
break; // exit the for (;;)
}
// The other threads will wait for the first thread to call notifyAll(),
// and go back to the top and retry.
synchronized (cache) {
// Repeat until the state becomes STATE_READY or STATE_NOT_FOUND.
// We can't respond to interrupts here; just like we can't in the "doInitialize"
// path, so we remember the interrupt state here and re-interrupt later.
while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
try {
// Clear the interrupt state.
interrupted |= Thread.interrupted();
cache.wait();
} catch (InterruptedException e) {
// This shouldn't normally happen, but if someone interrupts the
// thread, it will.
Slog.w(TAG, "getService() interrupted");
interrupted = true;
}
}
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
return ret;
}
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}
/**
* Override this class when the system service does not need a ContextImpl
* and should be cached and retained process-wide.
*/
static abstract class StaticServiceFetcher<T> implements ServiceFetcher<T> {
private T mCachedInstance;
@Override
public final T getService(ContextImpl ctx) {
synchronized (StaticServiceFetcher.this) {
if (mCachedInstance == null) {
try {
mCachedInstance = createService();
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
}
}
return mCachedInstance;
}
}
public abstract T createService() throws ServiceNotFoundException;
}
...
}
ContextImpl 部分源码:
java
/**
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/
class ContextImpl extends Context {
...
// 4. 根据 key 获取对应的服务
@Override
public Object getSystemService(String name) {
if (vmIncorrectContextUseEnabled()) {
// Check incorrect Context usage.
if (WINDOW_SERVICE.equals(name) && !isUiContext()) {
final String errorMessage = "Tried to access visual service "
+ SystemServiceRegistry.getSystemServiceClassName(name)
+ " from a non-visual Context:" + getOuterContext();
final String message = "WindowManager should be accessed from Activity or other "
+ "visual Context. Use an Activity or a Context created with "
+ "Context#createWindowContext(int, Bundle), which are adjusted to "
+ "the configuration and visual bounds of an area on screen.";
final Exception exception = new IllegalAccessException(errorMessage);
StrictMode.onIncorrectContextUsed(message, exception);
Log.e(TAG, errorMessage + " " + message, exception);
}
}
// 根据 name 来获取服务
return SystemServiceRegistry.getSystemService(this, name);
}
...
}
从 SystemServiceRegistry 类的部分代码中可以看到,在虚拟机第一次加载该类时会注册各种 ServiceFatcher,其中就包含了 LayoutlnflaterService。将这些服务以键值对的形式存储在一个 HashMap 中,用户使用时只需要根据 key 来获取到对应的 ServiceFetcher,然后通过 ServiceFetcher 对象的 getService 函数来获取具体的服务对象。当第一次获取时,会调用 ServiceFetcher 的 createService 函数创建服务对象,然后将该对象缓存到一个列表中,下次再取时直接从缓存中获取,避免重复创建对象,从而达到单例的效果。这种模式就是 6.5 小节中通过容器的单例模式实现方式,系统核心服务以单例形式存在,减少了资源消耗。
8. 无名英雄 一 深入理解 LayoutInflater
9. 运用单例模式
10. 小结
单例模式是运用频率很高的模式,但是,由于在客户端通常没有高并发的情况,因此,选择哪种实现方式并不会有太大的影响。即便如此,出于效率考虑,推荐用 6.2 小节、6.3 小节使用的形式。
优点
(1)由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
(2)由于单例模式只生成一个实例,所以,减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。
(3)单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
(4)单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。
缺点
(1)单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
(2)单例对象如果持有 Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的 Context 最好是 ApplicationContext。