單例模式(Singleton)是一種使用率非常高的設(shè)計模式朝抖,其主要目的在于保證某一類在運行期間僅被創(chuàng)建一個實例,并為該實例提供了一個全局訪問方法晌端,通常命名為getInstance()方法喜最。在APP開發(fā)中,我們會遇到大量的單例聚蝶,如各種ImageManager、ShareManager藻治、DownloadManger碘勉、ApiService等,此外單例的不恰當(dāng)使用還會帶來內(nèi)存泄露問題桩卵,因此验靡,對單例進行統(tǒng)一封裝倍宾、管理就顯得很有必要了。
先從單例模式說起
單例模式的實現(xiàn)主要有以下幾種方式:餓漢式胜嗓、懶漢式高职、靜態(tài)內(nèi)部類、enum等辞州,在實操過程中怔锌,我們經(jīng)常采用線程安全下的懶漢式和靜態(tài)內(nèi)部類式實現(xiàn),我們簡單回顧以下這兩種方式:
懶漢式
所謂懶漢变过,就是lazy load埃元,主要解決的問題是避免單例在classLoader的時候就被預(yù)先創(chuàng)建,而是在使用的時候再去創(chuàng)建媚狰,同時亚情,這這個模式下聽過線程鎖機制來保證線程安全,它的實現(xiàn)如下:
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;
}
}
靜態(tài)內(nèi)部類式
這種方式是通過Java類加載機制來保證線程安全哈雏,JVM Class Loader時會執(zhí)行類的靜態(tài)變量賦初始值和執(zhí)行靜態(tài)代碼塊中的內(nèi)容,ClassLoader肯定是單線程的衫生,保證了單例的唯一性裳瘪。而靜態(tài)內(nèi)部類只有在調(diào)用了getIntance方法時,才會觸發(fā)內(nèi)部類的裝載罪针,因此這又保證了延遲加載彭羹。具體的實現(xiàn)如下:
public class Singleton {
private static class Instance {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return Instance.instance;
}
}
再談單例的封裝
我們要解決的問題有兩個:
- 方便的單例創(chuàng)建;
- 避免內(nèi)存泄露泪酱,尤其是APP開發(fā)過程中的 context 造成的泄露問題
具體實現(xiàn)
DJContext
DJContext 持有 ApplicationContext, 避免因為 context 帶來的內(nèi)存泄露問題派殷;DJContext 提供統(tǒng)一的單例獲取方式,比如:
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
采用靜態(tài)注冊的方式墓阀,注冊不同單例的獲取方式毡惜,同時通過內(nèi)部抽象類實現(xiàn)延遲加載。
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預(yù)先進行register帕膜;
然后使用時通過以下方式:
ImageManager imgManager = DJContext.getService(ImageManager.class);
缺點
因為要實現(xiàn)單例可以被 register,所以單例類的構(gòu)造方式只能是 public/ protected 的溢十,這與單例的構(gòu)造方法需是 private 有所出入垮刹。針對這一點,使用過程中可以通過將所有的單例放到一個 package 下张弛,然后采用 protected 形式的構(gòu)造方法.
這里我把這種封裝稱之為常規(guī)式荒典,顯然還有一種非常規(guī)式酪劫,非常規(guī)式可以基于動態(tài)代理來實現(xiàn)封裝。敬請期待...