Android設計模式之單例模式在項目中的運用

前言

單例模式(Singleton Pattern)一般被認爲是最簡單、最易理解的設計模式,也因爲它的簡潔易懂,是項目中最常用、最易被識別出來的模式。

本文會重點總結一下Android開發中常用的單例模式場景,理論與實踐結合,深入學習設計模式,從而提高大家的開發水平,完美解決開發中遇到的類似問題。

單例模式介紹

單例模式的定義

確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。

單例模式的使用場景

確保某個類有且只有一個對象的場景。避免產生多個對象消耗過多的資源,或者某種類型的對象應該有且只有一個。如在一個應用中,應用只有一個ImageLoader 實例,這個 ImageLoader 中又含有線程池、緩存系統、網絡請求等,很消耗資源,因此,沒有理由讓它構造多個實例。

優點

  • 由於單例模式在內存中只有一個實例,減少了內存開支,特別是一個對象需要頻繁地創建、銷燬時,而且創建或銷燬時性能又無法優化,單例模式的優勢就非常明顯。

  • 由於單例模式只生成一個實例,所以減少了系統的性能開銷,當一個對象的產生需要比較多的資源時,如讀取配置、產生其他依賴對象時,則可以通過在應用啓動時直接產生一個單例對象,然後用永久駐留內存的方式來解決。

  • 單例模式可以避免對資源的多重佔用,例如一個寫文件動作,由於只有一個實例存在內存中,避免對同一個資源文件的同時寫操作。

  • 單例模式可以在系統設置全局的訪問點,優化和共享資源訪問,例如可以設計一個單例類,負責所有數據表的映射處理。

缺點

擴展很困難,容易引發內存泄露,測試困難,一定程度上違背了單一職責原則,進程被殺時可能有狀態不一致問題。

實現方式

單例模式的實現方式 有很多種,先來一個使用最爲常見的實現方式。

1.雙重檢查鎖定(Double CheckLock)( DCL)


public class Singleton {
    //JDK>=1.5 增加了volatile關鍵字,定義時加上它即可保證執行順序(雖然會影響性能),從而單例生效。
    private static volatile Singleton instance = null;

    private Singleton(){}
 
