Android设计模式之单例模式在项目中的运用

前言

单例模式(Singleton Pattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最常用、最易被识别出来的模式。

本文会重点总结一下Android开发中常用的单例模式场景,理论与实践结合,深入学习设计模式,从而提高大家的开发水平,完美解决开发中遇到的类似问题。

文章目录

      • 前言
      • 单例模式介绍
        • 单例模式的定义
        • 单例模式的使用场景
      • 优点
      • 缺点
      • 实现方式
        • 1.双重检查锁定(Double CheckLock)( DCL)
        • 2.静态内部类单例模式
        • 3.其他实现方式汇总
      • Android源码中的模式实现
      • 在Android中的其他运用场景
      • 在 Android 中使用单例还有哪些需要注意的地方
        • 总结

单例模式介绍

单例模式的定义

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

单例模式的使用场景

确保某个类有且只有一个对象的场景。避免产生多个对象消耗过多的资源,或者某种类型的对象应该有且只有一个。如在一个应用中,应用只有一个ImageLoader 实例,这个 ImageLoader 中又含有线程池、缓存系统、网络请求等,很消耗资源,因此,没有理由让它构造多个实例。

优点

  • 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。

  • 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。

  • 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。

  • 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

缺点

扩展很困难,容易引发内存泄露,测试困难,一定程度上违背了单一职责原则,进程被杀时可能有状态不一致问题。

实现方式

单例模式的实现方式 有很多种,先来一个使用最为常见的实现方式。

1.双重检查锁定(Double CheckLock)( DCL)


public class Singleton {
    //JDK>=1.5 增加了volatile关键字,定义时加上它即可保证执行顺序(虽然会影响性能),从而单例生效。
    private static volatile Singleton instance = null;

    private Singleton(){}
 
    public static Singleton getInstance() {
        if (instance == null) { //第一个if
            synchronized (Singleton.class) {//同步判断
                if (instance == null) {//第二个if
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

DCL 方式实现单例的优点是既能在需要时才初始化单例,又能够保证线程安全,且单例对象初始后调用getInstance不进行同步锁。

上面版本是java语言实现的,用kotlin实现代码如下,同时用到了Kotlin的延迟属性 Lazy

class Singleton private constructor() {
    companion object {
        val instance: Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
        Singleton() }
    }
}

2.静态内部类单例模式

public class SingleTon {
    private SingleTon(){}
    public static SingleTon getInstance(){
        return SingletonHolder.sInstance
    }
    private static class SingletonHolder{
        private static final SingleTon sInstance = new SingleTon();
    }
}

DCL 虽然在一定程序上解决了资源消耗、多余的同步、线程安全等问题,但是,它还是在某些情况下出现失效的问题。这《Java并发编程实践》一书中最后谈到了这个总是,并指出这种“优化”是丑陋的,不赞成使用,而建议用上述代码替代。

当第一次加载Singleton类时不会初始化sInstance,只有在第一次调用SingleTon的getInstance方法时才会去初始化sInstance。因此,第一次调用getInstance方法会导致虚拟机加载SingletonHolder,这种方式不仅能够保证线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐的单例模式实现方式。

3.其他实现方式汇总

public class Singleton {
	private static Singleton mInstance = null;

	private Singleton() {

	}

	public void doSomething() {
		System.out.println("do sth.");
	}
    
    

	/**
	 * 方式二、double-check, 避免并发时创建了多个实例, 该方式不能完全避免并发带来的破坏.
	 * 
	 * @return
	 */
	public static Singleton getInstance() {
		if (mInstance == null) {
			synchronized (Singleton.class) {
				if (mInstance == null) {
					mInstance = new Singleton();
				}
			}
		}
		return mInstance;
	}

	/**
	 * 方式三 : 在第一次加载SingletonHolder时初始化一次mOnlyInstance对象, 保证唯一性, 也延迟了单例的实例化,
	 * 如果该单例比较耗资源可以使用这种模式.
	 * 
	 * @return
	 */
	public static Singleton getInstanceFromHolder() {
		return SingletonHolder.mOnlyInstance;
	}

	/**
	 * 静态内部类
	 * 
	 *
	 */
	private static class SingletonHolder {
		private static final Singleton mOnlyInstance = new Singleton();
	}

	/**
	 *  方式四 : 枚举单例, 线程安全
	 *
	 */
	enum SingletonEnum {
		INSTANCE;
		public void doSomething() {
			System.out.println("do sth.");
		}
	}

	/**
	 * 方式五 : 注册到容器, 根据key获取对象.一般都会有多种相同属性类型的对象会注册到一个map中
	 * instance容器
	 */
	private static Map<string singleton=""> objMap = new HashMap<string singleton="">();
	/**
	 * 注册对象到map中
	 * @param key
	 * @param instance
	 */
	public static void registerService(String key, Singleton instance) {
		if (!objMap.containsKey(key) ) {
			objMap.put(key, instance) ;
		}
	}
	
	/**
	 * 根据key获取对象
	 * @param key
	 * @return
	 */
	public static Singleton getService(String key) {
		return objMap.get(key) ;
	}

}

不管以哪种形式实现单例模式,它们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在这个获取的过程中你必须保证线程安全、反序列化导致重新生成实例对象等问题,该模式简单,但使用率较高。

Android源码中的模式实现

在Android系统中,我们经常会通过Context获取系统级别的服务,比如WindowsManagerService, ActivityManagerService等,更常用的是一个叫LayoutInflater的类。这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候就通过Context的getSystemService(String name)获取。我们以LayoutInflater为例来说明, 平时我们使用LayoutInflater较为常见的地方是在ListView的getView方法中。

@Override
public View getView(int position, View convertView, ViewGroup parent)	
	View itemView = null;
	if (convertView == null) {
		itemView = LayoutInflater.from(mContext).inflate(mLayoutId, null);
		// 其他代码
	} else {
		itemView = convertView;
	}
	// 获取Holder
	// 初始化每项的数据
	return itemView;
}

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

    /**
     * Obtains the LayoutInflater from the given context.
     */
    public static LayoutInflater from(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(String key)方法,我们跟踪到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) {
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());
        Process.setArgV0("");
        // 主线程消息循环
        Looper.prepareMainLooper();
        // 创建ActivityThread对象
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

    private void attach(boolean system) {
        sThreadLocal.set(this);
        mSystemThread = system;
        if (!system) {
            ViewRootImpl.addFirstDrawHandler(new Runnable() {
                public void run() {
                    ensureJitEnabled();
                }
            });
            android.ddm.DdmHandleAppName.setAppName("",
                                                    UserHandle.myUserId());
            RuntimeInit.setApplicationObject(mAppThread.asBinder());
            IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }
        } else {
               // 省略
        }
}

在main方法中,我们创建一个ActivityThread对象后,调用了其attach函数,并且参数为false. 在attach函数中, 参数为false的情况下, 会通过Binder机制与ActivityManagerService通信,并且最终调用handleLaunchActivity函数 ( 具体分析请参考老罗的博客 : Activity的启动流程),我们看看该函数的实现 。

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // 代码省略
        Activity a = performLaunchActivity(r, customIntent);
        // 代码省略
    }
    
     private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
        // 代码省略
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(         // 1 : 创建Activity
                    cl, component.getClassName(), r.intent);
         // 代码省略
        } catch (Exception e) {
         // 省略
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity); // 2 : 获取Context对象
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                // 3: 将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);

                // 代码省略
                // 4 : 调用Activity的onCreate方法
                mInstrumentation.callActivityOnCreate(activity, r.state);
                // 代码省略
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            // 代码省略
        }

        return activity;
    }


    private Context createBaseContextForActivity(ActivityClientRecord r,
            final Activity activity) {
        // 5 : 创建Context对象, 可以看到实现类是ContextImpl
        ContextImpl appContext = new ContextImpl();           appContext.init(r.packageInfo, r.token, this);
        appContext.setOuterContext(activity);

        // 代码省略
        return baseContext;
    }
    

