kyle shanks
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    1
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- 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`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully