Singleton 單例模式、設計 | 解說實現 | Android Framework Context Service

Singleton 單例模式、設計 | 解說實現 | Android Framework Context Service

Overview of Content

這篇文章將介紹單例模式在物件導向設計中的重要性以及多種實現方法。

首先,我們將探討單例模式的UML結構,並詳細討論單例模式的特性和實現方法的優缺點。在實現方法部分,我們將討論幾種常見的單例實現方法,包括 Eager Initialization (餓漢模式)、Lazy Initialization (懶漢模式)、雙鎖機制(Double Check Lock)、靜態內部類和列舉單例。

此外,我們還將探討單例模式在Android源碼中的應用,包括如何取得LayoutInflater、如何包裝Activity的Context,以及分析ContextImpl與SystemServiceRegistry之間的關係。通过本文,读者将深入了解單例模式的各種實現方式以及在Android開發中的應用


Singleton UML

Singleton 實做特性

A. construct 必須設定為 private,不讓使用者隨意創建目標物件

B. 通過靜態方法、成員、列舉返回單例物件

C. 注意使用場景,別注意 多執行序的安全性 問題

D. 確保單例物件在 反序列化 時,不會重新建構物件

單例設計:優缺點

● 單例設計優點

● 最大的特點就是 減少對內存的使用,避免重複創建

● 可以設定該 APP 進程的全局訪問點

● 單例設計缺點

不容易符合開閉原則:單例要拓展通常需要手動修改,一般來講也不會有 interface

● 建議不要對內部傳入 Context,否則會造成內存無法回收的問題(由於是單例,可達性分析總會分析到 static 仍在使用,導致 GC 無法回收沒有要用的 Activity)

● 建議傳入 Application#context 來使用,因為 Application#context 的生命週期同 App 進程,所以不被回收也沒關係


Singleton 實現方法

其特色都有相似的點,目的是為了不要有多個物件,要使用該物件都必須使用同一個物件,在這個條件下在考慮其性能,而每種實現方式都有不同特色

Eager Initialization

私有化建構函數,創立私有靜態變數,取得實例的 Func 靜態化,沒有多執行序問題

優點缺點
沒有加鎖,效率較高類加載時初始化,稍微浪

class Hungry {
    private static Hungry h = new Hungry();

    private Hungry() {
    }

    public static Hungry getInstance() {
        return h;
    }
    
    
    public static void say() {
        System.out.println("Hello");
    }
}

這種單例方式比較常用,類加載時就初始化,它是 基於 ClassLoader 機制 避免了多執行序同步問題(類加載時 static 就會被實例化)

● 但是初次呼叫時不一定是呼叫 getInstance 方法,由此可知 可能會造成在不必要創建時實例時被創建,可能會浪費資源


public class Test {
    public static void main(String[] args) {
        // 呼叫一個靜態方法,仍會創建 Hungry Field
        Hungry.say();
    }
}

--實作--

創建 20 個任務,確認沒有多執行序問題

Lazy Initialization

Lazy Initialization 不初始化靜態變量,避免了不必要的資源浪費

這樣會有兩種狀況,執行序安全,執行序不安全

A. 執行序不安全


class LazyNoSyn {
    private static LazyNoSyn l;

    private LazyNoSyn() {
    }

    public static LazyNoSyn getInstance() {
        if(l == null) {
            l = new LazyNoSyn();
        }
        return l;
    }
}

--實作--

創建 20 個任務,執行序不安全 問題(看下圖左邊的藍標,明明是單例模式,但是它卻創建了多個物件);

這個範例並非每次執行都會產生執行序多次創建物件的狀況(多執行序預設是沒有鎖的不安全操作,但是被非每次都會發生),你可以多按執行幾次嘗試看看

B. 執行序安全

重點多了 synchronized 關鍵字來保證多執行序訪問同段代碼的「同步」行為


class LazySyn {
    private static LazySyn l;

    private LazySyn() {
    }

    public static synchronized LazySyn getInstance() {
        if(l == null) {
            l = new LazySyn();
        }
        return l;
    }
}

--實作--

同樣是創建 20 個任務但可以看到這次就是 執行序安全(你可以多執行幾次)

優點缺點
延遲加載,在需要時才載入類使用同步加鎖機制又會增加負載,每次呼叫 getInstance 都會進行一次同步動作,會造成不必要的開銷

雙鎖 DCL (Double Check Lock)

● 雙鎖機制是優化 Lazy Initialization ,保證執行序安全


class DCL {
    private volatile static DCL d;    // 4

    private DCL() { }

    public static DCL getInstance() {
        if(d == null) {		// 1
            synchronized(DCL.class) {	// 3
                if(d == null) {		// 2
                    d = new DCL();
                }
            }
        }
        return d;
    }
}

A. 第一個 Check:避免不必要的 synchronized 同步,優化開銷

B. 第二次 Check:第一次加載才創建,第二次判斷 null 是為了避免並發問題 (可能有多個 thread 進入上一個判斷)

C. 區塊同步:使用 class 類同步 (Java 加載時 class 時會在 class 後創建一個不變的模板),或是使用一個靜態物件同步也可以

D. volatile 關鍵字:我們來看看 JVM 是如何創建一個物件實例的(假設類已經被 JVM 加載完畢)

