[关闭]
@Beeder 2017-07-06T10:11:16.000000Z 字数 14447 阅读 863

Android源码设计模式 学习笔记(二) 单例模式

android



第二章 单例模式

2.1 单例模式的定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式应该是日常使用最为广泛的一种模式了。他的作用是确保某个类只有一个实例,避免产生多个对象消耗过多的资源。比如对数据库的操作时,就可以使用单例模式。

2.2 各种单例

2.2.1 饿汉模式

  1. public class Singleton {
  2. private static Singleton instance = new Singleton();
  3. private Singleton (){
  4. }
  5. public static Singleton getInstance() {
  6. return instance;
  7. }
  8. }

这个实现的核心在于将Singleton类的构造方法私有化,使得外部程序不能通过构造函数来构造Singleton对象。这种写法是在类装载时就实例化instance,他避免了多线程的同步问题。但是不能保证有别的方式去装载,没有达到懒加载。


2.2.2 懒汉模式(线程不安全)

  1. public class Singleton {
  2. private static Singleton instance;
  3. private Singleton (){
  4. }
  5. public static Singleton getInstance() {
  6. if (instance == null) {
  7. instance = new Singleton();
  8. }
  9. return instance;
  10. }
  11. }

达到了懒加载,但是在多线程不能正常工作。

2.2.3 懒汉模式(线程安全)

不建议使用

  1. public class Singleton {
  2. private static Singleton instance;
  3. private Singleton (){
  4. }
  5. public static synchronized Singleton getInstance() {
  6. if (instance == null) {
  7. instance = new Singleton();
  8. }
  9. return instance;
  10. }
  11. }

这种写法能够在多线程中很好的工作,但是每次调用getInstance方法都会进行同步,反应稍慢,会消耗不必要的资源,还会造成不必要的开销,所以这种不建议使用。

2.2.4 DCL单例(Double Check Lock 双重检查锁定)

  1. public class Singleton {
  2. private volatile static Singleton mSingleton;
  3. private Singleton (){
  4. }
  5. public static Singleton getSingleton() {
  6. if (mSingleton == null) {
  7. synchronized (Singleton.class) {
  8. if (mSingleton == null) {
  9. mSingleton = new Singleton();
  10. }
  11. }
  12. }
  13. return mSingleton;
  14. }
  15. }

这种写法在getSingleton方法中对singleton进行了两次判空,第一次是为了不必要的同步,第二次是为了在null的情况下创建实例。

DCL的优点:资源利用率高,第一次执行mSingleton时单例对象才会被实例化,效率高。
DCL的缺点:第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生概率很小。
DCL模式是使用最多的单例实现方式,它能够在需要的时候才实例化单例对象,并且能够在绝大多数的场景下保证单例对象的唯一性,除非你的代码在高并发场景比较复杂或者低于JDK 1.6版本下使用,否则,这种方式一般能够满足要求。

JDK1.5之前会有DCL失效问题

原因分析:
假设线程A执行到 mSingleton = new Singleton() 语句,看起来像是一句代码,但实际上它并不是一个原子操作,这句代码最终会被编译成多条汇编指令,它大致做了3件事情:

但是,由于java编译器允许处理器乱序执行,以及JDK1.5之前JMM(Java Memory Model,即java内存模型)中的Cache、寄存器到主内存回写顺序的规定,上面的第二和第三顺序是无法保证的。也就是说,执行顺序可能是1-2-3也可能是1-3-2。如果是后者,并且在3执行完毕、2未执行之前,被切换到线程B上,这时候mSingleton因为已经在线程A内执行过了第三点,mSingleton已经是非空了,所以,线程B直接取走mSingleton,再使用时就会报错,这就是DCL失效的问题,而且这种难以跟踪难以重视的错误很可能会隐藏很久。
在JDK1.5之后,SUN官方已经注意到这个问题,调整了JVM,具现化了volatile关键字,因此,如果JDK是1.5或之后的版本,mSingleton的定义改成private volatile static Singleton mSingleton = null 就可以保证mSingleton对象每次都是从主内存中读取,就可以使用DCL的写法完成单例模式。当然,volatitle 或多或少也会影响到性能,但考虑到程序的正确性,牺牲这点性能还是值得的。


