設計模式 in Android——單例模式

前言

Sqlite數據庫操作輔助類(各種DbManager)、線程池、網絡請求等會消耗大量的資源,應儘量避免頻繁地創建與銷燬對象,造成資源的浪費;其次,文件系統、緩存、日誌對象等,需要保證程序中有且只有一個,否則會導致狀態不一直等問題。
這時,就需要我們用一種機制實現此類僅有一個實例,也即是單例模式。
單例模式的基本模式都是私有化構造函數以及提供一個訪問內部靜態單例的方法:
單例模式
單例的特點決定了它擴展困難、測試困難,修改有可能會影響一大片;而且單例非常容易引發內存泄露,最常見的,就是持有了非Application的context的強應用,導致context對象不能釋放;而且在Android中,當進程被殺死時,單例的狀態也可能不一致,比如在Application中存放的內存數據,在應用重啓時會丟失。

單例 in java

線程安全的單例的實現主要有以下幾種(圖片照抄的):
在這裏插入圖片描述
此外,使用容器(常見的是Map)管理單例也是一種方式。

/**
 * java單例模式,只考慮線程安全的
 * Created by lk on 2018/9/26.
 */
public class SingleTonJava implements Parcelable, Serializable {
    private String data;

    private SingleTonJava() {
    }

    private SingleTonJava(String data) {
        this.data = data;
    }

    //------------------------------
    // 餓漢,類創建的同時就實例化了靜態對象,適用於無參構造,如果一個類初始化需要耗費很多時間,
    // 或應用程序總是會使用到該單例,那建議使用餓漢模式,
    // 比如各種補丁加載、緩存信息、系統配置信息等
    //------------------------------
    private static SingleTonJava INSTANCE = new SingleTonJava();
    public static SingleTonJava getInstance() {
        return INSTANCE;
    }

    //------------------------------
    // 懶漢雙重校驗鎖(synchronized效率差,不考慮),延遲初始化,節省資源。對於不需一直使用、不一定會用到、
    // 或資源敏感的單例,如相機管理、音頻管理等類,可以使用此種方式。
    // 此種方式可以支持參數化初始化,但只有第一個參數起效...
    //------------------------------
    private volatile static SingleTonJava INSTANCE;
    public static SingleTonJava getInstance(String data) {
        if (INSTANCE == null) {
            synchronized (SingleTonJava.class) {
                if (INSTANCE == null) {
                    INSTANCE = new SingleTonJava(data);
                }
            }
        }
        return INSTANCE;
    }

    //------------------------------
    // 內部靜態類,也是無法使用參數化構造初始化。
    // classloader會在實際使用到SingleTonJavaHolder類時纔去加載SingleTonJavaHolder
    //------------------------------
    private static class SingleTonJavaHolder {
        private static SingleTonJava INSTANCE = new SingleTonJava();
    }
    public static SingleTonJava getInstance(String data) {
        return SingleTonJavaHolder.INSTANCE;
    }
    
    //------------------------------
    // 枚舉單例,最簡單,線程安全+防反射+防序列化+防克隆+懶加載,同樣不能參數化構造
    //------------------------------
    enum SingleTonJav8a {
        INSTANCE;
        public void fun() {
        }
    }

    //------------------------------
    // 容器單例,將多種單例模式注入到一個統一的管理類中,在使用時根據key獲取對應類型的對象。
    // Android系統的各種Service就是這模式
    //------------------------------
    private static Map<String, Object> map = new ConcurrentHashMap<>();
    public static void registerInstance(String key, Object instance) {
        map.put(key, instance);
    }
    public static Object getInstance(String key) {
        return map.get(key);
    }
}

除必須使用餓漢式的情況,懶加載無參構造時,推薦使用枚舉或內部靜態類單例,有參構造時也可以使用DCL單例,我們項目中常用的EventBus用的就是DCL的單例模式:

/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
     if (defaultInstance == null) {
         synchronized (EventBus.class) {
             if (defaultInstance == null) {
                 defaultInstance = new EventBus();
             }
         }
     }
     return defaultInstance;
 }

單例保證

java中,使用克隆、反序列化以及反射,可以繞過私有構造方法,創建出新的對象。鑑於要保持單例唯一,除使用枚舉,其他模式都必須防止創建新的單例對象。

枚舉單例

摘自深入理解Java枚舉類型(enum)
通過反編譯枚舉生成的class枚舉,可以發現枚舉就是繼承Enum類的final類:

// 聲明枚舉
enum EXAMPLE {
   ENUM1, ENUM2
}