new DCL() 看起來是一句程式,但實際上它並不是一個原子操作,它做了以下三件事

  1. 初始化記憶體:給 DCL 的實例分配記憶體
  2. 初始化物件:呼叫 DCL 的建構函數,初始化成員元素 (Field, Static Field)
  3. 賦予 Field:將 DCL 物件指向分配的記憶體空間 (給予它內存 Addr 此時 DCL's d Field 不再是 null)

Java 編譯器允許 CPU 失序執行,也就是上面執行的順序可能是 1-2-3 or 1-3-2,如果是 1-3-2 這種狀況很難追蹤,已經分配記憶體空見卻未創建 !

這時可以使用 volatile 關鍵字

private volatile static DCL d;
volatile 也加減會影響效能(volatile 修飾語每次更改時都會即時存入記憶體,在 Android 執行序會詳說)

--實作--

創建 20 個任務,執行序安全

優點缺點
確保多執行序的單例化第一次加載較慢,在高併發的環境下可能失敗(由於 JMM 模型)

● JDK 1.5 以後才具體實現 volatile 關鍵

靜態內部類

● 在某些情況下 DCL 會失效(高並發時),Java 並發編程實踐 也有提及這種 DCL 最佳化是醜陋的,建議使用靜態內部類


class StaticInnerClass {
    private StaticInnerClass() {
    }

    public static StaticInnerClass getInstance() {
        return innerClass.sic;
    }

    private static class innerClass {
        private static final StaticInnerClass sic = new StaticInnerClass();
    }
}

這同餓漢式是使用 classLoader 加載機制,如果未使用內部類,那該 class 就不會被加載,它的 Field 也不會被創建,可以達到 延遲加載機制

--實作--

創建 20 個任務,執行序安全

enum 列舉單例

● enum 類舉在 Java 中與普通類別是一樣的,可以有自己的方法,並在任何情況下都是單例 (透過反編譯 Class 可以看出 enum 成員各個都是一個列別)


public enum SingletonEnum {
    INSTANCE;

    public void toDo() {
        System.out.println("to Do Something");
    }
}

--實作--

● 可以透過 javac 自行編譯,在透過 javap 查看 enum 成員的實現方式,可以發現每個 Enum 成員都是 SingletonEnum 的實現物件 (static final)!


public enum SingletonEnum {
    INSTANCE,
    HELLO,
    WORLD;

    public void toDo() {
        System.out.println("to Do Something");
    }
}

單例:反序列化

● 注意,在一種情況下會出現重新建構物件的情況,就是反序列化,每次反序列化都會導致物件重新被創建

● 反序列化有一個特別的鉤子函數 readResolve ,只要寫這個方法並返回被實例化的物件就不會創建出新物件


private Object readResolve() throws ObjectStreamException {
turn ...;

● 以下為序列化 TestClass 與反序列化 TestClass


public class Main {
    public static void main(String[] args) {
        makeFile();

        readFile();
    }

    private static void readFile() {
        File in = new File("/home/alienpan/IdeaProjects/algorithm_ds/src/main/java/oop/singleton/serializable/SerialFile");

        try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(in))) {

            System.out.println("readFile: " + objectInputStream.readObject().hashCode());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void makeFile() {
        File out = new File("/home/alienpan/IdeaProjects/algorithm_ds/src/main/java/oop/singleton/serializable/SerialFile");

        if(out.exists() && out.delete()) {
            System.out.println("Delete file");
        }

        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(out))) {

            TestClass test = TestClass.getInstance();

            objectOutputStream.writeObject(test);
            objectOutputStream.flush();

            System.out.println("makeFile: " + test.hashCode());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

A. 首先測試尚未使用 readResolve 方法


public class TestClass implements Serializable {
    private static final TestClass instance = new TestClass();

    private TestClass() {
    }

    public static TestClass getInstance() {
        return instance;
    }

}

透過 hashCode 可以看到序列化、反序列化時兩者是 不同物件

目前使用靜態創建加載(Lazy Initialize),相同的你使用在 DCL 也一樣,序列化、反序列化時兩者是 不同物件

B. 使用 readResolve 方法,並返回相同物件


public class TestClass implements Serializable {
    private static final TestClass instance = new TestClass();

    private TestClass() {
    }

    public static TestClass getInstance() {
        return instance;
    }

    @Serial
    private Object readResolve() throws ObjectStreamException {
        return instance;
    }

}

透過 hashCode 可以看到序列化、反序列化時兩者是 相同物件

● enum 宣告則很特別,就算不用添加 readResolve 方法一樣是可以達到 序列化、反序列化時兩者是 同物件


public enum TestClass {
    INSTANCE;
    public static TestClass getInstance() {
        return INSTANCE;
    }
}

Android Source Content Service

以下是一段很常見到的 ListView 加載方式,並使用了簡單的 set/getTag 去緩存資源來達到快速加載,這個優化並不是當前文章的主要內容,所以不去細說

取得 LayoutInflater

● 最常使用在 ListView 的 BaseAdapter 做出 Layout 界面加載,這次主要關注在加載圖片時的 LayoutInflater.from(Context) 方法


public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ListView listView = findViewById(R.id.listView);

        MyBaseAdapter adapter = new MyBaseAdapter(new String[] {"Apple", "Box", "Car", "Drop"}, this);
        listView.setAdapter(adapter);

    }
}

class MyBaseAdapter extends BaseAdapter {

    private String[] strings;
    private Context context;

    MyBaseAdapter(String[] strings, Context context) {
        this.strings = strings;
        this.context = context;
    }

    @Override
    public int getCount() {
        return strings.length;
    }

    @Override
    public Object getItem(int position) {
        return strings[position];
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if(convertView == null) {
            // @ 分析 LayoutInflater.from 方法
            
            convertView = LayoutInflater.from(context).inflate(R.layout.my_item, null);
            holder = new ViewHolder();
            holder.textView = convertView.findViewById(R.id.MyTitle);
            holder.imageView = convertView.findViewById(R.id.MyImage);

            // 設定緩存儲存,設定一個 Tag 方便下次取出 ViewHolder
            convertView.setTag(holder);
        } else {
            // 使用之前設定的 Tag 取出已有的 ViewHolder
            holder = (ViewHolder) convertView.getTag();
        }

        holder.textView.setText(strings[position]);

        return convertView;
    }

    private static class ViewHolder {
        ImageView imageView;
        TextView textView;
    }
}

● LayoutInflater 的靜態 from 方法:它內部使用 context 抽象方法 getSystemService (Context 本身也是抽象類)


// LayoutInflater.java

    public static LayoutInflater from(Context context) {
        
        // 內部透過 Context#getSystemService 方法取得 LayoutInflater
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

// --------------------------------------------------------------------
// Context.java
    public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);

Context 包裝 Activity

● 這裡要從 App 程式的 main 入口開始分析,分析的過程可以得到 Context 是如何去註冊服務的

關注功能
ActivityThread.java內部有一個 main 方法,是 Java 程式的起點方法
ApplicationThread.java屬於 ActivityThread 的內部類,是該進程與外部進程通訊的橋樑

// ActivityThread.class

public class ActivityThread {

    final ApplicationThread mAppThread = new ApplicationThread();

    ...

    // 目前 system 是 false
    private void attach(boolean system, long startSeq) {
        // sCurrentActivityThread 使用 volatile 修飾
        sCurrentActivityThread = this;
        mSystemThread = system;

        if (!system) {

            // 取得與 Binder 通訊的代理
            // 這裡由 AIDL 實現
            final IActivityManager mgr = ActivityManager.getService();

            try {
                // @ 追蹤 attachApplication
                mgr.attachApplication(mAppThread, startSeq);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }

            ...
        } else {
            ... SystemSerivce 才會走到這
        }

        ...
    }


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

        // "1. "
        // 準備主執行序的 Looper (建構)
        Looper.prepareMainLooper();

        // 創建 ActivityThread 物件
        ActivityThread thread = new ActivityThread();
        // 非系統載入
        thread.attach(false);

        if (sMainThreadHandler == null) {
            // 取得該 class 的 Handler
            sMainThreadHandler = thread.getHandler();
        }

        ...

        Looper.loop();    // 啟動 Looper

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

}

A. 這段程式可以看出 Android 的消息驅動是透過 Looper 推動 (要先了解 Handler 機制)

B. IActivityManager#attachApplication 會觸及 Binder 機制,這裡我們當作一個黑盒子 (略過與 AMS 進程通訊),而 attachApplication 函數最終會回到該進程中的 ActivityThread#handleLaunchActivity 方法


// ActivityThread.class

    @Override
    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {

        ... 省略部份

        // @ 追蹤 performLaunchActivity 
        final Activity a = performLaunchActivity(r, customIntent);

        ...
    }

● ActivityThreadperformLaunchActivity:準備啟動 Activity

A. 反射創建 Activity、對應的 Context 類 (Context 實做類是 ContextImpl 物件)


// ActivityThread.class

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;

        ... 省略部份

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            
            // 透過 Instrumentation 創建 Actiivty
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ...
        } /* 省略 catch */

        ...
    }

    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        ...
        
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
        
        ... 省略部份
        
        return appContext;
    }