2.2.5 静态内部类单例模式

推荐使用
由于DCL单例会在某些情况下出现 “双重检查锁定(DCL)失效” 的问题,在《Java并发编程实践》一书最后指出这种“优化”是丑陋的,不赞成使用。而建议用如下的代码代替:

  1. public class Singleton {
  2. private Singleton (){
  3. }
  4. public static final Singleton getInstance() {
  5. return SingletonHolder.INSTANCE;
  6. }
  7. /**
  8. *静态内部类
  9. */
  10. private static class SingletonHolder {
  11. private static final Singleton INSTANCE = new Singleton();
  12. }
  13. }

这种是推荐使用的单例模式实现方式。当第一次加载Singleton类时并不会初始化INSTANCE,只有在第一次调用getInstance方法时才会导致INSTANCE被初始化。这种方式不仅能够保证线程安全,也能保证单例对象的唯一性,同时也延迟了单例的实例化。

2.2.6 枚举单例

推荐使用

  1. public enum Singleton {
  2. INSTANCE;
  3. public void whateverMethod() {
  4. }
  5. }

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。


2.2.7 使用容器实现单例模式

  1. public class SingletonManager {
  2.   private static Map<String, Object> objMap = new HashMap<String,Object>();
  3.   private SingletonManager() {
  4.   }
  5.   public static void registerService(String key, Objectinstance) {
  6.     if (!objMap.containsKey(key) ) {
  7.       objMap.put(key, instance) ;
  8.     }
  9.   }
  10.   public static ObjectgetService(String key) {
  11.     return objMap.get(key) ;
  12.   }
  13. }

将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

2.2.8 单例模式总结

    不管以哪种形式实现单例模式,它们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在这个获取的过程中必须保证线程安全,防止反实例化导致重新生成实例对象等问题。选择哪种实现方式取决于项目本身,如是否是复杂的并发环境,JDK版本是否过低、单例对象的资源消耗等。
类型 简单描述 存在问题
饿汉模式 [基本]静态方法get静态对象,类装载时就实例化对象 没有达到懒加载
懒汉模式 使用synchronized关键字,但每次get时都会进行同步 达到懒加载,但反应比较慢
DCL单例 使用volatile关键字,get时先判空再进行同步 JDK1.5 之前无volatile关键字
静态内部类单例模式 使用静态内部类,外部使用静态方法get调用 推荐使用
枚举单例 推荐使用
容器实现单例 实例一个静态Map,通过put和get管理

优点:

缺点:


2.3 Android源码中的单例模式

2.3.1 容器实现单例-Context的实现及系统服务的管理

在Android系统中,我们经常会通过Context获取系统级别的服务,如WindowsManagerService、ActivityManagerService等,更常用的是一个LayoutInflater的类,这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候就通过Context的getSystemService(String name)获取。

2.3.1 LayoutInflater类

源码路径:frameworks/base/core/java/android/view/LayoutInflater.java

