Android中Context的詳細介紹

sydMobile發表於2017-12-15

文章最早釋出於我的微信公眾號 Android_De_Home 中,歡迎大家掃描下面二維碼關注微信公眾獲取更多知識內容。
本文為sydMobile原創文章,可以隨意轉載,但請務必註明出處!

寫過Java程式的都知道,我們在開發Java程式的時候,程式的入口在main()方法裡面,在main()方法裡面開始寫我們程式就可以了,隨便建立類就可以,但是在Android開發中,你見過 new Activity()、new Service()嗎?沒有把,這些Android中的元件不會像普通的Java物件一樣new一下就可以建立例項然後自己呼叫相對應的方法執行了,這就是Android不同於普通的Java開發之處,因為Android系統給你定義了一個環境,這些元件都是在這個環境下執行的,有屬於他們的生命週期,這些都是在Android系統中定義好的。這就是Android系統的環境,而這個環境中最重要的就是Context。

Context的官方介紹

Context介紹

這是在Android開發者官網中關於Context的介紹,很簡單的幾句介紹。 連線著有關應用程式的全域性資訊,這是一個通過Android系統實現的抽象類,它允許訪問特定應用程式的資源和類,以及對應用程式級的操作(比如啟動活動,廣播和接受意圖等等)。它來允許獲取以當前應用為特徵的資源型別,是一個掌握著一些 資源(系統級別的,比如獲取資源包裡面的內容,圖片,getSystemService等等一類);是一個抽象類,Android系統提供了該抽象類的具體實現類(ContextImpl);通過它我們就可以獲取應用程式的資源和類(包括我們最常用的比如啟動Activity,發廣播等等)。 這樣的描述是不是還是很抽象啊,慢慢往下來看。

先看一下Context類的繼承關係

context繼承關係

context繼承關係
這是在Android Studio中檢視的繼承關係,在這個繼承關係中,我把幾個我們常用的圈了出來,Application、Activity、Service 這幾個都是屬於Context的子類。 Context的中文翻譯為:上下文;語境;環境;背景,那麼在我們開發中我們常常把他稱為“上下文”,那麼到底什麼意思呢?我們在程式中可以這麼理解,就是我們的當前物件(Activity、Server、Application等等這些都是物件)在這個程式中所執行的環境,也就是在整個程式中的環境,這些類都是在這個環境中進行執行,完成自己的生命週期的同樣他們也都是這個環境中的一員。

這樣抽象的介紹可能不利於理解,我來舉一個栗子(可能會有點不恰當,但是會便於理解):

通過API對Context的介紹,我們可以知道他掌握著應用程式的全域性資訊,通過他我們才可以訪問使用應用程式的資源和類。把Android系統比如成一個公司,那麼Context可以認為是這個公司的老闆直接掌握著公司的資源,Application,Activity、Service等等其他的一些Context的子類,這些都是老闆找的員工,有老闆會給他們制定特定的工作和作息時間(對應它們的生命週期),這些員工可不是隨隨便便就找的都是需要系統(也就是公司)指定的負責特定的工作任務,所以不能像別的物件一樣隨便new一個,而TextView、View等等可以看做是這些主要員工工作的時候所需要的用品(比如:書本、桌子、電腦、滑鼠等等一些工作輔助用品),相對來說可以根據所需的功能隨便制定,需要了就可以new一個(相當於隨便在市場上買一個)。這些所有的員工只有在老闆的帶領下,利用公司的資源才可以完成工作(開發一個APP)。這樣的栗子應該會更加形象一些吧。

為了便於理解我簡單畫了一下Context的繼承關係圖

Context繼承關係圖

當然這裡畫的只是重點把我們常用的類畫了出來,Context的直接和間接的子類很多的。可能有人會說了為什麼我在Android Studio中檢視的時候沒有看到ContextImpl呢,是這樣的:

ContextImpl關係

圖片(ContextImpl)

上面是我在Android原始碼中的截圖ContextImpl其實並不是以public class的形式存在,而是class繼承了Context,這個檔案是保護檔案,就是註解了內部保護檔案,所以我 們在IDE中是沒有顯示的,可以去原始碼中檢視。

ContextImpl類介紹:

圖片(ContextImpl原始碼介紹)

ContextImpl是Context API的常見實現,它為Activity和其他應用程式元件提供基本上下文物件,說的通俗一點就是ContextImpl實現了抽象類的方法,我們在使用Context的時候的方法就是它實現的。

ContextWrapper類介紹:

圖片(ContextWrapper原始碼介紹)