通过上面1~5的代码分析可以知道, Context的实现类为ComtextImpl类。我们继续跟踪到ContextImpl类。

class ContextImpl extends Context {
  
    // 代码省略
    /**
     * Override this class when the system service constructor needs a
     * ContextImpl.  Else, use StaticServiceFetcher below.
     */
     static class ServiceFetcher {
        int mContextCacheIndex = -1;

        /**
         * Main entrypoint; only override if you don't need caching.
         */
        public Object getService(ContextImpl ctx) {
            ArrayList<Object> cache = ctx.mServiceCache;
            Object service;
            synchronized (cache) {
                if (cache.size() == 0) {
                    for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
                        cache.add(null);
                    }
                } else {
                    service = cache.get(mContextCacheIndex);
                    if (service != null) {
                        return service;
                    }
                }
                service = createService(ctx);
                cache.set(mContextCacheIndex, service);
                return service;
            }
        }

        /**
         * Override this to create a new per-Context instance of the
         * service.  getService() will handle locking and caching.
         */
        public Object createService(ContextImpl ctx) {
            throw new RuntimeException("Not implemented");
        }
    }

    // 1 : service容器
    private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
            new HashMap<String, ServiceFetcher>();

    private static int sNextPerContextServiceCacheIndex = 0;
    // 2: 注册服务器
    private static void registerService(String serviceName, ServiceFetcher fetcher) {
        if (!(fetcher instanceof StaticServiceFetcher)) {
            fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
        }
        SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
    }


    // 3: 静态语句块, 第一次加载该类时执行 ( 只执行一次, 保证实例的唯一性. )
    static {
        //  代码省略
        // 注册Activity Servicer
        registerService(ACTIVITY_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
                }});

        // 注册LayoutInflater service
        registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
                }});
        // 代码省略
    }

    // 4: 根据key获取对应的服务, 
    @Override
    public Object getSystemService(String name) {
        // 根据name来获取服务
        ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
        return fetcher == null ? null : fetcher.getService(this);
    }

    // 代码省略
}