通常我们使用LayoutInflater.from来获取LayoutInflater服务,LayoutInflater.from(Context)的实现如下:

  1. /**
  2. * Obtains the LayoutInflater from the given context.
  3. */
  4. public static LayoutInflater from(Context context) {
  5. LayoutInflater LayoutInflater =
  6. (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  7. if (LayoutInflater == null) {
  8. throw new AssertionError("LayoutInflater not found.");
  9. }
  10. return LayoutInflater;
  11. }

可以看出from(Context)函数内部调用的是Context类的getSystemService(String key)方法,我们跟踪到Context类看到,该类是抽象类:

  1. //相关源码路径:frameworks/base/core/java/android/content/Context.java
  2. public abstract class Context {
  3. //省略
  4. }

getView 中使用的Context对象的具体实现类是什么呢?其实在Application、Activity、Service中都会存在一个Context对象,即Contest的总个数Activity个数+Service个数+1,而ListView通常都是显示在Activity中,那么我们就以Acitviy中的Context来分析。
我们知道,一个Activity的入口是ActivityThread的main函数,在main函数中创建一个新的ActivityThread的对象,并且在启动消息循环(UI线程)、创建新的Activity、新的Context对象,然后将该Context对象传递给Activity。下面看到ActivityThread源代码:

  1. //相关源码路径:frameworks/base/core/java/android/app/ActivityThread.java
  2. public final class ActivityThread {
  3. //省略
  4. public static void main(String[] args) {
  5. //省略
  6. Process.setArgV0("<pre-initialized>");
  7. Looper.prepareMainLooper();
  8. //创建ActivityThread对象
  9. ActivityThread thread = new ActivityThread();
  10. thread.attach(false);
  11. if (sMainThreadHandler == null) {
  12. sMainThreadHandler = thread.getHandler();
  13. }
  14. //省略
  15. Looper.loop();
  16. }
  17. private void attach(boolean system) {
  18. sCurrentActivityThread = this;
  19. mSystemThread = system;
  20. //不是系统应用的情况
  21. if (!system) {
  22. ViewRootImpl.addFirstDrawHandler(new Runnable() {
  23. @Override
  24. public void run() {
  25. ensureJitEnabled();
  26. }
  27. });
  28. android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",UserHandle.myUserId());
  29. RuntimeInit.setApplicationObject(mAppThread.asBinder());
  30. final IActivityManager mgr = ActivityManagerNative.getDefault();
  31. try {
  32. //关联mAppThread
  33. mgr.attachApplication(mAppThread);
  34. } catch (RemoteException ex) {
  35. // Ignore
  36. }
  37. //省略
  38. } else {
  39. //省略
  40. }
  41. }

在main方法中,我们创建了一个Activitythread对象后,调用了其attach函数,并且参数为false。在attach函数中,在参数为false的情况下(非系统应用),会通过Binder机制与ActivityManagerService通信,并且最终调用handleLaunchActivity函数,我们看看该函数的实现:

  1. //相关源码路径:frameworks/base/core/java/android/app/ActivityThread.java
  2. public final class ActivityThread {
  3. //省略
  4. private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  5. //省略
  6. Activity a = performLaunchActivity(r, customIntent);
  7. //省略
  8. }
  9. private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  10. Activity activity = null;
  11. try {
  12. java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
  13. //1、创建Acitvity
  14. activity = mInstrumentation.newActivity(
  15. cl, component.getClassName(), r.intent);
  16. //省略
  17. }
  18. } catch (Exception e) {
  19. //省略
  20. }
  21. }
  22. try {
  23. //创建Application对象
  24. Application app = r.packageInfo.makeApplication(false, mInstrumentation);
  25. //省略
  26. if (activity != null) {
  27. Context appContext = createBaseContextForActivity(r, activity);
  28. //2、获取Context对象
  29. CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
  30. Configuration config = new Configuration(mCompatConfiguration);
  31. //3、将appContext等对象attach到activity中
  32. activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,
  33. r.referrer, r.voiceInteractor);
  34. //省略
  35. //4、调用Activity中的Create方法
  36. if (r.isPersistable()) {
  37. mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
  38. } else {
  39. mInstrumentation.callActivityOnCreate(activity, r.state);
  40. }
  41. //省略
  42. } catch (SuperNotCalledException e) {
  43. throw e;
  44. } catch (Exception e) {
  45. //省略
  46. }
  47. return activity;
  48. }
  49. private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
  50. //5、创建Context对象,可以看到实现类是ContextImpl
  51. ContextImpl appContext = ContextImpl.createActivityContext(
  52. this, r.packageInfo, displayId, r.overrideConfig);
  53. appContext.setOuterContext(activity);
  54. Context baseContext = appContext;
  55. return baseContext;
  56. }
  57. }

通过上面的1-5注释处的代码分析可知道,Context的实现类为ContextImpl,其最终返回一个ContextImpl实例。

  1. //路径:frameworks/base/core/java/android/app/ContextImpl.java
  2. class ContextImpl extends Context {
  3. private final static String TAG = "ContextImpl";
  4. // The system service cache for the system services that are cached per-ContextImpl.
  5. /用SystemServiceRegistry类.createServiceCache()创建系统服务
  6. f实现。inal Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
  7. /*可以看到返回的是一个ContextImpl*/
  8. static ContextImpl createActivityContext(ActivityThread mainThread,LoadedApk packageInfo, int displayId, Configuration overrideConfiguration) {
  9. if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
  10. return new ContextImpl(null, mainThread, packageInfo, null, null, false,null, overrideConfiguration, displayId);
  11. }
  12. }

