單例模式(Singleton)是一種使用率非常高的設計模式,其主要目的在于保證某一類在運行期間僅被創建一個實例,并為該實例提供了一個全局訪問方法,通常命名為getInstance()方法。在APP開發中,我們會遇到大量的單例,如各種ImageManager、ShareManager、DownloadManger、ApiService等,此外單例的不恰當使用還會帶來內存泄露問題,因此,對單例進行統一封裝、管理就顯得很有必要了。
先從單例模式說起
單例模式的實現主要有以下幾種方式:餓漢式、懶漢式、靜態內部類、enum等,在實操過程中,我們經常采用線程安全下的懶漢式和靜態內部類式實現,我們簡單回顧以下這兩種方式:
懶漢式
所謂懶漢,就是lazy load,主要解決的問題是避免單例在classLoader的時候就被預先創建,而是在使用的時候再去創建,同時,這這個模式下聽過線程鎖機制來保證線程安全,它的實現如下:
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
靜態內部類式
這種方式是通過Java類加載機制來保證線程安全,JVM Class Loader時會執行類的靜態變量賦初始值和執行靜態代碼塊中的內容,ClassLoader肯定是單線程的,保證了單例的唯一性。而靜態內部類只有在調用了getIntance方法時,才會觸發內部類的裝載,因此這又保證了延遲加載。具體的實現如下:
public class Singleton {
private static class Instance {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return Instance.instance;
}
}
再談單例的封裝
我們要解決的問題有兩個:
- 方便的單例創建;
- 避免內存泄露,尤其是APP開發過程中的 context 造成的泄露問題
具體實現
DJContext
DJContext 持有 ApplicationContext, 避免因為 context 帶來的內存泄露問題;DJContext 提供統一的單例獲取方式,比如:
UserManager um = DJContext.getService(UserManager.class);
public final class DJContext {
private static Context mContext;
public static void init(Context context) {
mContext = context.getApplicationContext();
}
public static <T> T getService(Class<T> tClass) {
checkContextValid();
return ServiceRegistry.getService(mContext, tClass);
}
private static void checkContextValid() {
if (mContext == null) {
throw new IllegalStateException("must call method [init] first !!!");
}
}
}
ServiceRegistry
采用靜態注冊的方式,注冊不同單例的獲取方式,同時通過內部抽象類實現延遲加載。
final class ServiceRegistry {
private static final Map<String, ServiceFetcher> SERVICE_FETCHERS = new HashMap<>();
private static <T> void registerService(String name, ServiceFetcher<T> fetcher) {
SERVICE_FETCHERS.put(name, fetcher);
}
static {
registerService(UserManager.class.getName(), new ServiceFetcher<UserManager>() {
@Override
public UserManager createService(Context context) {
return new UserManager();
}
});
registerService(ImageManager.class.getName(), new ServiceFetcher<ImageManager>() {
@Override
public ImageManager createService(Context context) {
return new ImageManager(context);
}
});
}
public static <T> T getService(Context context, Class<T> tClass) {
final ServiceFetcher fetcher = SERVICE_FETCHERS.get(tClass.getName());
return fetcher != null ? (T) fetcher.getService(context) : null;
}
private static abstract class ServiceFetcher<T> {
private T mService;
public final T getService(Context context) {
synchronized (ServiceFetcher.this) {
if (mService == null) {
mService = createService(context);
}
return mService;
}
}
public abstract T createService(Context context);
}
}
使用方式
使用分兩步:
1、DJContext的初始化:一般放在 Application.onCreate() 中;
@Override
public void onCreate() {
super.onCreate();
DJContext.init(this);
}
2、通過DJContext獲取實例;
比如有個實例叫做:
public class ImageManager {
private Context context;
public ImageManager(Context context) {
this.context = context;
}
}
在ServiceRegistry預先進行register;
然后使用時通過以下方式:
ImageManager imgManager = DJContext.getService(ImageManager.class);
缺點
因為要實現單例可以被 register,所以單例類的構造方式只能是 public/ protected 的,這與單例的構造方法需是 private 有所出入。針對這一點,使用過程中可以通過將所有的單例放到一個 package 下,然后采用 protected 形式的構造方法.
這里我把這種封裝稱之為常規式,顯然還有一種非常規式,非常規式可以基于動態代理來實現封裝。敬請期待...