从ContextImpl类的部分代码中可以看到,在虚拟机第一次加载该类时会注册各种服务,其中就包含了LayoutInflater Service, 将这些服务以键值对的形式存储在一个HashMap中,用户使用时只需要根据key来获取到对应的服务,从而达到单例的效果。这种模式就是上文中提到的“单例模式的实现方式5”。系统核心服务以单例形式存在,减少了资源消耗。

在Android中的其他运用场景

1.开源控件图片加载库Android-Universal-Image-Loader

private volatile static ImageLoader instance;

public static ImageLoader getInstance() {
        if (instance == null) {
            synchronized (ImageLoader.class) {
                if (instance == null) {
                    instance = new ImageLoader();
                }
            }
        }
        return instance;
    }

2.Android的事件发布-订阅总线 EventBus

private static volatile EventBus defaultInstance;

public static EventBus getDefault() {
	if (defaultInstance == null) {
		synchronized (EventBus.class) {
			if (defaultInstance == null) {
				defaultInstance = new EventBus();
			}
		}
	}
	return defaultInstance;
}

3.ActivityManager中管理Activity堆栈

public class ActivityManager {
    
    private static ActivityManager instance;
    private Stack<Activity> activityStack;// activity栈

    // 单例模式
    public static ActivityManager getInstance() {
        if (instance == null) {
            instance = new ActivityManager();
        }
        return instance;
    }

    // 把一个activity压入栈中
    public void pushOneActivity(Activity actvity) {
        if (activityStack == null) {
            activityStack = new Stack<>();
        }
        activityStack.add(actvity);
    }

    // 获取栈顶的activity,先进后出原则
    public Activity getLastActivity() {
        return activityStack.lastElement();
    }

    // 移除一个activity
    public void popOneActivity(Activity activity) {
        if (activityStack != null && activityStack.size() > 0) {
            if (activity != null) {
                activity.finish();
                activityStack.remove(activity);
                activity = null;
            }

        }
    }

    // 退出所有activity
    public void finishAllActivity() {
        if (activityStack != null) {
            while (activityStack.size() > 0) {
                Activity activity = getLastActivity();
                if (activity == null)
                    break;
                popOneActivity(activity);
            }
        }
    }
}

在 Android 中使用单例还有哪些需要注意的地方

单例在 Android 中的生命周期等于应用的生命周期,所以要特别小心它持有的对象是否会造成内存泄露。如果将 Activity 等 Context 传递给单例又没有释放,就会发生内存泄露,所以最好仅传递给单例 Application Context。

总结

单例模式作为最容易理解的一种设计模式,同时也是运用频率很高的模式,由于客户端通常没有高并发的情况,因此,选择哪种实现方式不会有太大的影响。但是出于效率考虑,推荐使用使用DCL静态内部类实现方式。

每个模式都有它的优缺点和适用范围,相信大家看过的每一本介绍模式的书籍,都会详细写明某个模式适用于哪些场景。我的观点是,我们要做的是更清楚地了解每一个模式,从而决定在当前的应用场景是否需要使用,以及如何更好地使用这个模式。就像《深入浅出设计模式》里说的:

使用模式最好的方式是:“把模式装进脑子里,然后在你的设计和已有的应用中,寻找何处可以使用它们。”

单例模式是经得起时间考验的模式,只是在错误使用的情况下可能为项目带来额外的风险,因此在使用单例模式之前,我们一定要明确知道自己在做什么,也必须搞清楚为什么要这么做。

参考资料:

1.何红辉,关爱民. Android 源码设计模式解析与实战[M]. 北京:人民邮电出版社

2.单例模式都用在什么地方?能举几个例子吗

你可能感兴趣的:(Android,手机端,设计模式,架构设计,Android设计模式)