由ContextImpl类代码可以看到,创建系统服务时,调用的是SystemServiceRegistry类的createServiceCache方法。

  1. //路径:frameworks/base/core/java/android/app/SystemServiceRegistry.java
  2. final class SystemServiceRegistry {
  3. //实际返回Object[sServiceCacheSize]
  4. public static Object[] createServiceCache() {
  5. return new Object[sServiceCacheSize];
  6. }
  7. /**
  8. * Base interface for classes that fetch services.
  9. * These objects must only be created during static initialization.
  10. */
  11. static abstract interface ServiceFetcher<T> {
  12. T getService(ContextImpl ctx);
  13. }
  14. /**
  15. * Override this class when the system service constructor needs a
  16. * ContextImpl and should be cached and retained by that context.
  17. */
  18. static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
  19. private final int mCacheIndex;
  20. public CachedServiceFetcher() {
  21. mCacheIndex = sServiceCacheSize++;
  22. }
  23. @Override
  24. @SuppressWarnings("unchecked")
  25. //获取系统服务[最终]
  26. public final T getService(ContextImpl ctx) {
  27. final Object[] cache = ctx.mServiceCache;
  28. synchronized (cache) {
  29. //从缓存中获取服务对象
  30. // Fetch or create the service.
  31. Object service = cache[mCacheIndex];
  32. //没有就创建服务
  33. if (service == null) {
  34. service = createService(ctx);
  35. cache[mCacheIndex] = service;
  36. }
  37. return (T)service;
  38. }
  39. }
  40. //抽象方法,用于创建系统服务
  41. public abstract T createService(ContextImpl ctx);
  42. }
  43. }

在SystemServiceRegistry类中, 主要通过抽象内部类SystemServiceRegistry类控制service对象,如果没有service对象则通过实现抽象方法createServic来创建。

  1. //路径:frameworks/base/core/java/android/app/SystemServiceRegistry.java
  2. final class SystemServiceRegistry {
  3. //1、Service 容器
  4. private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
  5. new HashMap<String, ServiceFetcher<?>>();
  6. //3、静态代码块,第一次加载该类时执行(只执行一次,保证实例的唯一性)
  7. static{
  8. //省略
  9. //注册LayoutInflater service
  10. //调用上述代码的CachedServiceFetcher内部类创建系统服务对象
  11. registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,new CachedServiceFetcher<LayoutInflater>() {
  12. @Override
  13. public LayoutInflater createService(ContextImpl ctx) {
  14. return new PhoneLayoutInflater(ctx.getOuterContext());
  15. }});
  16. }
  17. /**
  18. * Statically registers a system service with the context.
  19. * This method must be called during static initialization only.
  20. */
  21. //2、注册服务器
  22. private static <T> void registerService(String serviceName, Class<T> serviceClass,ServiceFetcher<T> serviceFetcher) {
  23. SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
  24. //将Service对象加入Service容器
  25. SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
  26. }
  27. }

SystemServiceRegistry类中,1-3注释处代码为注册及创建Service对象的具体实现过程。
在静态代码块中。通过调用registerService方法,实现抽象内部类CachedServiceFetcher,并实现CachedServiceFetcher类的抽象方法createService方法创建Service对象,并将对象加入Service容器中。

结合上述代码可以看到,在虚拟机第一次加载时会注册各种serviceFetcher,其中就包含了LayoutInflater Service。将这些服务以键值对的形式储存在一个HashMap钟,用户使用时只需要根据key来获取到对应的serviceFetcher,然后通过serviceFetcher对象的getService函数来获取具体的服务对象。当第一次获取时,会调用serviceFetcher的CreatService函数创建服务对象,然后将该对象缓存到一个列表中,下次再获取时直接从缓存中获取,避免重复创建对象,从而达到单例的效果。这种模式就是‘通过容器实现的单例模式’,系统核心服务以单例形式存在,减少了资源。


2.3.2 LayoutInflater类

