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 只要一个,请看下面示例。
示例完成代码:
/**
* 普通职工
*/
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();
}
}
输出成果如下:
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 类)是在声明静态目标时就现已初始化。懒汉单例方法完成如下。
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 不进行同步锁。代码如下所示:
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并发编程实践》一书的最终谈到了这个问题,并指出这种“优化”是丑陋的,不赞成运用。而主张运用如下的代码替代。
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 枚举单例
前面讲解了一些单例方法完成办法,可是,这些完成办法不是稍显麻烦便是会在某些状况下呈现问题。还有没有更简略的完成办法呢?咱们看看下面的完成。
public enum SingletonEnum {
INSTANCE;
}
写法简略是枚举单例最大的长处,枚举在 Java 中与普通的类是相同的,不只能够有字段,还能够有自己的办法。最重要的是默认枚举实例的创立是线程安全的,并且在任何状况下它都是一个单例。为什么这么说呢?在上述的几种单例方法完成中,在反序列化的状况下它们会呈现从头创立目标。
咱们知道经过序列化能够将一个单例的实例目标写到磁盘,然后再读回来,然后有效地获得一个实例。即便构造函数是私有的,反序列化时仍然能够经过特殊的途径去创立类的一个新的实例, 相当于调用该类的构造函数。反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的 readResolve() 函数,这个函数能够让开发人员控制目标的反序列化。例如,上述几个示例中,如果要杜绝单例目标在被反序列化时从头生成目标,那么有必要参加 readResolve 函数。
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 运用容器完成单例方法
在学习了上述各类单例方法的完成之后,再来看看一种另类的完成,详细代码如下:
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 办法中。
@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) 的完成。
/**
* 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 类看到,该类是笼统类。
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 源代码。
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");
}
@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 函数,咱们看看该函数的完成。
/**
* 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);
...
}
/** 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 部分源码:
/**
* 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 部分源码:
/**
* 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。