結合 Android 看看單例模式怎麼寫

IAM四十二發表於2019-01-03

定義及使用場景

定義

單例模式,就是在整個系統中某一個類的例項只有一個,並且自行例項化向整個系統提供;簡單來說,就是某個類被例項化的方式是唯一的;同時他它必須向系統自動提供這個例項。

使用場景

  • 可以避免產生多個物件消耗過多的資源,如I/O訪問等。
  • 某些類的物件就是應該只有,多個物件將導致邏輯錯誤或混亂。

常見的實現方式

下面是單例模式常見的兩種實現方式 餓漢模式和 雙重鎖模式

  • 餓漢模式
public class HungrySingleton {

    private static HungrySingleton mInstance = new HungrySingleton();

    private HungrySingleton() {

    }

    public static HungrySingleton getInstance() {
        return mInstance;
    }
}複製程式碼

不得不說,餓漢模式這個名字起得的確很巧,這種方式,不管你用不用得著這個例項,先給你建立(new)出來,生怕將來建立沒機會似得,完全就是今朝有酒今朝醉的節奏。

與上面對應的還有一種就是懶漢模式,就是在用的時候才在getInstance 方法中完成例項的建立(new),真是“懶”,同時給這個方法新增synchronized 關鍵字,可以確保在多執行緒情況下單例依舊唯一,但是懶漢模式每次呼叫getInstance 方法時由於synchronized 的存在,需要進行同步,造成不必要的資源開銷。因此便有了下面雙重鎖模式的實現方式。

  • 雙重鎖模式(DCL 實現)
public class LazySingleton {
    private static LazySingleton mInstance = null;

    private LazySingleton() {

    }

    public static LazySingleton getInstance() {
        if (mInstance == null) {
            synchronized (LazySingleton.class) {
                if (mInstance == null) {
                    mInstance = new LazySingleton();
                }
            }
        }

        return mInstance;
    }
}複製程式碼

這樣既避免了餓漢模式的缺點,又解決了懶漢模式的不足;確保單例只在第一次真正需要的時候建立。

Android 中的使用

在日常的Android開發中,也可以見到單例模式的身影。

  • Glide
    使用Glide載入圖片非常方便,大家應該不陌生,可以看一下它的原始碼中單例模式的實現方式。
    Glide.with(this).load(url).into(imageView);
    //Glide.with()
    public static RequestManager with(FragmentActivity activity) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(activity);
    }
   //RequestManagerRetriever.get()
     /** The singleton instance of RequestManagerRetriever. */
    private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever();
     /**
     * Retrieves and returns the RequestManagerRetriever singleton.
     */
    public static RequestManagerRetriever get() {
        return INSTANCE;
    }複製程式碼

可以看到,當我們寫下Glide.with(..) 這行程式碼時,就完成了RequestManagerRetriever 這個類的例項化,這個類的單例模式是使用餓漢模式實現的。

  • EventBus
    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    };複製程式碼

很明顯,EventBus的單例模式使用雙重鎖模式實現的。

  • InputMethodManager
      static InputMethodManager sInstance
      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;
          }
      }複製程式碼

    InputMethodManager 的單例模式是使用懶漢模式實現。

可以看到,關於單例模式的實現方式,面對不同的場景,我們可以做出不同的選擇

  • Glide的單例模式雖然是使用餓漢模式實現,但理論上來說並不會造成記憶體資源的浪費,因為當我們通過gradle的配置引入Glide的庫時,就是為了載入圖片,必然會使用Glide.with進行相關的操作。同時RequestManagerRetriever 這個類應該是一個網路請求的管理類(Glide原始碼沒有研究過,這裡只是猜測),這樣的一個類必然需要使用單列模式,試想如果存在多個管理類的例項,那麼談何管理,那麼的多Request到底聽哪個manger 的,這就是前面提到必須使用單列模式的情景。

  • EventBus 作為事件匯流排的更要使用單例模式了,如果說EventBus的例項不是單例模式,那麼他就無法實現它的功能了。對於EventBus不瞭解的同學,可以看看EventBus 3.0 相見恨晚,EventBus真的很強大。

  • InputMethodManager 使用懶漢模式實現單例也是無可厚非的,畢竟誰會去頻繁的獲取那麼多他的例項呢;同時作為一個系統的輸入法管理器,他也必須是唯一的,因此這個類也需要單例模式來實現它唯一的例項供外部使用。

由上可見,關於單例模式的實現,沒有說哪一種方式最好,只有最合適的實現方式;實際開發中,單例模式應該怎麼寫,還需要根據業務場景做最合適的選擇,無論是餓漢懶漢實用才是好漢。個人感覺,餓漢模式是一種簡單又方便的實現方式, 一個類既然已經寫成了單例模式,必然是要使用的呀,誰會去建立一個餓漢模式的單例,又不去使用這個單例呢?

之前在使用Volley的時候,就是使用餓漢模式建立整個應用的RequestQueue單例,所有需要網路請求的地方,把request新增到RequestQueue單例中即可。

public class MyApplication extends Application{
    // 建立請求佇列
    public static RequestQueue queue;

    @Override
    public void onCreate() {
        super.onCreate();
        queue = Volley.newRequestQueue(getApplicationContext());
    }

    public static RequestQueue getHttpQueue() {
        return queue;
    }
}複製程式碼

在應用Application的onCreate方法中建立了屬於整個應用的queue,之後每一次網路請求時,只需要queue.add(Request)即可,這裡使用單例模式,可以有效的避免在多個地方建立RequestQueue 的例項,浪費系統資源。

更多

在某些複雜的場景中,上述的兩種方式都或多或少的存在一些缺陷。因此便有了以下兩種單例模式的實現方式。

靜態內部類

public class StaticSingleton {
    private StaticSingleton(){

    }

    public static StaticSingleton getInstance(){
        return SingletonHolder.mInstance;
    }

    /**
     * 靜態內部類
     */
    private static class SingletonHolder{
        private static final StaticSingleton mInstance=new StaticSingleton();
    }
}複製程式碼

可以說,這是最安全的實現方式了,無論怎樣,這樣產生的單例必然是單例。

列舉單例

public enum  EnumSingleton {
    INSTANCE;
}複製程式碼

定義一個列舉元素,而他就是單例;可以說,這是實現單例最簡單最實惠的方式;可以有效的避免單例在反序列化的過程中被建立,從而讓單例變得不唯一。但是,Google官方是不建議在Android開發中使用列舉的,所以使用具體使用哪種方式實現單例模式,仁者見仁智者見智了。

單例模式是設計模式中最簡單的一種,因為他最容易理解;但通過上述分析可以看到,簡單不意味著隨意,針對不同的業務場景,需要我們仔細斟酌單例模式的實現方式


好了,關於單例模式就是這些了。

相關文章