通常,我们的单例模式都需要有一个静态的函数来获取instance,如:
public static synchronized DataManager getInstance() {
if (null == sInstance) {
sInstance = new DataManager();
}
return sInstance;
}
在使用的时候,我们可以:
DataManager.getInstnace().initialize(context);
DataManager.getInstnace().doSomething();
但是,也有这种写法,如:
public static synchronized DataManager getInstance(Context context) {
if (null == sInstance) {
sInstance = new DataManager(context);
}
return sInstance;
}
在使用的时候,我们可以:
DataManager.getInstnace(context).doSomething();
讨论:两种方式看起来都是正确的,我们应该如何取舍呢,哪种方式更好?
支持getInstance(context)的观点:
1. 在初始化单实例时,需要必要的参数来创建对象,DataManager.getInstnace().initialize(context),那就会有一个疑问,每次getInstance()之后,我到底要不要再调用一个函数来初始化?它无法保障DataManager已被正确的初始化。
支持getInstance()的观点:
1. 单例的设计,一般不需要初始化参数来创建,如果需要一个或者多个参数来初始化,那为什么要考虑使用单例呢?使用一个普通的类不是更好吗?单例提供的是一个唯一的访问据点,更多的用来做管理类(XXXManager),维护全局数据,一般它不需要额外的参数来初始化本身。
2. 既然是单例,就不要产生歧义,如果还需要参数的话,不同的参数(不一定是context,可以是其它数据)是否会产生不同的实例?
3. 使用getInstance(context),每次传进去context,都会重新初始化(或重置)我的单例子参数吗?
4. 使用getInstance(context),在调用层次很深的类里面,我还得要缓存个context,或者调用函数里面都得加上context参数?
结论:从上来的观点来看,个人更加倾向于使用无参的getInstnace()方式。
如果你真的需要参数(可能不止一个)来构造或者初始化你的instance,可以考虑提供initialize方法,并在程序的入口处初始化,这样,在其它任何地方使用时,不需要考虑初始化问题,看上去似乎有些牵强,不过我们可以找一个具体的使用实例。我们看看大名鼎鼎的Android-Universal-Image-Loader的使用方式:
public class UILApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initImageLoader(getApplicationContext());
}
public static void initImageLoader(Context context) {
// This configuration tuning is custom. You can tune every option, you may tune
// some of them, or you can create default configuration by
// ImageLoaderConfiguration.createDefault(this); method.
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
.threadPriority(Thread.NORM_PRIORITY - 2)
.denyCacheImageMultipleSizesInMemory()
.diskCacheFileNameGenerator(new Md5FileNameGenerator())
.diskCacheSize(50 * 1024 * 1024) // 50 Mb
.tasksProcessingOrder(QueueProcessingType.LIFO)
.writeDebugLogs() // Remove for release app
.build();
// Initialize ImageLoader with configuration.
ImageLoader.getInstance().init(config);
}
}
在Android源码中,有无数单例,我们也一起看看其使用方式:
\frameworks\base\core\java\android\view\WindowManagerGlobal.java
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
像以下类(仅列出几个),都是采用的上面的方式,这种类型的使用是用得最广泛的:
\frameworks\base\core\java\android\app\ResourcesManager.java
\frameworks\base\core\java\android\hardware\display\DisplayManagerGlobal.java
\frameworks\base\core\java\android\webkit\CookieManager.java
\frameworks\base\core\java\android\view\accessibility\AccessibilityInteractionClient.java
\frameworks\base\core\java\android\net\http\CertificateChainValidator.java
private static class NoPreloadHolder {
private static final CertificateChainValidator sInstance = new CertificateChainValidator();
private static final HostnameVerifier sVerifier = HttpsURLConnection
.getDefaultHostnameVerifier();
}
public static CertificateChainValidator getInstance() {
return NoPreloadHolder.sInstance;
}
带参数的getInstance(Context context),也有不少,但数量要小得多:
\frameworks\base\core\java\android\view\accessibility\AccessibilityManager.java
\frameworks\base\core\java\android\hardware\location\GeofenceHardwareImpl.java
\frameworks\base\core\java\android\appwidget\AppWidgetManager.java
Context mContext;
public static AppWidgetManager getInstance(Context context) {
synchronized (sManagerCache) {
if (sService == null) {
IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
sService = IAppWidgetService.Stub.asInterface(b);
}
WeakReference<AppWidgetManager> ref = sManagerCache.get(context);
AppWidgetManager result = null;
if (ref != null) {
result = ref.get();
}
if (result == null) {
result = new AppWidgetManager(context);
sManagerCache.put(context, new WeakReference<AppWidgetManager>(result));
}
return result;
}
}
private AppWidgetManager(Context context) {
mContext = context; // 缓存context
mDisplayMetrics = context.getResources().getDisplayMetrics();
}
public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
try {
sService.updateAppWidgetIds(appWidgetIds, views, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
}
上面的这个例子,每次在获取单例子时,需要带上context,用起来麻烦。但也有好处,实例在初始化后并将context缓存起来,当内部的函数需要使用context(如updateAppWidget函数),直接使用缓存的mContext即可。
如果让我来设计这个类,我有两种改造方式:
- 不缓存context,在需要使用context的地方,让调用者以参数的形式表现出来,如下:
public void updateAppWidget(Context context, int[] appWidgetIds, RemoteViews views)
- 缓存context,提供ininalize方法来设置context。但很明显这有比较大的漏洞,因为在使用其它方法前,我必须要确保initialize方法已被调用过,否则在调用像updateAppWidget这种需要context的方法时,其行为就不正确了。
(写到这里,其实我对我之前的观点也动摇了,因为各有优缺点,因此,还是得需要根据实际情况来做判断)
在源码中搜了一下,还有一些另类的getInstance,支持多参数,仔细看一下,发现它就起到了一个缓存的作用,严格的来讲,已经不是单例的范畴了。
\frameworks\base\core\java\android\text\method\DigitsKeyListener.java
public class DigitsKeyListener extends NumberKeyListener {
private static DigitsKeyListener[] sInstance = new DigitsKeyListener[4];
public DigitsKeyListener() {
this(false, false);
}
public DigitsKeyListener(boolean sign, boolean decimal) {
mSign = sign;
mDecimal = decimal;
int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
mAccepted = CHARACTERS[kind];
}
public static DigitsKeyListener getInstance() {
return getInstance(false, false);
}
public static DigitsKeyListener getInstance(boolean sign, boolean decimal) {
int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
if (sInstance[kind] != null)
return sInstance[kind];
sInstance[kind] = new DigitsKeyListener(sign, decimal);
return sInstance[kind];
}
}
最后,推荐一篇经典的文章,它教你如何构造一个完美的单例。
http://www.iteye.com/topic/575052
另外,再记录一个《Head First Design Patterns》中推荐的单例:
public class Singleton {
/**
* The volatile keyword ensures that multiple threads
* handle the sInstance variable correctly when it
* is being initialized to the Singleton instance.
*/
private volatile static Singleton sInstance;
private Singleton() {}
public static Singleton getInstance() {
if(sInstance == null) {
synchronized (Singleton.class) {
if(sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}