// 反編譯生成的枚舉類
final class EXAMPLE extends Enum
{
    //編譯器爲我們添加的靜態的values()方法,用於class的getEnumConstantsShared反射獲取所有枚舉值數組
    public static EXAMPLE [] values()
    {
        return (EXAMPLE [])$VALUES.clone();
    }
    //編譯器爲我們添加的靜態的valueOf()方法,注意間接調用了Enum也類的valueOf方法
    public static EXAMPLE valueOf(String s)
    {
        return (EXAMPLE )Enum.valueOf(xxx.EXAMPLE.class, s);
    }
    //私有構造函數
    private EXAMPLE(String s, int i)
    {
        super(s, i);
    }
     // 前面定義的2種枚舉實例
    public static final EXAMPLE ENUM1;
    public static final EXAMPLE ENUM2;
    private static final EXAMPLE $VALUES[];

    static 
    {    
        //實例化枚舉實例
        ENUM1 = new EXAMPLE("ENUM1", 0);
        ENUM2 = new EXAMPLE("ENUM2", 1);
        $VALUES = (new Day[] {
            ENUM1, ENUM2
        });
    }
}

這不就是類似於容器單例了麼…而在Enum的源碼中,重寫了clone:

// Enum.java節選
//--------------------------------------------
// 防止克隆
//--------------------------------------------
protected final Object clone() throws CloneNotSupportedException {
       throw new CloneNotSupportedException();
}

編譯器不允許任何對這種序列化機制的定製,並禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法:

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
    throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
    throw new InvalidObjectException("can't deserialize enum");
}

在序列化時,Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是通過java.lang.Enum的valueOf方法來根據名字查找枚舉對象,防止反序列化:

public static <T extends Enum<T>> T valueOf(Class<T> var0, String var1) {
   Enum var2 = (Enum)var0.enumConstantDirectory().get(var1);
    if(var2 != null) {
        return var2;
    } else if(var1 == null) {
        throw new NullPointerException("Name is null");
    } else {
        throw new IllegalArgumentException("No enum constant " + var0.getCanonicalName() + "." + var1);
    }
}

// class.java節選
Map<String, T> enumConstantDirectory() {
     if(this.enumConstantDirectory == null) {
         // getEnumConstantsShared最終通過反射調用生成的枚舉類的values方法
         Object[] var1 = this.getEnumConstantsShared();
         if(var1 == null) {
             throw new IllegalArgumentException(this.getName() + " is not an enum type");
         }

         HashMap var2 = new HashMap(2 * var1.length);
         Object[] var3 = var1;
         int var4 = var1.length;
         
         // var2存放了當前enum類的所有枚舉實例變量,以name爲key值
         for(int var5 = 0; var5 < var4; ++var5) {
             Object var6 = var3[var5];
             var2.put(((Enum)var6).name(), var6);
         }
         
         this.enumConstantDirectory = var2;
     }

     return this.enumConstantDirectory;
 }

而對於反射,使用Constructor.newInstance方法構造Enum時,會拋IllegalArgumentException,查看newInstance方法:

@CallerSensitive
public T newInstance(Object... var1) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    if(!this.override && !Reflection.quickCheckMemberAccess(this.clazz, this.modifiers)) {
        Class var2 = Reflection.getCallerClass();
        this.checkAccess(var2, this.clazz, (Object)null, this.modifiers);
    }

    // 判斷是否爲枚舉修飾符,如果是就拋異常,反射gg
    if((this.clazz.getModifiers() & 16384) != 0) {
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    } else {
        ConstructorAccessor var4 = this.constructorAccessor;
        if(var4 == null) {
            var4 = this.acquireConstructorAccessor();
        }

        Object var3 = var4.newInstance(var1);
        return var3;
    }
}

枚舉單例確實能簡單快捷地實現線程安全,延遲加載,序列化與反序列化、反射安全,但在java中,枚舉佔的內存通常是靜態變量的兩倍以上,所以通常是建議使用DCL或內部靜態類單例代替枚舉單例。

防克隆

單例類不應實現Cloneable接口,若非得繼承實現了Cloneable的父類,重寫吧:

//------------------------------
// 克隆處理
//------------------------------
@Override
protected Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}

防反序列化

若實現了Parcelable:

//------------------------------
// Parcelable反序列化處理
//------------------------------
private SingleTonJava(Parcel in) {
    data = in.readString();
}

public static final Creator<SingleTonJava> CREATOR = new Creator<SingleTonJava>() {
    @Override
    public SingleTonJava createFromParcel(Parcel in) {
        if (INSTANCE == null) {
            synchronized (SingleTonJava.class) {
                if (INSTANCE == null) {
                    INSTANCE = new SingleTonJava(in);
                }
            }
        }
        return INSTANCE;
    }

    @Override
    public SingleTonJava[] newArray(int size) {
        throw new Error("不能創建數組");
    }
};

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(data);
}

