1. 程式人生 > >Android全面解析之Context機制

Android全面解析之Context機制

## 前言 很高興遇見你~ 歡迎閱讀我的文章。 在文章[Android全面解析之由淺及深Handler訊息機制](https://blog.csdn.net/weixin_43766753/article/details/108968666)中討論到,Handler可以: > 避免我們自己去手動寫 死迴圈和輸入阻塞 來不斷獲取使用者的輸入以及避免執行緒直接結束,而是採用事務驅動型設計,使用Handler訊息機制,讓AMS可以控制整個程式的執行邏輯。 這是關於android程式在設計上更加重要的一部分,不太瞭解的讀者可以前往閱讀了解一下。而當我們知道android程式的程式是通過main方法跑起來的,然後通過handler機制來控制程式的執行,那麼四大元件和普通的Java類到底有什麼區別?為什麼同樣是Java類,而ActivityThread、Activity等等這些類就顯得那麼特殊呢?我們的程式碼、寫的佈局是通過什麼路徑使用系統資源把介面展示在螢幕上的?這一切就涉及到我們今天的主角:Context。 ## 什麼是Context 回想一下最初學習Android開發的時候,第一用到context是什麼時候?如果你跟我一樣是通過郭霖的《第一行程式碼》來入門android,那麼一般是Toast。Toast的常規用法是: ```kotlin Toast.makeText(this, "我是toast", Toast.LENGTH_SHORT).show() ``` 當初也不知道什麼是Context,只知道他需要一個context型別,把activity物件傳進去即可。從此context貫穿在我開發過程的方方面面,但我始終不知道這個context到底有什麼用?為什麼要這個物件?我們首先來看官方對於Context類的註釋: ```java /** * Interface to global information about an application environment. This is * an abstract class whose implementation is provided by * the Android system. It * allows access to application-specific resources and classes, as well as * up-calls for application-level operations such as launching activities, * broadcasting and receiving intents, etc. */ public abstract class Context {...} ``` `關於應用程式環境的全域性資訊的介面。 這是一個抽象類,它的實現是由Android系統提供。 它允許訪問特定應用的資源和類,以及向上呼叫應用程式級的操作,如啟動活動,廣播和接收Intent等`。 可以看到Context最重要的作用就是獲取全域性訊息、訪問系統資源、呼叫應用程式級的操作。可能對於這些作用沒什麼印象,想一下,如果沒有context,我們如何做到以下操作: - 彈出一個toast - 啟動一個activity - 獲取程式佈局檔案、drawable檔案等 - 訪問資料庫 這些平時看似簡單的操作,一旦失去了context將無法執行。這些行為都有一個共同點:**需要與系統交匯**。四大元件為什麼配為元件,而我們的寫的就只能叫做一個普通的Java類,正是因為context的這些功能讓四大元件有了不一樣的能力。簡單來說,context是: > 應用程式和系統之間的橋樑,應用程式訪問系統各種資源的介面。 我們一般使用context最多的是兩種情景:直接呼叫context的方法和呼叫介面時需要context引數。這些行為都意味著我們需要訪問系統相關的資源。 那context是從哪裡來的?AMS!AMS是系統級程序,擁有訪問系統級操作的權利,應用程式的啟動受AMS的調控,在程式啟動的過程中,AMS會把一個“憑證”通過跨程序通訊給到我們的應用程式,我們的程式會把這個“憑證”封裝成context,並提供一系列的介面,這樣我們的程式也就可以很方便地訪問系統資源了。這樣的好處是: > 系統可以對應用程式級的操作進行調控,限制各種情景下的許可權,同時也可以防止惡意攻擊。 如Application類的context和Activity的context權利是不一樣的,生命週期也不一樣。對於想要作業系統攻擊使用者的程式也進行了阻止,沒有獲得允許的Java類沒有任何權利,而Activity開放給使用者也只有部分有限的權利。而我們開發者獲取context的路徑,也只有從activity、application等元件獲取。 因而,什麼是Context?Context是應用程式與系統之間溝通的橋樑,是應用程式訪問系統資源的介面,同時也是系統給應用程式的一張“許可權憑證”。有了context,一個Java類才可以被稱之為元件。 ## Context家族 上一部分我們瞭解什麼是context以及context的重要性,這一部分就來了解一下context在原始碼中的子類繼承情況。先看一個圖:
最頂層是`Context抽象類`,他定義了一系列與系統交匯的介面。`ContextWrapper`繼承自Context,但是並**沒有真正**實現Context中的介面,而是把介面的實現都託管給`ContextImpl`,ContextImpl是Context介面的真正實現者,從AMS拿來的“憑證”也是封裝到了ContextImpl中,然後賦值給ContextWrapper,這裡運用到了一種模式:**裝飾者模式**。`Application`和`Service`都繼承自ContextWrapper,那麼他們也就擁有Context的介面方法且本身即是context,方便開發者的使用。`Activity`比較特殊,因為它是有介面的,所以他需要一個主題:Theme,`ContextThemeWrapper`在ContextWrapper的基礎上增加與主題相關的操作。 這樣的設計有這樣的優點: - Activity等可以更加方便地使用context,可以把自身當成context來使用,遇到需要context的介面直接把自身傳進去即可。 - 運用裝飾者模式,向外遮蔽ContextImpl的內部邏輯,同時當需要更改ContextImpl的邏輯實現,ContextWrapper的邏輯幾乎不需要更改。 - 更方便地擴充套件不同情景下的邏輯。如service和activity,情景不同,需要的介面方法也不同,但是與系統互動的介面是相同的,使用裝飾者模式可以拓展出很多的功能,同時只需要把ContextImpl物件賦值進去即可。 ## context的分類 前面講到Context的家族體系時,瞭解到他的最終實現類有:Application、Activity、Service,ContextImpl被前三者持有,是Context介面的真正實現。那麼這裡討論一下這三者有什麼不同,和使用時需要注意的問題。 #### Application Application是全域性Context,整個應用程式只有一個,他可以訪問到應用程式的包資訊等資源資訊。獲取Application的方法一般有兩個: ```kotlin context.getApplicationContext() activity.getApplication() ``` 通過context和activity都可以獲取到Application,那這兩個方法有什麼區別?沒有區別。我們可以列印來看一下: ```kotlin override fun onCreate(savedInstanceState: Bundle?) { ... Log.d("一隻修仙的猿", "application:$application") Log.d("一隻修仙的猿", "applicationContext:$applicationContext") } ```
可以看到確實是同個物件。但為什麼要提供兩個一樣作用的方法?`getApplication()`方法更加直觀,但是隻能在activity中呼叫。`getApplicationContext()`適用範圍更廣,任意一個context物件皆可以呼叫此方法。 Application類的Context的特點是生命週期長,在整個應用程式執行的期間他都會存在。同時我們可以自定義Application,並在裡面做一些全域性的初始化操作,或者寫一個靜態的context供給全域性獲取,不需要在方法中傳入context。如: ```kotlin class MyApplication : Application(){ // 全域性context companion object{ lateinit var context: Context } override fun onCreate() { super.onCreate() // 做全域性初始化操作 RetrofitManager.init(this) context = this } } ``` 這樣我們就可以在應用啟動的時候對一些元件進行初始化,同時可以通過MyApplication.context來獲取Application物件。 但是!!!請不要把Application當成工具類使用。由於Application獲取的便利性,有開發者會在Application中編寫一些工具方法,全域性獲取使用,這樣是不行的。自定義Application的目的是在程式啟動的時候做全域性初始化工作,而不能拿來取代工具類,這嚴重違背谷歌設計Application的原則,也違背Java程式碼規範的單一職責原則。 #### 四大元件 Activity繼承自ContextThemeWrapper,是一個擁有主題的context物件。**Activity常用於與UI有關的操作**,如新增window等。常規使用可以直接用**activity.this**。 Service繼承自ContextWrapper,也可以和Activity一樣直接使用service.this來使用context。和activity不同的是,Service沒有介面,所以也不需要主題。 ContextProvider使用的是Application的context,Broadcast使用的是activity的context,這兩點在後面會進行原始碼分析。 #### BaseContext 嗯?baseContext是什麼?把這個拿出來單獨講,細心的讀者可能會發現activity中有一個方法:`getBaseContext`。這個是ContextWrapper中的mBase物件,也就是ContextImpl,也是context介面的真正邏輯實現。 #### context的使用問題 使用context最重要的問題之一是**注意記憶體洩露**。不同的context的生命週期不同,Application是在應用存在的期間會一直存在,而Activity是會隨著介面的銷燬而銷燬,如果當我們的程式碼長時間持有了activity的context,如靜態引用或者單例類,那麼會導致activity無法被釋放。如下面的程式碼: ```kotlin object MyClass { lateinit var mContext : Context fun showToast(context : Context){ mContext = context } } ``` 單例類在應用持續的時間都會一直存在,這樣context也就會被一直被持有,activity無法被回收,導致記憶體洩露。 那,我們就都換成Application不就可以了,如下: ```kotlin object MyClass { lateinit var mContext : Context fun showToast(context : Context){ mContext = context.applicationContext } } ``` 答案是:不可以。什麼時候可以使用Application?**不涉及UI以及啟動Activity操作。**Activity的context是擁有主題屬性的,如果使用Application來操作UI,那麼會丟失自定義的主題,採用系統預設的主題。同時,有些UI操作只有Activity可以執行,如彈出dialog,這涉及到window的token問題,我在這篇文章[token驗證](https://blog.csdn.net/weixin_43766753/article/details/109060496)進行了詳細的解答,有興趣的讀者可以去閱讀一下。這也是官方對於context不同許可權的設計,**沒有介面的context,就不應該有操作介面的權利**。使用Application啟動的Activity必須指定task以及標記為singleTask,因為Application是沒有任務棧的,需要重新開一個新的任務棧。因此,**我們需要根據不同context的不同職責來執行不同的任務**。 ## Context的建立過程 經過上面的討論,讀者對於context在心中有了一定的理解。但始終覺得少點什麼:activity是什麼時候被建立的,他的contextImpl是如何被賦值的?Application呢?為什麼說ContextProvider的context是Application,Broadcast的context是Activity?contextImpl又是如何被建立的?解決這些疑惑,就必須閱讀原始碼了。閱讀原始碼的好處非常多,上面我的講述,都是基於我閱讀原始碼之後的理解,而“一千個觀眾有一千個哈姆雷特”,閱讀原始碼可以**形成自己對整個機制自己的思考和理解**,同時可以讓自己對context那些知識真正落實到程式碼上,**增強自己對知識的自信心**。當別人和你意見不同的時候,你可以拍拍胸脯說:我看過原始碼,這個地方就是這樣。是不是非常自信且傲嬌? 然而閱讀原始碼不是越多越好,而是**把握整體的流程之後閱讀關鍵原始碼**,不要深入原始碼堆中無法自拔。例如我覺得activity的contextImpl是在Activity建立的過程中被賦值的,那麼我就會去找activity的啟動流程原始碼,然後只看和context有關的部分。提高效率的同時,還可以切中我們學習的點。下面的原始碼閱讀我們給出整體流程,然後重點理解關鍵程式碼,其他的原始碼讀者可自行下載原始碼去跟蹤閱讀一下。 #### Application Application應用級別的context,是在應用被建立的時候被建立的,是第一個被建立的context,也是最後一個被銷燬的context。因而追蹤Application的建立需要從應用程式的啟動流程看起。應用啟動的原始碼流程如下(簡化版):
應用程式從ActivityThread的main方法開始執行,從[Handler訊息機制](https://blog.csdn.net/weixin_43766753/article/details/108968666)中我們知道main方法主要是開啟執行緒的Looper以及handler,然後由AMS向主執行緒傳送message控制應用的啟動過程。因而我們可以把目標鎖定在圖中的最後一個方法:`handleBindApplication`,Application最有可能在這裡被建立: ```java ActivityThread.class (api29) private void handleBindApplication(AppBindData data) { ... // 建立LoadedApk物件 data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); ... Application app; ... try { // 建立Application app = data.info.makeApplication(data.restrictedBackupMode, null); ... } try { ... // 回撥Application的onCreate方法 mInstrumentation.callApplicationOnCreate(app); } ... } ``` `handleBindApplication`的引數AppBindData是AMS給應用程式的啟動資訊,其中就包含了“許可權憑證”——ApplicationInfo等。LoadedApk就是通過這些物件來建立獲取對系統資源的訪問許可權,然後通過LoadApk來建立ContextImpl以及Application。 這裡我們只關注和context建立有關的邏輯,前面啟動程式的原始碼以及AMS如何處理,這裡就不講了,讀者有興趣可以讀[ContextProvider啟動流程](https://blog.csdn.net/weixin_43766753/article/details/108110605)這篇文章,其中對ContextProvider的啟動過程就有對上述原始碼進行追蹤詳解。 那麼接下來我們繼續關注Application是如何建立的: ```java LoadeApk.class(api29) public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { // 如果application已經存在則直接返回 if (mApplication != null) { return mApplication; } ... Application app = null; String appClass = mApplicationInfo.className; ... try { java.lang.ClassLoader cl = getClassLoader(); ... // 建立ContextImpl ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); // 利用類載入器載入我們在AndroidMenifest指定的Application類 app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); // 把Application的引用給comtextImpl,這樣contextImpl也可以很方便地訪問Application appContext.setOuterContext(app); } ... mActivityThread.mAllApplications.add(app); // 把app設定為mApplication,當我們呼叫context.getApplicationContext就是獲取這個物件 mApplication = app; if (instrumentation != null) { try { // 回撥Application的onCreate方法 instrumentation.callApplicationOnCreate(app); } ... } ... return app; } ``` 程式碼的邏輯也不復雜,首先判斷LoadedApk物件中的mApplication是否存在,否則建立ContextImpl,再利用類載入器和contextImpl建立Application,最後把Application物件賦值給LoadedApk的mApplication,再回調Application的onCreate方法。我們先來看一下contextImpl是如何建立的: ```java ContextImpl.class(api29) static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo, String opPackageName) { if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null, opPackageName); context.setResources(packageInfo.getResources()); return context; } ``` 這裡直接new了一個ContextImpl,同時給ContextImpl賦值訪問系統資源相關的“許可權”物件——ActivityThread,LoadedApk等。讓我們再回到Application的建立過程。我們可以猜測,在`newApplication`包含的邏輯肯定有:利用反射建立Application,再把contextImpl賦值給Application。原因是每個人自定義的Application類不同,需要利用反射來建立物件,其次Application中的mBase屬性是對ContextImpl的引用。看原始碼: ```java Instrumentation.class(api29) public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Application app = getFactory(context.getPackageName()) .instantiateApplication(cl, className); app.attach(context); return app; } Application.class(api29) final void attach(Context context) { attachBaseContext(context); mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; } ContextWrapper.class(api29) Context mBase; protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base; } ``` 結果非常符合我們的猜測,先建立Application物件,再把ContextImpl通過Application的attach方法賦值給Application。然後Application的attach方法呼叫了ContextWrapper的attachBaseContext方法,因為Application也是繼承自ContextWrapper。這樣,就把ContextImpl賦值給Application的mBase屬性了。 再回到前面的邏輯,建立了Application之後需要回調onCreate方法: ```java Instrumentation.class(api29) public void callApplicationOnCreate(Application app) { app.onCreate(); } ``` 簡單粗暴,直接回調。到這裡,Application的建立以及context的建立流程就走完了。但是需要注意的是,**全域性初始化需要在onCreate中進行,而不要在Application的構造器中執行**。從程式碼中我們可以看到ContextImpl是在Application被建立之後再賦值的。 #### Activity Activity的context也是在Activity建立的過程中被建立的,這個就涉及到Activity的啟動流程,這裡涉及到三個流程:應用程式請求AMS,AMS處理請求,應用程式響應Activity建立事務: 依然,我們專注於Activity的建立流程,其他的讀者可閱讀[Activity啟動流程](https://blog.csdn.net/weixin_43766753/article/details/107746968)這篇文章瞭解。和Application一樣,Activity的建立時由AMS來控制的,AMS嚮應用程式程序傳送訊息來執行具體的啟動邏輯。最後會執行到`handleLaunchActivity`這個方法: ```java ActivityThread.class(api29) public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) { ... final Activity a = performLaunchActivity(r, customIntent); ... return a; } ``` 最終的就是中間這句程式碼,進入看原始碼: ```java ActivityThread.class(api29) private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... // 建立Activity的ContextImpl ContextImpl appContext = createBaseContextForActivity(r); Activity activity = null; try { // 利用類載入建立activity例項 java.lang.ClassLoader cl = appContext.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); ... } try { // 建立Application Application app = r.packageInfo.makeApplication(false, mInstrumentation); ... if (activity != null) { ... // 把activity設定給context,這樣context也可以訪問到activity了 appContext.setOuterContext(activity); // 呼叫activity的attach方法把contextImpl設定給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); int theme = r.activityInfo.getThemeResource(); if (theme != 0) { // 設定主題 activity.setTheme(theme); } ... // 回撥onCreate方法 if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); } ... } ... } ... return activity; } ``` 程式碼的邏輯不是很複雜,首先建立Activity的ContextImpl,利用類載入建立activity例項,然後再通過LoadedApk建立Application,這個方法在前面我們講過,如果Application已經建立會直接返回已經建立的物件。然後把activity設定給context,這樣context也可以訪問到activity了。這裡要注意,前面講到使用Activity的context會造成記憶體洩露,那麼可不可以用Activity的contextImpl物件呢?答案是不可以,因為**ContextImpl也會持有Activity的引用**,需要特別注意一下。隨後再呼叫activity的attach方法把contextImpl設定給activity。後面是設定主題和回撥onCreate方法,我們就不深入了,主要看看attach方法: ```java Activity.class(api29) final void attach(Context context,...) { attachBaseContext(context); ... } ``` 這裡省略了大量的程式碼,只保留關鍵一句:`attachBaseContext`,是不是很熟悉?呼叫ContextWrapper的方法來給mBase屬性賦值,和前面Application是一樣的,就不再贅述。 #### Service 依然只關注關鍵程式碼流程,先看Service的啟動流程圖: Service的建立過程也是受AMS的控制,同樣我們看到建立Service的那一步,最終會呼叫到`handleCreateService`這個方法: ```java private void handleCreateService(CreateServiceData data) { ... LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null; try { java.lang.ClassLoader cl = packageInfo.getClassLoader(); service = packageInfo.getAppFactory() .instantiateService(cl, data.info.name, data.intent); } ... try { ... ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); service.onCreate(); mServices.put(data.token, service); ... } ... } ``` Service的邏輯就相對簡單了,同樣建立service例項,再建立contextImpl,最後把contextImpl通過Service的attach方法賦值給mBase屬性,最後回撥Service的onCreate方法。過程和上面的很像,這裡就不再深入講了,感興趣的讀者可自行去閱讀原始碼,也可以閱讀[Android中Service的啟動與繫結過程詳解(基於api29)](https://blog.csdn.net/weixin_43766753/article/details/107881248)這篇文章瞭解Service的詳細內容。 #### Broadcast Broadcast和上面的元件不同,他並不是繼承自Context,所以他的Context是需要通過上述三者來給予。我們一般使用廣播的context是在接受器中,如: ```kotlin class MyClass :BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { TODO("use context") } } ``` 那麼onReceive的context物件是從哪裡來的呢?同樣我們先看廣播接收器的註冊流程: 同樣,詳細的廣播相關工作流程可以閱讀[Android廣播Broadcast的註冊與廣播原始碼過程詳解(基於api29)](https://blog.csdn.net/weixin_43766753/article/details/108066203)這篇文章瞭解。因為在建立Receiver的時候並沒有傳入context,所以我們需要追蹤他的註冊流程,看看在哪裡獲取了context。我們先看到ContextImpl的`registerReceiver`方法: ```java ContextImpl.class(api29) public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { // 注意引數 return registerReceiverInternal(receiver, getUserId(), filter, broadcastPermission, scheduler, getOuterContext(), 0); } ``` registerReceiver方法最終會來到這個過載方法,我們可以注意到,這裡有個`getOuterContext`,這個是什麼?還記得Activity的context建立過程嗎?這個方法獲取的就是activity本身。我們繼續看下去: ```java ContextImpl.class(api29) private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId, IntentFilter filter, String broadcastPermission, Handler scheduler, Context context, int flags) { IIntentReceiver rd = null; if (receiver != null) { if (mPackageInfo != null && context != null) { ... rd = mPackageInfo.getReceiverDispatcher( receiver, context, scheduler, mMainThread.getInstrumentation(), true); } ... } ... } ``` 這裡利用context建立了ReceiverDispatcher,我們繼續深入看: ```java LoadedApk.class(api29) public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r, Context context, Handler handler, Instrumentation instrumentation, boolean registered) { synchronized (mReceivers) { LoadedApk.ReceiverDispatcher rd = null; ... if (rd == null) { rd = new ReceiverDispatcher(r, context, handler, instrumentation, registered); ... } ... } } ReceiverDispatcher.class(api29) ReceiverDispatcher(..., Context context,...) { ... mContext = context; ... } ``` 這裡確實把receiver和context建立了ReceiverDispatcher,嗯?怎麼沒有給Receiver?其實這涉及到廣播的內部設計結構。Receiver是沒有跨程序通訊能力的,而廣播需要AMS的調控,所以必須有一個可以跟AMS溝通的物件,這個物件是InnerReceiver,而ReceiverDispatcher就是負責維護他們兩個的聯絡,如下圖: 而onReceive方法也是由ReceiverDispatcher回撥的,最後我們再看到回撥onReceive的那部分程式碼: ```java ReceiverDispatcher.java/Args.class; public final Runnable getRunnable() { return () -> { ...; try { ...; // 可以看到這裡回調了receiver的方法,這樣整個接收廣播的流程就走完了。 receiver.onReceive(mContext, intent); } } } ``` Args是Receiver的內部類,mContext就是在建立ReceiverDispatcher時傳入的物件,到這裡我們就知道這個物件確實是Activity了。 但是,,**不一定每個都是Activity**。在原始碼中我們知道是通過`getOuterContext`來獲取context,如果是通過別的context註冊廣播,那麼對應的物件也就不同了,只是我們一般都是在Activity中建立廣播,所以這個context一般是activity物件。 #### ContentProvider ContextProvider我們用的就比較少了,內容提供器主要是用於應用間內容共享的。雖然ContentProvider是由系統建立的,但是他本身並不屬於Context家族體系內,所以他的context也是從其他獲取的。老樣子,先看ContentProvider的建立流程: 咦?這不是Application建立的流程圖嗎?是的,ContentProvider是伴隨著應用啟動被建立的,來看一張更加詳細的流程圖: 我們把目光聚集到ContentProvider的建立上,也就是`installContentProviders`方法。同樣,詳細的ContentProvider工作流程可以訪問[Android中ContentProvider的啟動與請求原始碼流程詳解(基於api29)](https://blog.csdn.net/weixin_43766753/article/details/108110605)這篇文章。`installContentProviders`是在handleBindApplication中被呼叫的,我們看到呼叫這個方法的地方: ```java private void handleBindApplication(AppBindData data) { try { // 建立Application app = data.info.makeApplication(data.restrictedBackupMode, null); ... if (!data.restrictedBackupMode) { if (!ArrayUtils.isEmpty(data.providers)) { // 安裝ContentProvider installContentProviders(app, data.providers); } } } ``` 可以看到這裡傳入了application物件,我們繼續看下去: ```java private void installContentProviders( Context context, List providers) { final ArrayList results = new ArrayList<>(); for (ProviderInfo cpi : providers) { ... ContentProviderHolder cph = installProvider(context, null, cpi, false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/); ... } ... } ``` 這裡呼叫了installProvider,繼續往下看: ```java private ContentProviderHolder installProvider(Context context, ContentProviderHolder holder, ProviderInfo info, boolean noisy, boolean noReleaseNeeded, boolean stable) { ContentProvider localProvider = null; IContentProvider provider; if (holder == null || holder.provider == null) { ... // 這裡c最終是由context構造的 Context c = null; ApplicationInfo ai = info.applicationInfo; if (context.getPackageName().equals(ai.packageName)) { c = context; } ... try { // 建立ContentProvider final java.lang.ClassLoader cl = c.getClassLoader(); LoadedApk packageInfo = peekPackageInfo(ai.packageName, true); ... localProvider = packageInfo.getAppFactory() .instantiateProvider(cl, info.name); provider = localProvider.getIContentProvider(); ... // 把context設定給ContentProvider localProvider.attachInfo(c, info); } ... } ... } ``` 這裡最重要的一行程式碼是`localProvider.attachInfo(c, info);`,在這裡把context設定給了ContentProvider,我們再深入一點看看: ```java ContentProvider.class(api29) public void attachInfo(Context context, ProviderInfo info) { attachInfo(context, info, false); } private void attachInfo(Context context, ProviderInfo info, boolean testing) { ... if (mContext == null) { mContext = context; ... } ... } ``` 這裡確實把context賦值給了ContentProvider的內部變數mContext,這樣ContentProvider就可以使用Context了。而這個context正是一開始傳進來的Application。 ## 從原始碼設計角度看Context 到這裡關於Context的知識也講得差不多了。研究Framework層知識,不能只停留在他是什麼,有什麼作用即可。Framework層他是一個整體,構成了android這個龐大的體系,還需要看Context,在其中扮演著什麼樣的角色,解決了什麼樣的問題。在[window機制](https://blog.csdn.net/weixin_43766753/article/details/108350589)中我講到window的存在是為了解決螢幕上view的顯示邏輯與觸控反饋問題,在[Hanlder機制](https://blog.csdn.net/weixin_43766753/article/details/108968666)中我寫到整個android程式都是基於Handler機制來驅動執行的,而Context呢? Android系統是一個完整的生態,他搭建了一個環境,讓各種程式可以執行在上面。而任何一個程式,想要執行在這個環境上,必須得到系統的允許,也就是**軟體安裝**。安卓與電腦不同的是,他不是任意一個程式就可以直接訪問到系統的資源。我們在window上可以寫一個java程式,然後直接開啟一個檔案流就可以讀取和修改檔案了。而Android沒這麼簡單,他**任意一個程式的執行都必須經過系統的調控**。也就是,即時程式獲得允許(安裝在手機上了),程式本身要執行,還得是系統來控制程式執行,程式無法自發地執行在Android環境中。我們通過原始碼可以知道程式的main方法,僅僅只是開啟了執行緒的Looper迴圈,而後續的一切,都必須等待AMS來控制。 那應用程式自己硬要執行可不可以?可以,但是沒卵用。想要獲得系統資源,如啟動四大元件、讀取佈局檔案、讀寫資料庫、呼叫系統櫃攝像頭等等,都必須要通過Context,而context必須要通過AMS來獲取。這就區分了一個程式是一個普通的Java程式,還是android程式。 Context承受的兩大重要職責是:身份許可權、程式訪問系統的介面。一個Java類,如果沒有context那麼就是一個普通的Java類,而當他獲得context那麼他就可以稱之為一個元件了,因為它獲得了訪問系統的許可權,他不再是一個普通的身份,是屬於android“公民”了。而“公民”並不是無法無天,系統也可以通過context來封裝以及限制程式的許可權。要想彈出一個通知,你必須通過這個api,使用者關閉你的通知許可權,你就別想通過第二條路來彈出通知了。同時 程式也無需知道底層到底是如何實現,只管呼叫api即可。四大元件為何稱為四大元件,因為他們生來就有了context,特別是activity和service,包括Application。而我們寫的一切程式,都必須間接或者直接從其中獲取context。 總而言之,context就是負責區分android內外程式的一個機制,限制程式訪問系統資源的許可權。 ## 總結 文章從什麼是context開始介紹,再針對context的不同子類進行解析,最後結合原始碼深入地講解了context的建立過程。最後再談了我對context的設計理解。 關於context想說的就已經說完了。雖然這些內容日常很少用得到,但是非常有助於我們對Android整個系統框架的理解。而當我們對系統有更加深入的理解後,寫出來的程式也就會更加健壯。 希望文章對你有幫助。 > 全文到此,原創不易,覺得有幫助可以點贊收藏評論轉發。 > 筆者能力有限,有任何想法歡迎評論區交流指正。 > 如需轉載請私信交流。 > > 另外歡迎光臨筆者的個人部落格:[傳送門](https://qwerhuan.g