    public static Singleton getInstance() {
        if (instance == null) { //第一個if
            synchronized (Singleton.class) {//同步判斷
                if (instance == null) {//第二個if
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

DCL 方式實現單例的優點是既能在需要時才初始化單例,又能夠保證線程安全,且單例對象初始後調用getInstance不進行同步鎖。

上面版本是java語言實現的,用kotlin實現代碼如下,同時用到了Kotlin的延遲屬性 Lazy

class Singleton private constructor() {
    companion object {
        val instance: Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
        Singleton() }
    }
}

2.靜態內部類單例模式

public class SingleTon {
    private SingleTon(){}
    public static SingleTon getInstance(){
        return SingletonHolder.sInstance
    }
    private static class SingletonHolder{
        private static final SingleTon sInstance = new SingleTon();
    }
}

DCL 雖然在一定程序上解決了資源消耗、多餘的同步、線程安全等問題,但是,它還是在某些情況下出現失效的問題。這《Java併發編程實踐》一書中最後談到了這個總是,並指出這種“優化”是醜陋的,不贊成使用,而建議用上述代碼替代。

當第一次加載Singleton類時不會初始化sInstance,只有在第一次調用SingleTon的getInstance方法時纔會去初始化sInstance。因此,第一次調用getInstance方法會導致虛擬機加載SingletonHolder,這種方式不僅能夠保證線程安全,也能夠保證單例對象的唯一性,同時也延遲了單例的實例化,所以這是推薦的單例模式實現方式。

3.其他實現方式彙總

public class Singleton {
	private static Singleton mInstance = null;

	private Singleton() {

	}

	public void doSomething() {
		System.out.println("do sth.");
	}
    
    

	/**
	 * 方式二、double-check, 避免併發時創建了多個實例, 該方式不能完全避免併發帶來的破壞.
	 * 
	 * @return
	 */
	public static Singleton getInstance() {
		if (mInstance == null) {
			synchronized (Singleton.class) {
				if (mInstance == null) {
					mInstance = new Singleton();
				}
			}
		}
		return mInstance;
	}

	/**
	 * 方式三 : 在第一次加載SingletonHolder時初始化一次mOnlyInstance對象, 保證唯一性, 也延遲了單例的實例化,
	 * 如果該單例比較耗資源可以使用這種模式.
	 * 
	 * @return
	 */
	public static Singleton getInstanceFromHolder() {
		return SingletonHolder.mOnlyInstance;
	}

	/**
	 * 靜態內部類
	 * 
	 *
	 */
	private static class SingletonHolder {
		private static final Singleton mOnlyInstance = new Singleton();
	}

	/**
	 *  方式四 : 枚舉單例, 線程安全
	 *
	 */
	enum SingletonEnum {
		INSTANCE;
		public void doSomething() {
			System.out.println("do sth.");
		}
	}

	/**
	 * 方式五 : 註冊到容器, 根據key獲取對象.一般都會有多種相同屬性類型的對象會註冊到一個map中
	 * instance容器
	 */
	private static Map<string singleton=""> objMap = new HashMap<string singleton="">();
	/**
	 * 註冊對象到map中
	 * @param key
	 * @param instance
	 */
	public static void registerService(String key, Singleton instance) {
		if (!objMap.containsKey(key) ) {
			objMap.put(key, instance) ;
		}
	}
	
	/**
	 * 根據key獲取對象
	 * @param key
	 * @return
	 */
	public static Singleton getService(String key) {
		return objMap.get(key) ;
	}

}

不管以哪種形式實現單例模式,它們的核心原理都是將構造函數私有化,並且通過靜態方法獲取一個唯一的實例,在這個獲取的過程中你必須保證線程安全、反序列化導致重新生成實例對象等問題,該模式簡單,但使用率較高。

Android源碼中的模式實現

在Android系統中,我們經常會通過Context獲取系統級別的服務,比如WindowsManagerService, ActivityManagerService等,更常用的是一個叫LayoutInflater的類。這些服務會在合適的時候以單例的形式註冊在系統中,在我們需要的時候就通過Context的getSystemService(String name)獲取。我們以LayoutInflater爲例來說明, 平時我們使用LayoutInflater較爲常見的地方是在ListView的getView方法中。

@Override
public View getView(int position, View convertView, ViewGroup parent)	
	View itemView = null;
	if (convertView == null) {
		itemView = LayoutInflater.from(mContext).inflate(mLayoutId, null);
		// 其他代碼
	} else {
		itemView = convertView;
	}
	// 獲取Holder
	// 初始化每項的數據
	return itemView;
}

通常我們使用LayoutInflater.from(Context)來獲取LayoutInflater服務, 下面我們看看LayoutInflater.from(Context)的實現。

    /**
     * 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;
    }

可以看到from(Context)函數內部調用的是Context類的getSystemService(String key)方法,我們跟蹤到Context類看到, 該類是抽象類。

public abstract class Context {
    // 省略
}

使用的getView中使用的Context對象的具體實現類是什麼呢 ?其實在Application,Activity, Service,中都會存在一個Context對象,即Context的總個數爲Activity個數 + Service個數 + 1。而ListView通常都是顯示在Activity中,那麼我們就以Activity中的Context來分析。

我們知道,一個Activity的入口是ActivityThread的main函數。在該main函數中創建一個新的ActivityThread對象,並且啓動消息循環(UI線程),創建新的Activity、新的Context對象,然後將該Context對象傳遞給Activity。下面我們看看ActivityThread源碼。

    public static void main(String[] args) {
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());
        Process.setArgV0("<pre-initialized>");
        // 主線程消息循環
        Looper.prepareMainLooper();
        // 創建ActivityThread對象
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

    private void attach(boolean system) {
        sThreadLocal.set(this);
        mSystemThread = system;
        if (!system) {
            ViewRootImpl.addFirstDrawHandler(new Runnable() {
                public void run() {
                    ensureJitEnabled();
                }
            });
            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                    UserHandle.myUserId());
            RuntimeInit.setApplicationObject(mAppThread.asBinder());
            IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }
        } else {
               // 省略
        }
}

在main方法中,我們創建一個ActivityThread對象後,調用了其attach函數,並且參數爲false. 在attach函數中, 參數爲false的情況下, 會通過Binder機制與ActivityManagerService通信,並且最終調用handleLaunchActivity函數 ( 具體分析請參考老羅的博客 : Activity的啓動流程),我們看看該函數的實現 。

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // 代碼省略
        Activity a = performLaunchActivity(r, customIntent);
        // 代碼省略
    }
    
     private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
        // 代碼省略
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(         // 1 : 創建Activity
                    cl, component.getClassName(), r.intent);
         // 代碼省略
        } catch (Exception e) {
         // 省略
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity); // 2 : 獲取Context對象
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                // 3: 將appContext等對象attach到activity中
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config);

                // 代碼省略
                // 4 : 調用Activity的onCreate方法
                mInstrumentation.callActivityOnCreate(activity, r.state);
                // 代碼省略
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            // 代碼省略
        }

        return activity;
    }


    private Context createBaseContextForActivity(ActivityClientRecord r,
            final Activity activity) {
        // 5 : 創建Context對象, 可以看到實現類是ContextImpl
        ContextImpl appContext = new ContextImpl();           appContext.init(r.packageInfo, r.token, this);
        appContext.setOuterContext(activity);

        // 代碼省略
        return baseContext;
    }
    

通過上面1~5的代碼分析可以知道, Context的實現類爲ComtextImpl類。我們繼續跟蹤到ContextImpl類。

class ContextImpl extends Context {
  
    // 代碼省略
    /**
     * Override this class when the system service constructor needs a
     * ContextImpl.  Else, use StaticServiceFetcher below.
     */
     static class ServiceFetcher {
        int mContextCacheIndex = -1;

        /**
         * Main entrypoint; only override if you don't need caching.
         */
        public Object getService(ContextImpl ctx) {
            ArrayList<Object> cache = ctx.mServiceCache;
            Object service;
            synchronized (cache) {
                if (cache.size() == 0) {
                    for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
                        cache.add(null);
                    }
                } else {
                    service = cache.get(mContextCacheIndex);
                    if (service != null) {
                        return service;
                    }
                }
                service = createService(ctx);
                cache.set(mContextCacheIndex, service);
                return service;
            }
        }

        /**
         * Override this to create a new per-Context instance of the
         * service.  getService() will handle locking and caching.
         */
        public Object createService(ContextImpl ctx) {
            throw new RuntimeException("Not implemented");
        }
    }

    // 1 : service容器
    private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
            new HashMap<String, ServiceFetcher>();

    private static int sNextPerContextServiceCacheIndex = 0;
    // 2: 註冊服務器
    private static void registerService(String serviceName, ServiceFetcher fetcher) {
        if (!(fetcher instanceof StaticServiceFetcher)) {
            fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
        }
        SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
    }


    // 3: 靜態語句塊, 第一次加載該類時執行 ( 只執行一次, 保證實例的唯一性. )
    static {
        //  代碼省略
        // 註冊Activity Servicer
        registerService(ACTIVITY_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
                }});

