一、Context 概念
Context是個抽象類,經過類的結構能夠看到:Activity、Service、Application都是Context的子類;android
從Android系統的角度來理解:Context是一個場景,描述的是一個應用程序環境的信息,即上下文,表明與操做系統的交互的一種過程。安全
從程序的角度上來理解:Context是個抽象類,而Activity、Service、Application等都是該類的一個實現。app
查看類的繼承關係:ctrl + H (Windows系統)ide
應用在三種狀況下會建立Context對象(即一般說的context):
1> 建立Application 對象時,即第一次啓動app時。 整個App共一個Application對象,因此也只有一個Application 的Context,Application銷燬,它也銷燬;
2> 建立Activity對象時。Activity銷燬,它也銷燬;
3> 建立Service對象時。Service銷燬,它也銷燬。工具
由此能夠獲得應用程序App能夠建立的Context(Activity和Service沒啓動就不會建立)個數公式通常爲:
總Context實例個數 = Service個數 + Activity個數 + 1(Application對應的Context對象)oop
二、源碼中的Contextui
`/** * 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 { /** * File creation mode: the default mode, where the created file can only * be accessed by the calling application (or all applications sharing the * same user ID). * @see #MODE_WORLD_READABLE * @see #MODE_WORLD_WRITEABLE */ public static final int MODE_PRIVATE = 0x0000; public static final int MODE_WORLD_WRITEABLE = 0x0002; public static final int MODE_APPEND = 0x8000; public static final int MODE_MULTI_PROCESS = 0x0004; . . . }`
源碼中的註釋是這麼來解釋Context的:Context提供了關於應用環境全局信息的接口。它是一個抽象類,它的執行被Android系統所提供。它容許獲取以應用爲特徵的資源和類型,是一個統領一些資源(應用程序環境變量等)的上下文。就是說,它描述一個應用程序環境的信息(即上下文);是一個抽象類,Android提供了該抽象類的具體實現類;經過它咱們能夠獲取應用程序的資源和類(包括應用級別操做,如啓動Activity,發廣播,接受Intent等)。既然上面Context是一個抽象類,那麼確定有他的實現類咯,咱們在Context的源碼中經過IDE能夠查看到他的子類最終能夠獲得以下關係圖:this
Context 繼承結構
三、Context做用域
雖然Context神通廣大,但並非隨便拿到一個Context實例就能夠隨心所欲,它的使用仍是有一些規則限制的。因爲Context的具體實例是由ContextImpl類去實現的,所以在絕大多數場景下,Activity、Service和Application這三種類型的Context都是能夠通用的。不過有幾種場景比較特殊,好比啓動Activity,還有彈出Dialog。出於安全緣由的考慮,Android是不容許Activity或Dialog憑空出現的,一個Activity的啓動必需要創建在另外一個Activity的基礎之上,也就是以此造成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),所以在這種場景下,咱們只能使用Activity類型的Context,不然將會出錯。spa
Context做用域操作系統
從上圖咱們能夠發現Activity所持有的Context的做用域最廣,無所不能。由於Activity繼承自ContextThemeWrapper,而Application和Service繼承自ContextWrapper,很顯然ContextThemeWrapper在ContextWrapper的基礎上又作了一些操做使得Activity變得更強大,這裏我就再也不貼源碼給你們分析了,有興趣的童鞋能夠本身查查源碼。上圖中的YES和NO我也再也不作過多的解釋了,這裏我說一下上圖中Application和Service所不推薦的兩種使用狀況。
1:若是咱們用ApplicationContext去啓動一個LaunchMode爲standard的Activity的時候會報錯android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?這是由於非Activity類型的Context並無所謂的任務棧,因此待啓動的Activity就找不到棧了。解決這個問題的方法就是爲待啓動的Activity指定FLAG_ACTIVITY_NEW_TASK標記位,這樣啓動的時候就爲它建立一個新的任務棧,而此時Activity是以singleTask模式啓動的。全部這種用Application啓動Activity的方式不推薦使用,Service同Application。
2:在Application和Service中去layout inflate也是合法的,可是會使用系統默認的主題樣式,若是你自定義了某些樣式可能不會被使用。因此這種方式也不推薦使用。
一句話總結:凡是跟UI相關的,都應該使用Activity作爲Context來處理;其餘的一些操做,Service,Activity,Application等實例均可以,固然了,注意Context引用的持有,防止內存泄漏。
4 Context數量
那麼一個應用程序中到底有多少個Context呢?其實根據上面的Context類型咱們就已經能夠得出答案了。Context一共有Application、Activity和Service三種類型,所以一個應用程序中Context數量的計算公式就能夠這樣寫:
Context數量 = Activity數量 + Service數量 + 1
上面的1表明着Application的數量,由於一個應用程序中能夠有多個Activity和多個Service,可是隻能有一個Application。
5 Application Context的設計
基本上每個應用程序都會有一個本身的Application,並讓它繼承自系統的Application類,而後在本身的Application類中去封裝一些通用的操做。其實這並非Google所推薦的一種作法,由於這樣咱們只是把Application當成了一個通用工具類來使用的,而實際上使用一個簡單的單例類也能夠實現一樣的功能。可是根據個人觀察,有太多的項目都是這樣使用Application的。固然這種作法也並無什麼反作用,只是說明仍是有很多人對於Application理解的還有些欠缺。那麼這裏咱們先來對Application的設計進行分析,講一些你們所不知道的細節,而後再看一下平時使用Application的問題。
首先新建一個MyApplication並讓它繼承自Application,而後在AndroidManifest.xml文件中對MyApplication進行指定,以下所示:
<application android:name=".MyApplication" android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > ...... </application>
指定完成後,當咱們的程序啓動時Android系統就會建立一個MyApplication的實例,若是這裏不指定的話就會默認建立一個Application的實例。
前面提到過,如今不少的Application都是被看成通用工具類來使用的,那麼既然做爲一個通用工具類,咱們要怎樣才能獲取到它的實例呢?以下所示:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyApplication myApp = (MyApplication) getApplication(); Log.d("TAG", "getApplication is " + myApp); } }
能夠看到,代碼很簡單,只須要調用getApplication()方法就能拿到咱們自定義的Application的實例了,打印結果以下所示:
那麼除了getApplication()方法,其實還有一個getApplicationContext()方法,這兩個方法看上去好像有點關聯,那麼它們的區別是什麼呢?咱們將代碼修改一下:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyApplication myApp = (MyApplication) getApplication(); Log.d("TAG", "getApplication is " + myApp); Context appContext = getApplicationContext(); Log.d("TAG", "getApplicationContext is " + appContext); } }
一樣,咱們把getApplicationContext()的結果打印了出來,如今從新運行代碼,結果以下圖所示:
咦?好像打印出的結果是同樣的呀,連後面的內存地址都是相同的,看來它們是同一個對象。其實這個結果也很好理解,由於前面已經說過了,Application自己就是一個Context,因此這裏獲取getApplicationContext()獲得的結果就是MyApplication自己的實例。
那麼有的朋友可能就會問了,既然這兩個方法獲得的結果都是相同的,那麼Android爲何要提供兩個功能重複的方法呢?實際上這兩個方法在做用域上有比較大的區別。getApplication()方法的語義性很是強,一看就知道是用來獲取Application實例的,可是這個方法只有在Activity和Service中才能調用的到。那麼也許在絕大多數狀況下咱們都是在Activity或者Service中使用Application的,可是若是在一些其它的場景,好比BroadcastReceiver中也想得到Application的實例,這時就能夠藉助getApplicationContext()方法了,以下所示:
public class MyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { MyApplication myApp = (MyApplication) context.getApplicationContext(); Log.d("TAG", "myApp is " + myApp); } }
也就是說,getApplicationContext()方法的做用域會更廣一些,任何一個Context的實例,只要調用getApplicationContext()方法均可以拿到咱們的Application對象。
那麼更加細心的朋友會發現,除了這兩個方法以外,其實還有一個getBaseContext()方法,這個baseContext又是什麼東西呢?咱們仍是經過打印的方式來驗證一下:
哦?此次獲得的是不一樣的對象了,getBaseContext()方法獲得的是一個ContextImpl對象。這個ContextImpl是否是感受有點似曾相識?回去看一下Context的繼承結構圖吧,ContextImpl正是上下文功能的實現類。也就是說像Application、Activity這樣的類其實並不會去具體實現Context的功能,而僅僅是作了一層接口封裝而已,Context的具體功能都是由ContextImpl類去完成的。那麼這樣的設計究竟是怎麼實現的呢?咱們仍是來看一下源碼吧。由於Application、Activity、Service都是直接或間接繼承自ContextWrapper的,咱們就直接看ContextWrapper的源碼,以下所示:
/** * Proxying implementation of Context that simply delegates all of its calls to * another Context. Can be subclassed to modify behavior without changing * the original Context. */ public class ContextWrapper extends Context { Context mBase; /** * Set the base context for this ContextWrapper. All calls will then be * delegated to the base context. Throws * IllegalStateException if a base context has already been set. * * @param base The new base context for this wrapper. */ protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base; } /** * @return the base context as set by the constructor or setBaseContext */ public Context getBaseContext() { return mBase; } @Override public AssetManager getAssets() { return mBase.getAssets(); } @Override public Resources getResources() { return mBase.getResources(); } @Override public ContentResolver getContentResolver() { return mBase.getContentResolver(); } @Override public Looper getMainLooper() { return mBase.getMainLooper(); } @Override public Context getApplicationContext() { return mBase.getApplicationContext(); } @Override public String getPackageName() { return mBase.getPackageName(); } @Override public void startActivity(Intent intent) { mBase.startActivity(intent); } @Override public void sendBroadcast(Intent intent) { mBase.sendBroadcast(intent); } @Override public Intent registerReceiver( BroadcastReceiver receiver, IntentFilter filter) { return mBase.registerReceiver(receiver, filter); } @Override public void unregisterReceiver(BroadcastReceiver receiver) { mBase.unregisterReceiver(receiver); } @Override public ComponentName startService(Intent service) { return mBase.startService(service); } @Override public boolean stopService(Intent name) { return mBase.stopService(name); } @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { return mBase.bindService(service, conn, flags); } @Override public void unbindService(ServiceConnection conn) { mBase.unbindService(conn); } @Override public Object getSystemService(String name) { return mBase.getSystemService(name); } ...... }
因爲ContextWrapper中的方法仍是很是多的,我就進行了一些篩選,只貼出來了部分方法。那麼上面的這些方法相信你們都是很是熟悉的,getResources()、getPackageName()、getSystemService()等等都是咱們常常要用到的方法。那麼全部這些方法的實現又是什麼樣的呢?其實全部ContextWrapper中方法的實現都很是統一,就是調用了mBase對象中對應當前方法名的方法。
那麼這個mBase對象又是什麼呢?咱們來看第16行的attachBaseContext()方法,這個方法中傳入了一個base參數,並把這個參數賦值給了mBase對象。而attachBaseContext()方法實際上是由系統來調用的,它會把ContextImpl對象做爲參數傳遞到attachBaseContext()方法當中,從而賦值給mBase對象,以後ContextWrapper中的全部方法其實都是經過這種委託的機制交由ContextImpl去具體實現的,因此說ContextImpl是上下文功能的實現類是很是準確的。
那麼另外再看一下咱們剛剛打印的getBaseContext()方法,在第26行。這個方法只有一行代碼,就是返回了mBase對象而已,而mBase對象其實就是ContextImpl對象,所以剛纔的打印結果也獲得了印證。
6正確使用Context
通常Context形成的內存泄漏,幾乎都是當Context銷燬的時候,卻由於被引用致使銷燬失敗,而Application的Context對象能夠理解爲隨着進程存在的,因此咱們總結出使用Context的正確姿式:
1:當Application的Context能搞定的狀況下,而且生命週期長的對象,優先使用Application的Context。
2:不要讓生命週期長於Activity的對象持有到Activity的引用。
3:儘可能不要在Activity中使用非靜態內部類,由於非靜態內部類會隱式持有外部類實例的引用,若是使用靜態內部類,將外部實例引用做爲弱引用持有。
7總結
總之Context在Android系統中的地位很重要,它幾乎無所不能,但它也不是你想用就能隨便用的,謹防使用不當引發的內存問題。
好了,文章到這裏就結束了,若是你以爲文章寫得不錯就給個讚唄?若是你以爲那裏值得改進的,請給我留言。必定會認真查詢,修正不足。謝謝。