若實現了Serializable:

//------------------------------
// Serializable反序列化處理
//------------------------------
public Object readResolve() throws ObjectStreamException {
	if (INSTANCE == null) {
        synchronized (SingleTonJava.class) {
            if (INSTANCE == null) {
                INSTANCE = new SingleTonJava(in);
            }
        }
    }
    return INSTANCE;
}

防反射

反射無效不就行了:

//------------------------------
// 反射處理
//------------------------------
private static volatile boolean flag = true;
private SingleTonJava() {
    if (flag) {
        flag = false;
    } else {
        throw new RuntimeException("已存在單例");
    }
    // 其他初始化工作
    ...
}

分佈式/多類加載器

分佈式中的“單例“必須使用數據庫等方式來實現唯一性,而對於多class loader的情況,則必須手動設置單例類的“唯一指定”類加載器了。

單例 in Kotlin

單例保證

Kotlin保證單例的方式跟java基本一致:

/**
 * 單例模式
 */
open class Singleton : Serializable, Cloneable, Parcelable {
    var data: String? = null

    //--------------------------------
    // 防反射
    //--------------------------------
    @Throws(RuntimeException::class)
    private constructor () {
        if (flag) {
            flag = false
        } else {
            throw RuntimeException("已存在單例")
        }
        // 其他初始化工作
    }

    private constructor(parcel: Parcel) : this(parcel.readString())

    private constructor(data: String) : this() {
        this.data = data
    }

    //--------------------------------
    // 防反射
    //--------------------------------
    @Throws(CloneNotSupportedException::class)
    override fun clone(): Any {
        throw CloneNotSupportedException()
    }

    //--------------------------------
    // 防Serializable反序列化
    //--------------------------------
    @Throws(ObjectStreamException::class)
    fun readResolve(): Any {
        if (INSTANCE == null) {
            synchronized(SingleTonJava::class.java) {
                if (INSTANCE == null) {
                    INSTANCE = Singleton()
                }
            }
        }
        return INSTANCE
    }

    //--------------------------------
    // 防Parcelable反序列化
    //--------------------------------
    override fun writeToParcel(dest: Parcel?, flags: Int) {
        dest?.writeString(data)
     }

    override fun describeContents() = 0

    companion object {
        /**
         * 在JVM平臺,使用 @JvmStatic註解,可以將伴生對象的成員生成爲真正的靜態方法和字段
         */
        @JvmField
        val CREATOR: Parcelable.Creator<Singleton> = object : Parcelable.Creator<Singleton> {
            override fun createFromParcel(parcel: Parcel): Singleton {
                if (INSTANCE == null) {
                    synchronized(Singleton::class.java) {
                        if (INSTANCE == null) {
                            INSTANCE = Singleton(parcel)
                        }
                    }
                }
                return INSTANCE
            }

            override fun newArray(size: Int): Array<Singleton?> {
                throw Error("不能創建數組")
            }
        }

        @Volatile
        var flag = true

        //------------------------------
        // 餓漢
        //------------------------------
        private var INSTANCE = Singleton()
        fun getInstance() = INSTANCE
    }
}

// 餓漢的最簡形式,一行搞定
object Singleton

lazy&lateinit

Kotlin的延遲初始化主要依靠lazy或lateinit實現。
非抽象屬性在聲明時就必須先賦值,而lateinit可以使屬性在聲明後賦值:

 companion object {
    //------------------------------
    // 懶漢DCL
    //------------------------------
    // lateinit用於變量var,在使用前必須手動賦值,控制權交給了自己,比by lazy靈活,性能也好一點 
    private lateinit var INSTANCE:Singleton
    fun getInstance(data:String?): Singleton {
       if (INSTANCE == null) {
            synchronized(SingleTonJava::class.java) {
                if (INSTANCE == null) {
                    INSTANCE = Singleton()
                }
            }
        }
        return INSTANCE
    }
}

by lazy本質是一種屬性委託,通俗理解就是在val常量第一次被調用getter時,執行自定義的線程安全的唯一一次初始化,詳細分析可見玩轉 Kotlin 委託屬性:

companion object {
	val INSTANCE:Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
	    Singleton()
	}
}

kotlin單例實現

除去以上保證單例的代碼,其實現跟java基本一致

//------------------------------
// 餓漢最簡單方式
//------------------------------
object Singleton