ContextWrapper類代理Context的實現,將其所有呼叫簡單地委託給另一個Context物件(ContextImpl),可以被分類為修飾行為而不更改原始Context的類,其實就Context類的修飾類。真正的實現類是ContextImpl,ContextWrapper裡面的方法呼叫也是呼叫 ContextImpl裡面的方法。

ContextThemeWrapper

就是一個帶有主題的封裝類,比ContextWrapper多了主題,它的一個直接子類就是Activity。

通過Context的繼承關係圖結合我們幾個開發中比較熟悉的類,Activity、Service、Application,所以我們可以認為Context一共有三種型別,分別是Application、Activity和Service,他們分別承擔不同的作用,但是都屬於Context,而他們具有Context的功能則是由ContextImpl類實現的。

Context的功能

Context作為應用程式的執行環境,擁有的功能非常多,彈出Toast、啟動Activity、啟動Service、傳送廣播、運算元據庫、獲取資原始檔等等還有許多都需要用到Context,由於Context的具體能力是由ContextImpl類去實現的,因此在絕大多數情況下,Activity、Service和Application這三種型別的Context是可以通用的,不過有幾種場景比較特殊,比如啟動Activity,還有彈出Dialog,這個時候由於安全等原因的考慮,Android不允許Activity或者Dialog憑空出現,一個Activity的啟動必須要建立在另一個Activity的基礎上,也就是以此形成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert型別的Dialog),因此在這種場景下,我們只能使用Activity型別的Context,否則是會出錯的。(其實使用Application型別的Context也是可以啟動Activity的,但是不建議這麼使用,會存在關於棧的問題)。

Context數量

上面已經說過了,Context在我們常用的型別中有Application、Activity、Service三種型別因此一個應用程式中Context的數量可以這麼計算:
Context數量 = Activity數量 +Service數量 + 1(Application數量)

(注意在多程式狀態下Application的數量,網上有人說,有幾個程式就會產生幾Application例項,但是通過輸入getApplicationContext的地址,我發現在不同程式中Application的地址是相同的,於是我認為多程式中Application其實還是一個,只是在新的程式中Application會重新呼叫onCreate()進行例項化)

分別分析一下Context的這幾種型別

Application Context的設計

Application這個類也是很常見的,翻譯過來就是應用程式,首先我們來看關於Application Android原始碼是怎麼介紹的。

(圖片 Application原始碼)

維持著全域性應用程式狀態的基礎類,你可以通過繼承Application建立一個你自己的Application類。這個類要在AndroidMainfest.xml中的android:name屬性中宣告,並且這個類在任何其他類被例項化之前被例項化,換句話說Application這個類在我們開啟應用程式的時候會被第一個例項化,Application是單例模式的,通過一些模組化的方式可以給你提供許多方法,如果你的單例模式需要一個全域性的Context(比如註冊broadcast receive的時候) 包括getApplicationContext()作為一個引數可以呼叫你的單例的getInstance方法。
下面進行程式碼測試: 我們新建一個自己的Application繼承Application,並且在AndroidMainfest.xml檔案中宣告一下。

(圖片 Application宣告)

這樣在我們啟動我們的APP的時候Android系統就會首先為我們建立一個MyApplication我們在程式碼中是如何獲取我們的Application例項的呢?其實很簡單,Android API給我提供了getApplication方法來獲取

(圖片==MainActivity_getApplication)

這樣我們就會獲取我們的Application物件例項了,我們看一下列印結果:

(圖片==MainActivity_log)

這是在MainActivity中的列印結果。這樣看不刺激,我把與Context有關的幾個方法都寫上,我們來看一看結果(下面有具體的解釋)

(圖片==Application相關方法)

(圖片==MainActivity_log_內容)

(圖片==ActivitySecond_log_全部內容)

(圖片==ActivityThird_log_全部)

首先說明在這個程式是多程式,在啟動ActivitySecond的時候啟動了一個新的程式,也就是說ActivitySecond是屬於com.syd.mystudydemo:second這個程式。
從結果來看: getApplication、getApplicationContext無論在哪個Activity中或者在不同的程式中得到的結果都是一樣的。對應的都是MyApplicaton這個物件,不過需要注意的一點是在不同的程式中,開啟新的程式的時候MyApplication是會重新呼叫onCreate方法的。這是需要注意的(有可能在不同程式中Application中的變數會賦予不同的值)。

那麼getApplication和getApplicationContext有什麼不同呢? 這兩個方法所處的類不同,getApplicationContext的範圍更大一點,它是在Context裡面的方法,而getApplication這個方法,在Activity中。也就是所任何一個Context物件通過getApplicationContext都可以獲取Context物件,而getApplication只是在Activity物件中可以獲得。

