單例模式(Singleton)是一種使用率很是高的設計模式,其主要目的在於保證某一類在運行期間僅被建立一個實例,併爲該實例提供了一個全局訪問方法,一般命名爲getInstance()方法。在APP開發中,咱們會遇到大量的單例,如各類ImageManager、ShareManager、DownloadManger、ApiService等,此外單例的不恰當使用還會帶來內存泄露問題,所以,對單例進行統一封裝、管理就顯得頗有必要了。設計模式
單例模式的實現主要有如下幾種方式:餓漢式、懶漢式、靜態內部類、enum等,在實操過程當中,咱們常常採用線程安全下的懶漢式和靜態內部類式實現,咱們簡單回顧如下這兩種方式:安全
所謂懶漢,就是lazy load,主要解決的問題是避免單例在classLoader的時候就被預先建立,而是在使用的時候再去建立,同時,這這個模式下聽過線程鎖機制來保證線程安全,它的實現以下:bash
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方法時,纔會觸發內部類的裝載,所以這又保證了延遲加載。具體的實現以下:ide
public class Singleton {
private static class Instance {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return Instance.instance;
}
}
複製代碼
咱們要解決的問題有兩個:fetch
DJContextui
DJContext 持有 ApplicationContext, 避免由於 context 帶來的內存泄露問題;DJContext 提供統一的單例獲取方式,好比: UserManager um = DJContext.getService(UserManager.class);this
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 !!!");
}
}
}
複製代碼
ServiceRegistryspa
採用靜態註冊的方式,註冊不一樣單例的獲取方式,同時經過內部抽象類實現延遲加載。線程
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);
}
}
複製代碼
使用分兩步:設計
一、DJContext的初始化:通常放在 Application.onCreate() 中;
@Override
public void onCreate() {
super.onCreate();
DJContext.init(this);
}
複製代碼
二、經過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 形式的構造方法.
這裏我把這種封裝稱之爲常規式,顯然還有一種很是規式,很是規式能夠基於動態代理來實現封裝。敬請期待...