// ----------------------------------------------------------------
// ContextImpl.java

    static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
            Configuration overrideConfiguration) {

        ...

        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, ContextParams.EMPTY,
                attributionTag, null, activityInfo.splitName, activityToken, null, 0, classLoader,
                null);

        ...

        return context;
    }

B. 創建 Application 類,並讓 Context 將 Activity 包裝起來,之後在呼叫 Activity#attach 方法


// ActivityThread.class

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;

        ... 省略部份

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            
            if (activity != null) {
                
                ...
            
                // Context 內包裝 Activity
                appContext.setOuterContext(activity);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken, r.shareableActivityToken);
                
                ...

                
                // 透過 Instrumentation 呼叫 onCreate 方法
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
            } 
            
        } /* 省略 catch */

        ...
    }

● 從這裡我們可以知道

A. Context 的實做類是 ContextImpl (接下來我們會繼續分析)

B. Activity 的生命週期大部分可以透過 Instrumentation Hook 改寫,因為 Activity 的控制大分份也都通過 Instrumentation

ContextImpl 分析:SystemServiceRegistry 單例創建 PhoneLayoutInflater

● 我們了解到 Activity 是由 Context 來包裝,而 Context 的實做類是 ContextImpl 類,現在我們來看看 ContextImpl 類