(圖片==getApplicationContext—API說明)

getApplicationContext的API就說的很明白了,獲取一個當前程式的Application物件,它應該被用在這種情況下,如果你需要這個Context物件和當前所在的context生命週期分離的話(也就是用getApplicationContext獲取的Application生命週期和當前元件沒有關係是和當前程式有關係的)用registerReceive(BroadcastReceiver,IntentFilter)來舉個例子。如果你用一個來自Activity的Context(就是當前Activity,前面說了Activity是Context的子類)去註冊一個receive的話,這個receiver是在這個Activity這個範圍內的,這樣的話就是意味著你在此Activity被銷燬前就要unregister。事實上如果你不這樣做的話,Android系統將會清除你洩露的註冊,移除這個Activity然後log一個錯誤資訊。因此,如果你使用Activity上下文來註冊一個靜態的接收者(對程式是全域性的,而不是與一個Activity例項關聯的),那麼在你使用的活動被銷燬的任何點上,你的註冊將被刪除。如果你使用getApplicationContext方法返回的Context,這個receiver被註冊在全域性狀態在全域性狀態下和你的Application有有關聯。因此它將永遠不會被系統unregistered。在receiver與靜態資料有關,而不是某一個特殊的元件的時候這樣做是有必要的。然而使用getApplicationContext在別的地方是很容易引起記憶體洩露的如果你忘記了unregister,unbind,等等。

所以我們可以知道使用getApplicationContext所獲取的Context是和具體某一個元件是沒有關係的,而他和你的整個程式有關係,作用的範圍很大,但是如果你不計條件場景亂用的話,很容易造成記憶體的洩露。

(圖片==getApplication-API說明)
API說的很明確也很簡單就是返回屬於當前Activity的Application物件,這樣也說明了getApplication是Activity類的方法。

(圖片==getContext--API說明)

返回基礎context物件,通過構造方法或者setBaseContext設定值的context。
其實獲取的這個context物件就是ContextImpl物件,沒錯就是抽象類Context的實現類,其實Context裡面的方法都是在ContextImpl裡面完成的,而Application、Activity、Service這些Context的型別並沒有實現Context裡面的方法,而是呼叫的ContextImpl裡面實現的方法。我們來看看程式碼是怎麼實現的吧,Application、Service的父類就是ContextWrapper,而ContextWrapper就是Context的裝飾類,它並沒有具體的實現Context裡面的抽象方法,而是這樣呼叫的,上原始碼。

(圖片==ContextWrapper原始碼)

(圖片==ContextWrapper原始碼)

可以看到ContextWrapper裡面的方法的實現都是mBase.method(),其實呼叫的都是mBase裡面的方法,而這個地方的mBase就是ContextImpl物件。

我們可以看一下這個方法

(圖片 ==ContextWrapper-att方法)

其實mBase都是通過這個方法來賦值的,而這個方法系統會自動呼叫,在onCreate之前就會呼叫。我們需要知道的是,類似於Application、Activity、Service這幾種系統元件,不是通過構造方法new出來的,而是系統呼叫的,我們可以認為Application是在onCreate後便由Android系統生成了一個Application物件(單例,onCreate不會重複呼叫,除非多程式中)。

總結:

Context作為應用程式的執行環境,擁有的功能非常多,彈出Toast、啟動Activity、啟動Service、傳送廣播、運算元據庫、獲取資原始檔等等這些方法都是在Context中的。

比如我們常見的方法: getResources(),getPackageManager(),getContentResolver(),getApplicationContext(),getColor(),getDrawable(),obtainStyledAttributes(),getPackageName(),getPackageResourcePath(),getSharedPreferences(),deleteFile(),getExternalCacheDir()
等等方法,你可以這麼想,Context表示Android系統中我們所編寫的APP程式的執行環境,它掌握著App的執行資源,所有這些與我們APP程式有關的資源方法都在Context裡面(這樣我們在Context的所有子類中就可以使用了),只有一些很具體的方法在具體的某個元件中比如Activity是與頁面有關的所以關於視窗的方法在Activity(getWindowsManager()等等類似)裡面。 Context作為Android中程式的執行環境是Android系統定製的,所有關於他的子類我們就不要再通過new來生成了,就算你使用new生成了,那樣生成的也僅僅是普通物件,而不是Android系統中的元件,他沒有被系統賦予生命週期,所以是沒有任何價值的。要對Android系統的執行環境有個認識,我們的App是在一個Context環境下執行的,這些元件是由系統定製的,在環境下賦予的生命週期,這一點是和Java不同的。

掃一掃關注微信公眾號,獲取更多幹貨和資源

相關文章