LayoutInflater在我们的开发中扮演着重要的角色,但很多时候我们都不知道它的重要性,因为它的重要性被隐藏在了Activity、Fragment等组件的光环之下。
LayoutInflater是一个抽象类,具体代码如下:

  1. //代码:frameworks/base/core/java/android/view/LayoutInflater.java
  2. public abstract class LayoutInflater {
  3. //代码省略
  4. }

既然是抽象不是具体的,那我们就必须找出它的实现类。这需要先从LayoutInflater的起源开始,在上文中知道,在加载ContextImpl类时,会调用SystemServiceRegistry类,在SystemServiceRegistry类的静态代码块中会通过如下代码将LayoutInflater的serviceFetcher注入到Service容器中。具体代码如下:

  1. //路径:frameworks/base/core/java/android/app/SystemServiceRegistry.java
  2. final class SystemServiceRegistry {
  3. static {
  4. registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
  5. new CachedServiceFetcher<LayoutInflater>() {
  6. @Override
  7. public LayoutInflater createService(ContextImpl ctx) {
  8. //创建Service实例
  9. return new PhoneLayoutInflater(ctx.getOuterContext());
  10. }});
  11. }
  12. }

结果这里调用了PhoneLayoutInflater类,具体如下:

  1. //路径:frameworks/base/core/java/com/android/internal/policy/PhoneLayoutInflater.java
  2. public class PhoneLayoutInflater extends LayoutInflater {
  3. //内置View类型的前缀,如TextView的完整路径是android.widget.TextView
  4. private static final String[] sClassPrefixList = {
  5. "android.widget.",
  6. "android.webkit.",
  7. "android.app."
  8. };
  9. //代码省略
  10. /** Override onCreateView to instantiate names that correspond to the
  11. widgets known to the Widget factory. If we don't find a match,
  12. call through to our super class.
  13. */
  14. @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
  15. //在View名字的前面添加前缀来构造View的完整路径。
  16. //例如类名为TextView,那么TextView的完整路径为android.widget.TextView
  17. for (String prefix : sClassPrefixList) {
  18. try {
  19. View view = createView(name, prefix, attrs);
  20. if (view != null) {
  21. return view;
  22. }
  23. } catch (ClassNotFoundException e) {
  24. // In this case we want to let the base class take a crack
  25. // at it.
  26. }
  27. }
  28. return super.onCreateView(name, attrs);
  29. }
  30. }

PhoneLayoutInflater类的核心是覆写了LayoutInflater的onCreateView方法,该方法在传递进来的View名字前加上前缀以得到该内置View的完整路径。最后根据完整路径来构造对应的View对象。
以Activity的setContentView为例,先来看看其实现:

  1. //路径:frameworks/base/core/java/android/app/Activity.java
  2. public class Activity extends ContextThemeWrapper
  3. implements LayoutInflater.Factory2,
  4. Window.Callback, KeyEvent.Callback,
  5. OnCreateContextMenuListener, ComponentCallbacks2,
  6. Window.OnWindowDismissedCallback {
  7. public void setContentView(View view) {
  8. getWindow().setContentView(view);
  9. initWindowDecorActionBar();
  10. }
  11. }

Activity的setContentView方法实际上调用的是Window的setContentView,而Window是一个抽象类,其具体实现类为PhoneWindow,PhoneWindow类中对应的方法如下:

  1. //路径:frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
  2. public class PhoneWindow extends Window implements MenuBuilder.Callback {
  3. @Override
  4. public void setContentView(int layoutResID) {
  5. // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
  6. // decor, when theme attributes and the like are crystalized. Do not check the feature
  7. // before this happens.
  8. //1、当mContentParent为空时先创建DecorView
  9. if (mContentParent == null) {
  10. installDecor();
  11. } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  12. mContentParent.removeAllViews();
  13. }
  14. if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  15. final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
  16. getContext());
  17. transitionTo(newScene);
  18. } else {
  19. mLayoutInflater.inflate(layoutResID, mContentParent);
  20. }
  21. mContentParent.requestApplyInsets();
  22. final Callback cb = getCallback();
  23. if (cb != null && !isDestroyed()) {
  24. cb.onContentChanged();
  25. }
  26. }
  27. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注