        // 註冊LayoutInflater service
        registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
                }});
        // 代碼省略
    }

    // 4: 根據key獲取對應的服務, 
    @Override
    public Object getSystemService(String name) {
        // 根據name來獲取服務
        ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
        return fetcher == null ? null : fetcher.getService(this);
    }

    // 代碼省略
}

從ContextImpl類的部分代碼中可以看到,在虛擬機第一次加載該類時會註冊各種服務,其中就包含了LayoutInflater Service, 將這些服務以鍵值對的形式存儲在一個HashMap中,用戶使用時只需要根據key來獲取到對應的服務,從而達到單例的效果。這種模式就是上文中提到的“單例模式的實現方式5”。系統核心服務以單例形式存在,減少了資源消耗。

在Android中的其他運用場景

1.開源控件圖片加載庫Android-Universal-Image-Loader

private volatile static ImageLoader instance;

public static ImageLoader getInstance() {
        if (instance == null) {
            synchronized (ImageLoader.class) {
                if (instance == null) {
                    instance = new ImageLoader();
                }
            }
        }
        return instance;
    }

2.Android的事件發佈-訂閱總線 EventBus

private static volatile EventBus defaultInstance;

public static EventBus getDefault() {
	if (defaultInstance == null) {
		synchronized (EventBus.class) {
			if (defaultInstance == null) {
				defaultInstance = new EventBus();
			}
		}
	}
	return defaultInstance;
}