//------------------------------
// 枚舉
//------------------------------
enum class Singleton{
     INSTANCE;
     fun fun0() = 1
}

class Singleton {
    var data: String? = null
    private constructor () {
    	...
    }
    private constructor(data: String) : this() {
        this.data = data
    }
   
    companion object {
        //------------------------------
        // 餓漢
        //------------------------------
        private val INSTANCE = Singleton()
        fun getInstance() = INSTANCE

        //------------------------------
        // 懶漢DCL
        //------------------------------
        private lateinit var INSTANCE:Singleton
        fun getInstance(data:String): Singleton {
            if (INSTANCE == null) {
                synchronized(SingleTonJava::class.java) {
                    if (INSTANCE == null) {
                        INSTANCE = Singleton(data)
                    }
                }
            }
            return INSTANCE
        }

        //------------------------------
        // 懶漢by lazy
        //------------------------------
        val INSTANCE:Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            Singleton()
        }

        //------------------------------
        // 內部靜態類
        //------------------------------
        fun getInstance() = Holder.INSTANCE
    }

    private object Holder{
        val INSTANCE = Singleton()
    }
}

單例 in Android Framework——LayoutInflater

Android開發中,經常用到Context的getSystemService方法,如ActivityManagerService、WindowManagerService等,而我們經常使用的LayoutInflater也是其中之一。系統服務需要供所有app使用,因此必定是單例,此處就以LayoutInflater爲例進行說明:

// activity中使用setContentView設置layout
setContentView(R.layout.activity_main)

// Activity.java
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

// Activity的getWindow其實是PhoneWindow
// PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
    	// 重點關注,使用了LayoutInflater類型的mLayoutInflater初始化mContentParent
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

// PhoneWindow在Activity初始化時,一併初始化了mLayoutInflater
public PhoneWindow(Context context) {
    super(context);
    mLayoutInflater = LayoutInflater.from(context);
}

此處的LayoutInflater.from在使用ListView、RecycleView的Adapter時,也經常用到,其最終調用的是context.getSystemService:

// LayoutInflater.java
 /**
 * Obtains the LayoutInflater from the given context.
 */
public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

Context具體的

// Activity.java
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
     ...
     return super.getSystemService(name);
 }
// Activity的父類ContextThemeWrapper.java
@Override
public Object getSystemService(String name) {
    if (LAYOUT_INFLATER_SERVICE.equals(name)) {
        if (mInflater == null) {
        	// 使用context包裝類ContextWrapper的getSystemService
            mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
        }
        return mInflater;
    }
    return getBaseContext().getSystemService(name);
}

// ContextWrapper.java
@Override
   public Object getSystemService(String name) {
       return mBase.getSystemService(name);
   }

首先我們需要先了解一下Android Context的構成,系統調用attachBaseContext時,會把ContextImpl對象作爲參數賦值給mBase對象,之後ContextWrapper中的所有方法,最終都是通過這種委託機制交由ContextImpl去實現的:

// ContextImpl.java
@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

SystemServiceRegistry是獲取系統服務的最終節點,它使用了容器單例的方式,提供了系統服務:

// SystemServiceRegistry.java(final類)
// 容器單例
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
         new HashMap<String, ServiceFetcher<?>>();
/**
 * Statically registers a system service with the context.
 * This method must be called during static initialization only.
 * 在靜態代碼塊中被調用,註冊服務
 */
private static <T> void registerService(String serviceName, Class<T> serviceClass,
        ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

// 靜態初始化註冊系統服務
static {
	...
	registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
            new CachedServiceFetcher<LayoutInflater>() {
	            @Override
	            public LayoutInflater createService(ContextImpl ctx) {
	                return new PhoneLayoutInflater(ctx.getOuterContext());
	            }});
}

/**
 * Gets a system service from a given context.
 * 獲取系統服務
 */
public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

單例 in Use

  1. 對於無需長期存在內存的類,如工具方法、常量類等,一般使用靜態類(kotlin中的object其實是餓漢單例,companion object纔是static)。
  2. 在使用單例時,需要特別注意儘量不要持有非Application的context,內存泄漏很多時候都是這樣發生的。
  3. 單例儘量懶加載,需要注意及時釋放不需要的資源,畢竟一直會在內存。
  4. 不能使用Application的單例來存放、傳遞組件數據,因爲進程被殺死時,Application會重建。
  5. 單例是測試不友好的,儘量不要過多修改。

參考資料

這篇比較深入哦,強烈建議閱讀
https://blog.csdn.net/happy_horse/article/details/50908439
https://blog.csdn.net/happy_horse/article/details/51164262
https://www.jianshu.com/p/91397bebe2c1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章