--- title: 'Singleton 單例模式' disqus: kyleAlien --- Singleton 單例模式 === 如有引用參考請詳註出處,感謝 :smile: ## Overview of Content :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**Singleton 單例模式、設計 | 解說實現 | Android Framework Context Service**](https://devtechascendancy.com/object-oriented_design_singleton/) ::: [TOC] ## Singleton UML > ![](https://i.imgur.com/5jzgdit.png) ### Singleton 實做特性 1. construct 必須設定為 **private**,不讓使用者隨意創建目標物件 2. 通過靜態方法、成員、列舉返回單例物件 3. 注意使用場景,**別注意 ==多執行序的安全性== 問題** 4. 確保單例物件在 **==反序列化== 時,不會重新建構物件** ### 單例設計 - 優缺點 * 單例設計優點 * 最大的特點就是 **減少對內存的使用**,避免重複創建 * 可以設定該 APP 進程的全局訪問點 * 單例設計缺點 * **不容易符合開閉原則**:單例要拓展通常需要手動修改,一般來講也不會有 interface * 建議不要對內部傳入 Context,否則會造成內存無法回收的問題(由於是單例,可達性分析總會分析到 static 仍在使用,導致 GC 無法回收沒有要用的 Activity) * 建議傳入 Application#context 來使用,因為 Application#context 的生命週期同 App 進程,所以不被回收也沒關係 ## 單例實現方法 其特色都有相似的點,目的是為了不要有多個物件,要使用該物件都必須使用同一個物件,在這個條件下在考慮其性能,**而每種實現方式都有不同特色** ### Eager Initialization * **私有化建構函數,創立私有靜態變數**,取得實例的 Func 靜態化,沒有多執行序問題 | 優點 | 缺點 | | -------- | -------- | | 沒有加鎖,效率較高 | 類加載時初始化,稍微浪費內存 | ```java= 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 就會被實例化) :::warning * 但是初次呼叫時不一定是呼叫 getInstance 方法,由此可知 **可能會造成在不必要創建時實例時被創建**,可能會浪費資源 ```java= public class Test { public static void main(String[] args) { // 呼叫一個靜態方法,仍會創建 Hungry Field Hungry.say(); } } ``` ::: **--實作--** > 創建 20 個任務,確認沒有多執行序問題 > > ![](https://i.imgur.com/YqTR0Wf.png) ### Lazy Initialization * `Lazy Initialization` 不初始化靜態變量,避免了不必要的資源浪費 > 這樣會有兩種狀況,執行序安全,執行序不安全 1. **執行序不安全** ```java= class LazyNoSyn { private static LazyNoSyn l; private LazyNoSyn() { } public static LazyNoSyn getInstance() { if(l == null) { l = new LazyNoSyn(); } return l; } } ``` **--實作--** > 創建 20 個任務,執行序不安全 問題(看下圖左邊的藍標,明明是單例模式,但是它卻創建了多個物件) > > 這個範例並非每次執行都會產生執行序多次創建物件的狀況(多執行序預設是沒有鎖的不安全操作,但是被非每次都會發生),你可以多按執行幾次嘗試看看 > > ![](https://i.imgur.com/nfyzLEg.png) 2. **執行序安全** 重點多了 `synchronized` 關鍵字來保證多執行序訪問同段代碼的「同步」行為 ```java= class LazySyn { private static LazySyn l; private LazySyn() { } public static synchronized LazySyn getInstance() { if(l == null) { l = new LazySyn(); } return l; } } ``` **--實作--** > 創建 20 個任務,`執行序安全` > > ![](https://i.imgur.com/aefkhKY.png) | 優點 | 缺點 | | -------- | -------- | | 延遲加載,在需要時才載入 | 類使用同步加鎖機制又會增加負載,每次呼叫 getInstance 都會進行一次同步動作,會造成不必要的開銷 | ### 雙鎖 DCL (Double Check Lock) * 雙鎖機制是優化 `Lazy Initialization` ,保證執行序安全 ```java= 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; } } ``` 1. 第一個 Check:**避免不必要的 `synchronized` 同步**,優化開銷 2. 第二次 Check:第一次加載才創建,第二次判斷 null **是為了避免並發問題** (可能有多個 thread 進入上一個判斷) 3. **區塊同步**:使用 class 類同步 (Java 加載時 class 時會在 class 後創建一個不變的模板),或是使用一個靜態物件同步也可以 4. **`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 關鍵字==** > ```java= > private volatile static DCL d; > ``` > `volatile` 也加減會影響效能(`volatile 修飾語每次更改時都會即時存入記憶體`,在 Android 執行序會詳說) **--實作--** > 創建 20 個任務,`執行序安全` > > ![](https://i.imgur.com/ZswvBYr.png) | 優點 | 缺點 | | -------- | -------- | | 確保多執行序的單例化 | 第一次加載較慢,在高併發的環境下可能失敗(由於 JMM 模型) | :::warning * JDK 1.5 以後才具體實現 volatile 關鍵字 ::: ### 靜態內部類 * 在某些情況下 DCL 會失效(高並發時),`Java 並發編程實踐` 也有提及這種 DCL 最佳化是醜陋的,建議使用靜態內部類 ```java= 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 個任務,`執行序安全` > > ![](https://i.imgur.com/HyUDyER.png) ### enum 列舉單例 * enum 類舉在 Java 中與普通類別是一樣的,可以有自己的方法,並在`任何情況下都是單例` (透過反編譯 Class 可以看出 enum 成員各個都是一個列別) ```java= public enum SingletonEnum { INSTANCE; public void toDo() { System.out.println("to Do Something"); } } ``` **--實作--** > ![](https://i.imgur.com/I9frziR.png) * 可以透過 `javac` 自行編譯,在透過 `javap` 查看 enum 成員的實現方式,可以發現每個 Enum 成員都是 SingletonEnum 的實現物件 (static final)! ```java= public enum SingletonEnum { INSTANCE, HELLO, WORLD; public void toDo() { System.out.println("to Do Something"); } } ``` > ![](https://i.imgur.com/aneybTL.png) ### 單例 - 反序列化 * 注意,在一種情況下會出現重新建構物件的情況,就是**反序列化**,每次反序列化都會導致物件重新被創建 * 反序列化有一個特別的`鉤子函數 readResolve` ,只要寫這個方法並返回被實例化的物件就不會創建出新物件 ```java= private Object readResolve() throws ObjectStreamException { return ...; } ``` * 以下為序列化 TestClass 與反序列化 TestClass ```java= 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(); } } } ``` 1. 首先測試尚未使用 `readResolve` 方法 ```java= public class TestClass implements Serializable { private static final TestClass instance = new TestClass(); private TestClass() { } public static TestClass getInstance() { return instance; } } ``` 透過 `hashCode` 可以看到序列化、反序列化時兩者是 **不同物件** > ![](https://i.imgur.com/z8b6giy.png) :::success * 目前使用靜態創建加載(Lazy Initialize),相同的你使用在 DCL 也一樣,序列化、反序列化時兩者是 **不同物件** ::: 2. 使用 `readResolve` 方法,並返回相同物件 ```java= 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` 可以看到序列化、反序列化時兩者是 **相同物件** > ![](https://i.imgur.com/fPJBZqb.png) :::info * enum 宣告則很特別,就算不用添加 `readResolve` 方法一樣是可以達到 序列化、反序列化時兩者是 **同物件** ::: ```java= public enum TestClass { INSTANCE; public static TestClass getInstance() { return INSTANCE; } } ``` ## Android Source 以下是一段很常見到的 ListView 加載方式,並使用了簡單的 set/getTag 去緩存資源來達到快速加載,這個優化並不是當前文章的主要內容,所以不去細說 ### 取得 LayoutInflater * 最常使用在 ListView 的 BaseAdapter 做出 Layout 界面加載,**這次主要關注在加載圖片時的 ++LayoutInflater.from(Context)++** 方法 ```java= 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 本身也是抽象類) ```java= // 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**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java?q=ActivityThread.java).java | 內部有一個 main 方法,是 Java 程式的起點方法 | | ApplicationThread.java | 屬於 ActivityThread 的內部類,是該進程與外部進程通訊的橋樑 | ```java= // 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"); } } ``` 1. 這段程式可以看出 Android 的消息驅動是透過 Looper 推動 (要先了解 [**Handler 機制**](https://hackmd.io/7fBX6uEtQt6AzCpuBWTHMQ?view)) 2. [**IActivityManager**](https://cs.android.com/android/platform/superproject/+/master:out/soong/.intermediates/frameworks/base/framework-minus-apex-intdefs/android_common/xref33/srcjars.xref/android/app/IActivityManager.java)#`attachApplication` 會觸及 Binder 機制,這裡我們當作一個黑盒子 (略過與 AMS 進程通訊),而 `attachApplication` 函數最終會回到該進程中的 ActivityThread#`handleLaunchActivity` 方法 ```java= // ActivityThread.class @Override public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) { ... 省略部份 // @ 追蹤 performLaunchActivity final Activity a = performLaunchActivity(r, customIntent); ... } ``` * ActivityThreadperformLaunchActivity:準備啟動 Activity 1. 反射創建 **Activity**、對應的 Context 類 (Context 實做類是 ContextImpl 物件) ```java= // 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; } ``` 2. 創建 **Application** 類,並讓 Context 將 Activity 包裝起來,之後在呼叫 Activity#attach 方法 ```java= // 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 */ ... } ``` > ![](https://i.imgur.com/NaaZUyp.png) :::info * 從這裡我們可以知道 1. Context 的實做類是 `ContextImpl` (接下來我們會繼續分析) 2. Activity 的生命週期大部分可以透過 Instrumentation Hook 改寫,因為 Activity 的控制大分份也都通過 **Instrumentation** ::: ### [ContextImpl](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ContextImpl.java) 分析 - [SystemServiceRegistry](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/SystemServiceRegistry.java) 單例創建 PhoneLayoutInflater * 我們了解到 Activity 是由 Context 來包裝,而 Context 的實做類是 ContextImpl 類,現在我們來看看 ContextImpl 類 1. 複習回憶:現在的目的是 **了解 LayoutInflater 這個服務 ++註冊時機++** ```java= // 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); } ``` > ![](https://i.imgur.com/eKi7OXD.png) 2. 分析 SystemServiceRegistry:SystemServiceRegistry 類是一個 **單例物件**(符合當前主題) * 在 SystemServiceRegistry 類加載時會先加載 static Field,包含 static 區塊 * 每個 registerService 實做 ServiceFetcher#createService 方法,也就是說 **==LayoutInflater 的實現物件是 PhoneLayoutInflater 類==** ```java= // 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; } } ``` 3. CachedServiceFetcher:功能有兩個 * 懶加載 Service 物件,在需要時才會呼叫 createService 方法創建 * 多 Thread 要求 Service 時會讓第一個 thread 創建,其他 thread 休眠(wait) ```java= // 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) > ![](https://i.imgur.com/xYSvmkW.png) ### ContextImpl 與 SystemServiceRegistry 關係 * ContextImpl 本身並不是單例(可以有多個),但是**其==服務就是單例==**,呼應上面所 `LayoutInflater.from()` 所呼叫的函數,Context.getSystemService 其 context 透過上面的追蹤可以知道實做類就是 ContextImpl 類 * 從這邊我們可以看到 SystemSerivceRegistry 的單例是**使用 static 靜態區塊加載 (也稱為餓漢式)**,代表所有的服務在 SystemSerivceRegistry 這個類加載的時候就已經註冊完畢 :::warning 雖說是靜態加載,但是也只加載了服務的數量,並未真正創建,**所以應該是說 ++懶加載 + 延遲加載++** > 靜態加載 -> 定義出總數量,以及其 index 位置 > > 延遲加載 -> 真正的服務物件 ::: * 特別說說 ContextImpl 中的 `getSystemService` 函數,它把 this 作為參數傳入,並且可以看到 :::success 1. 一個 Activity 有一個 ContextImpl 物件 2. 在 SystemServiceRegistry.getSystemService 中透過傳入的 Context 物件取得服務的物件,若沒有該物件則**使用當前 context 創建物件** ::: 結論: 可以透過當前的 Context 創建服務,若尚未使用到該服務則不加載 :::danger * 這裡的服務並非進程間的服務(並非 AMS、WMS、PKMS... 等等) ::: > ![](https://i.imgur.com/FNPWDX0.png) ## 更多的物件導向設計 物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)! :::info * [**設計建模 2 大概念- UML 分類、使用**](https://devtechascendancy.com/introduction-to-uml-and-diagrams/) * [**物件導向設計原則 – 6 大原則(一)**](https://devtechascendancy.com/object-oriented-design-principles_1/) * [**物件導向設計原則 – 6 大原則(二)**](https://devtechascendancy.com/object-oriented-design-principles_2/) ::: ### 創建模式 - Creation Patterns * [**創建模式 PK**](https://devtechascendancy.com/pk-design-patterns-factory-builder-best/) * **創建模式 - `Creation Patterns`**: 創建模式用於「**物件的創建**」,它關注於如何更靈活、更有效地創建物件。這些模式可以隱藏創建物件的細節,並提供創建物件的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結 :::success * [**Singleton 單例模式 | 解說實現 | Android Framework Context Service**](https://devtechascendancy.com/object-oriented_design_singleton/) * [**Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer**](https://devtechascendancy.com/object-oriented_design_abstract-factory/) * [**Factory 工廠方法模式 | 解說實現 | Java 集合設計**](https://devtechascendancy.com/object-oriented_design_factory_framework/) * [**Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗**](https://devtechascendancy.com/object-oriented_design_builder_dialog/) * [**Clone 原型模式 | 解說實現 | Android Framework Intent**](https://devtechascendancy.com/object-oriented_design_clone_framework/) * [**Object Pool 設計模式 | 實現與解說 | 利用 JVM**](https://devtechascendancy.com/object-oriented_design_object-pool/) * [**Flyweight 享元模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_flyweight/) ::: ### 行為模式 - Behavioral Patterns * [**行為模式 PK**](https://devtechascendancy.com/pk-design-patterns-cmd-strat-state-obs-chain/) * **行為模式 - `Behavioral Patterns`**: 行為模式關注物件之間的「**通信**」和「**職責分配**」。它們描述了一系列物件如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結 :::warning * [**Stragety 策略模式 | 解說實現 | Android Framework 動畫**](https://devtechascendancy.com/object-oriented_design_stragety_framework/) * [**Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService**](https://devtechascendancy.com/object-oriented_design_interpreter_framework/) * [**Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞**](https://devtechascendancy.com/object-oriented_design_chain_framework/) * [**State 狀態模式 | 實現解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_state/) * [**Specification 規格模式 | 解說實現 | Query 語句實做**](https://devtechascendancy.com/object-oriented_design_specification-query/) * [**Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_command_servant/) * [**Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存**](https://devtechascendancy.com/object-oriented_design_memo_framework/) * [**Visitor 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_visitor_dispatch/) * [**Template 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_template/) * [**Mediator 模式設計 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_mediator/) * [**Composite 組合模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_composite/) ::: ### 結構模式 - Structural Patterns * [**結構模式 PK**](https://devtechascendancy.com/pk-design-patterns-proxy-decorate-adapter/) * **結構模式 - `Structural Patterns`**: 結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結 :::danger * [**Bridge 橋接模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_bridge/) * [**Decorate 裝飾模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_decorate/) * [**Proxy 代理模式 | 解說實現 | 分析動態代理**](https://devtechascendancy.com/object-oriented_design_proxy_dynamic-proxy/) * [**Iterator 迭代設計 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_iterator/) * [**Facade 外觀、門面模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_facade/) * [**Adapter 設計模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_adapter/) ::: ## Appendix & FAQ :::info ::: ###### tags: `Java 設計模式` `基礎進階` `Android Framework`