3.ActivityManager中管理Activity堆棧

public class ActivityManager {
    
    private static ActivityManager instance;
    private Stack<Activity> activityStack;// activity棧

    // 單例模式
    public static ActivityManager getInstance() {
        if (instance == null) {
            instance = new ActivityManager();
        }
        return instance;
    }

    // 把一個activity壓入棧中
    public void pushOneActivity(Activity actvity) {
        if (activityStack == null) {
            activityStack = new Stack<>();
        }
        activityStack.add(actvity);
    }

    // 獲取棧頂的activity,先進後出原則
    public Activity getLastActivity() {
        return activityStack.lastElement();
    }

    // 移除一個activity
    public void popOneActivity(Activity activity) {
        if (activityStack != null && activityStack.size() > 0) {
            if (activity != null) {
                activity.finish();
                activityStack.remove(activity);
                activity = null;
            }

        }
    }

    // 退出所有activity
    public void finishAllActivity() {
        if (activityStack != null) {
            while (activityStack.size() > 0) {
                Activity activity = getLastActivity();
                if (activity == null)
                    break;
                popOneActivity(activity);
            }
        }
    }
}

在 Android 中使用單例還有哪些需要注意的地方

單例在 Android 中的生命週期等於應用的生命週期,所以要特別小心它持有的對象是否會造成內存泄露。如果將 Activity 等 Context 傳遞給單例又沒有釋放,就會發生內存泄露,所以最好僅傳遞給單例 Application Context。

總結

單例模式作爲最容易理解的一種設計模式,同時也是運用頻率很高的模式,由於客戶端通常沒有高併發的情況,因此,選擇哪種實現方式不會有太大的影響。但是出於效率考慮,推薦使用使用DCL靜態內部類實現方式。

每個模式都有它的優缺點和適用範圍,相信大家看過的每一本介紹模式的書籍,都會詳細寫明某個模式適用於哪些場景。我的觀點是,我們要做的是更清楚地瞭解每一個模式,從而決定在當前的應用場景是否需要使用,以及如何更好地使用這個模式。就像《深入淺出設計模式》裏說的:

使用模式最好的方式是:“把模式裝進腦子裏,然後在你的設計和已有的應用中,尋找何處可以使用它們。”

單例模式是經得起時間考驗的模式,只是在錯誤使用的情況下可能爲項目帶來額外的風險,因此在使用單例模式之前,我們一定要明確知道自己在做什麼,也必須搞清楚爲什麼要這麼做。

參考資料:

1.何紅輝,關愛民. Android 源碼設計模式解析與實戰[M]. 北京:人民郵電出版社

2.單例模式都用在什麼地方?能舉幾個例子嗎

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