基本概念
单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,从而方便对实例个数的控制并节约系统资源。
单例模式有三个特点;
1、某个类只能有一个实例;
2、它要自行创建这个实例;
3、它只有唯一途径向整个系统提供这个实例。
从具体实现的代码来看,就是完成以下三点工作:
1、单例模式的类只提供私有的构造函数;
2、类定义中含有一个该类的静态私有对象;
3、该类提供了一个静态且公有的函数用于创建或获取它本身的静态私有对象;
Android中的使用场合
Android开发中有用到单例模式的场合,主要具备唯一性、持续性、不可重复等特征,具体说来,有下列四种场合满足单例模式的需求:
一、app运行过程在内存中常驻的情况,包括:
1、Application。Application在app生命周期中一直存在,可用来保存Handler、各种容器等全局变量。Application的详细介绍见《 Android开发笔记(二十八)利用Application实现内存读写》。
2、图片缓存框架。缓存的图片需要常驻在内存中,这样无论在哪个页面显示图片,都能利用缓存及时获取图片。图片缓存框架的详细介绍见《 Android开发笔记(七十七)图片缓存算法》。
二、使用统一的数据库的情况,包括:
1、SQLite。防止数据库操作冲突,SQLite的详细介绍见《 Android开发笔记(三十)SQLite数据库基础操作》。
2、Realm。防止数据库操作冲突,Realm的详细介绍见《 Android开发笔记(八十五)手机数据库Realm》。
三、使用统一的配置文件的情况,包括:
1、SharedPreferences。防止配置文件读写冲突,SharedPreferences的详细介绍见《 Android开发笔记(二十九)使用SharedPreferences存取数据》。
2、Properties。防止配置文件读写冲突,Properties的详细介绍见《 Android开发笔记(八十四)使用Properties读写属性值》。
四、设备不能重复打开的情况,包括:
1、Camera。重复打开摄像头会报错,Camera的详细介绍见《 Android开发笔记(五十六)摄像头拍照》。
单例模式的构造方法
单例模式有两类构造方式,一类是懒汉式,在使用时才加载;另一类是饿汉式,在启动时就加载。
懒汉式构造
懒汉方式
该方式很简单,在getInstance函数中判断实例为空时才构造新实例,代码示例如下:
private static App instance;
public static App getInstance() {
if (instance == null) {
instance = new App();
}
return instance;
}
该方式的毛病是线程不安全,因为多线程情况下可能同时调用getInstance方法,同时生成不同的实例造成数据不同步。
加锁方式
因为简单的懒汉方式在多线程时存在问题,所以我们考虑引入同步锁机制,确保同一时刻只有唯一线程执行getInstance方法。同步锁的说明参见《 Android开发笔记(八十八)同步与加锁》。
使用synchronized加锁又有两种方式,一种是给方法加锁,另一种是给代码块加锁。下面是Android中两种加锁方式的代码例子:
Calendar采用的是给方法加锁:
public static synchronized Calendar getInstance() {
return new GregorianCalendar();
}
UserManager采用的也是给方法加锁:
private static UserManager sInstance = null;
//get是隐藏方法
public synchronized static UserManager get(Context context) {
if (sInstance == null) {
sInstance = (UserManager) context.getSystemService(Context.USER_SERVICE);
}
return sInstance;
}
LocalBroadcastManager采用的是给代码块加锁:
private static LocalBroadcastManager mInstance;
public static LocalBroadcastManager getInstance(Context context) {
synchronized (mLock) {
if (mInstance == null) {
mInstance = new LocalBroadcastManager(context.getApplicationContext());
}
return mInstance;
}
}
InputMethodManager采用的也是给代码块加锁:
static InputMethodManager sInstance;
//getInstance是隐藏方法
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
return sInstance;
}
}
该方式解决了线程不安全的问题,可是运行效率比较低,因为每次获取实例都要判断同步锁,浪费系统资源。
双重校验锁方式
既要保证线程安全,又要提高运行效率,于是出现了双重校验锁方式,即在同步代码块前再加个实例是否为空的判断。下面是图片缓存框架Universal-Image-Loader的代码例子:
private static volatile ImageLoader instance;
public static ImageLoader getInstance() {
if (instance == null) {
synchronized (ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader();
}
}
}
return instance;
}
该方法在多数场合可以完美运行,但个别时候会出现异常。因为在不同平台的编译过程中,可能出现instance还没初始化、就被分配内存空间的情况,也就是说会出现instance非空但是又没初始化的情况,这样就会导致返回的实例是不完整的。
饿汉式构造
饿汉方式
该方式采用在声明实例时就进行初始化的做法,这样程序启动之后便会自动创建对象,使用时直接把创建好的对象拿来即可。下面是Android中SmsManager类运用单例模式的代码例子:
private static final SmsManager sInstance = new SmsManager();
public static SmsManager getDefault() {
return sInstance;
}
private SmsManager() {
//nothing
}
改进方式(静态内部类)
简单的饿汉方式多数情况下也够用了,只是不管这个类有没有用到,都统统加载一个实例对象,这在内存上有些许浪费。改进的做法是静态内部类中加载实例,这样不必在程序启动时加载,只会在引用内部类时才加载。下面是一个在静态内部类中初始化实例的代码例子:
public class SingletonInner {
private static class SingletonHolder {
private static SingletonInner instance = new SingletonInner();
}
private SingletonInner() {
}
public static SingletonInner getInstance() {
return SingletonHolder.instance;
}
}
从上面可以看出,静态内部类在程序运行过程只有一个对象,所以保证了实例的唯一性。
点此查看Android开发笔记的完整目录