A. 複習回憶:現在的目的是 了解 LayoutInflater 這個服務 註冊時機


// LayoutInflater.java

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

// --------------------------------------------------------------------
// ContextImpl.java

    @Override
    public Object getSystemService(String name) {
        // @ 分析 SystemServiceRegistry 物件
        return SystemServiceRegistry.getSystemService(this, name);
    }

B. 分析 SystemServiceRegistry:SystemServiceRegistry 類是一個 單例物件(符合當前主題)

● 在 SystemServiceRegistry 類加載時會先加載 static Field,包含 static 區塊

● 每個 registerService 實做 ServiceFetcher#createService 方法,也就是說 LayoutInflater 的實現物件是 PhoneLayoutInflater 類


// SystemServiceRegistry.java

// Activity 可以透過 Context#getSystemService 獲取服務
final class SystemServiceRegistry {

    private SystemServiceRegistry() { } // 私有化 - 建構函數

    static {
        ... 省略其他服務

        // @ 追蹤 registerService
        registerService(
            // 服務名
            Context.LAYOUT_INFLATER_SERVICE, 
            // Class 類
            LayoutInflater.class,
            // @ 追蹤 CachedServiceFetcher
            new CachedServiceFetcher<LayoutInflater>() {
                // 實做 createService 方法
                @Override
                public LayoutInflater createService(ContextImpl ctx) {
                    // 服務真正實現類
                    return new PhoneLayoutInflater(ctx.getOuterContext());
                }}
        );
    }

// 儲存在兩個 Map 中,這個函數只能使用在初始化
    private static <T> void registerService(String serviceName, Class<T> serviceClass,
                ServiceFetcher<T> serviceFetcher) {

        // 各種 Map 緩存

        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        // 服務 Map
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);

        SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
    }

    // ContextImpl 呼叫
    // 透過緩存 SYSTEM_SERVICE_FETCHERS 找到對應的服務
    public static Object getSystemService(ContextImpl ctx, String name) {
        if (name == null) {
            return null;
        }
        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);

        if (fetcher == null) {
            ... 無法取得對應緩存

            return null;
        }

        // 呼叫實做 ServiceFetcher 的類 getService 方法
        final Object ret = fetcher.getService(ctx);

        ...

        return ret;
    }

}

C. CachedServiceFetcher:功能有兩個

● 懶加載 Service 物件,在需要時才會呼叫 createService 方法創建

● 多 Thread 要求 Service 時會讓第一個 thread 創建,其他 thread 休眠(wait)


// SystemServiceRegistry.java

// Activity 可以透過 Context#getSystemService 獲取服務
final class SystemServiceRegistry {

    private static int sServiceCacheSize;

// 匿名靜態內部類
    static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {

        // 緩存的 index
        private final int mCacheIndex;

        public CachedServiceFetcher() {
            // 屬性 sServiceCacheSize 是靜態變數
            mCacheIndex = sServiceCacheSize++;
        }

        @Override
        @SuppressWarnings("unchecked")
        public final T getService(ContextImpl ctx) {

            final Object[] cache = ctx.mServiceCache;
            final int[] gates = ctx.mServiceInitializationStateArray;
            boolean interrupted = false;

            T ret = null;

            for (;;) {
                boolean doInitialize = false;

                // 同步鎖
                synchronized (cache) {
                    // ContextImpl 內有緩存就直接返回
                    T service = (T) cache[mCacheIndex];
                    if (service != null) {
                        ret = service;
                        break; // exit the for (;;)
                    }

                    // 如果是 `STATE_READY` or `STATE_NOT_FOUND`
                    // 則定義 ContextImpl 中 Service 尚未初始化
                    if (gates[mCacheIndex] == ContextImpl.STATE_READY
                            || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
                        gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED;
                    }

                    // 如果有多的 thread 到這一步,必須確保第一個 Thread 呼叫 createService()
                    // 所以必須定義 STATE_UNINITIALIZED 為 STATE_INITIALIZING 狀態
                    if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) {
                        doInitialize = true;
                        gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING;
                    }
                }

                // 只有第一個 thread 才會執行以下 if 條件
                if (doInitialize) {

                    T service = null;

                    @ServiceInitializationState 
                    int newState = ContextImpl.STATE_NOT_FOUND;

                    try {
                        // 呼叫 createService 方法(每個 Service 實做類)
                        service = createService(ctx);
                        newState = ContextImpl.STATE_READY;

                    } catch (ServiceNotFoundException e) {
                        onServiceNotFound(e);

                    } finally {
                        // 最終會呼叫所有等待的 thread 醒來
                        synchronized (cache) {
                            cache[mCacheIndex] = service;
                            gates[mCacheIndex] = newState;
                            cache.notifyAll();
                        }
                    }
                    ret = service;
                    break; // exit the for (;;)
                }

                synchronized (cache) {
                    // 所有不是第一個 thread 都會進入這
                    while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
                        try {
                            // Clear the interrupt state.
                            interrupted |= Thread.interrupted();
                            cache.wait();
                        } catch (InterruptedException e) {
                            // Thread 被中斷
                            interrupted = true;
                        }
                    }
                }
            }
            if (interrupted) {
                Thread.currentThread().interrupt();
            }
            return ret;
        }

        public abstract T createService(ContextImpl ctx);
    }

}

● 在這其中也可以發現 依賴倒置的關係,高層模(SystemServiceRegistry)組依賴抽象(ServiceFetcher),而不是細節(CachedServiceFetcher、StaticServiceFetcher)

ContextImpl 與 SystemServiceRegistry 關係

● ContextImpl 本身並不是單例(可以有多個),但是服務就是單例,呼應上面所 LayoutInflater.from() 所呼叫的函數,Context.getSystemService 其 context 透過上面的追蹤可以知道實做類就是 ContextImpl 類

● 從這邊我們可以看到 SystemSerivceRegistry 的單例是使用 static 靜態區塊加載 (Eager Initialization, 也稱為餓漢式),代表所有的服務在 SystemSerivceRegistry 這個類加載的時候就已經註冊完畢

雖說是靜態加載,但是也只加載了服務的數量,並未真正創建,所以應該是說 懶加載 + 延遲加載

靜態加載 -> 定義出總數量,以及其 index 位置

延遲加載 -> 真正的服務物件

● 特別說說 ContextImpl 中的 getSystemService 函數,它把 this 作為參數傳入,並且可以看到

A. 一個 Activity 有一個 ContextImpl 物件

B. 在 SystemServiceRegistry.getSystemService 中透過傳入的 Context 物件取得服務的物件,若沒有該物件則使用當前 context 創建物件

結論: 可以透過當前的 Context 創建服務,若尚未使用到該服務則不加載

● 這裡的服務並非進程間的服務(並非 AMS、WMS、PKMS... 等等)

更多的物件導向設計

物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!

設計建模 2 大概念- UML 分類、使用

物件導向設計原則 – 6 大原則(一)

物件導向設計原則 – 6 大原則(二)

創建、行為、結構型設計 8 個比較 | 包裝模式 | 最佳實踐

創建模式:Creation Patterns

創建模式 PK

創建模式 - Creation Patterns

結構模式:Structural Patterns

結構模式 PK

結構模式 - Structural Patterns

結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結

Bridge 橋接模式 | 解說實現 | 物件導向設計

Decorate 裝飾模式 | 解說實現 | 物件導向設計

Proxy 代理模式 | 解說實現 | 分析動態代理

Iterator 迭代設計 | 解說實現 | 物件導向設計

Facade 外觀、門面模式 | 解說實現 | 物件導向設計

Adapter 設計模式 | 解說實現 | 物件導向設計

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響