咱們知道,Android應用都是使用Java語言來編寫的,那麼你們能夠思考一下,一個Android程序和一個Java程序,他們最大的區別在哪裏?其實簡單點分析,Android程序不像Java程序同樣,隨便建立一個類,寫個main()方法就能跑了,而是要有一個完整的Android工程環境,在這個環境下,咱們有像Activity、Service、BroadcastReceiver等系統組件,而這些組件並非像一個普通的Java對象new一下就能建立實例的了,而是要有它們各自的上下文環境,也就是咱們這裏討論的Context。能夠這樣講,Context是維持Android程序中各組件可以正常工做的一個核心功能類。java
Context自己是一個純的abstract類 , 它的直系子類有兩個:一個是ContextWrapper,一個是ContextImpl。那麼從名字上就能夠看出,ContextWrapper是上下文功能的封裝類, 它的內部包含了一個 Context對象,而ContextImpl則是上下文功能的實現類。而ContextWrapper又有三個直接的子類,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity。android
那麼在這裏咱們至少看到了幾個所比較熟悉的面孔,Activity、Service、還有Application。由此,其實咱們就已經能夠得出結論了,Context一共有三種類型,分別是Application、Activity和Service。這三個類雖然分別各類承擔着不一樣的做用,但它們都屬於Context的一種,而它們具體Context的功能則是由ContextImpl類去實現的。數據庫
那麼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,不然將會出錯。安全
名稱:Context app
路徑: /frameworks/base/core/java/android/content/Context.javaide
說明: 抽象類,提供了一組通用的API。函數
public abstract class Context { ... public abstract Object getSystemService(String name); //得到系統級服務 public abstract void startActivity(Intent intent); //經過一個Intent啓動Activity public abstract ComponentName startService(Intent service); //啓動Service //根據文件名獲得SharedPreferences對象 public abstract SharedPreferences getSharedPreferences(String name,int mode); ... }
名稱:ContextIml工具
路徑 :/frameworks/base/core/java/android/app/ContextImpl.javathis
說明:該Context類的實現類爲ContextIml,該類實現了Context類的功能。請注意,該函數的大部分功能都是直接調用spa
/** * Common implementation of Context API, which provides the base * context object for Activity and other application components. */ class ContextImpl extends Context{ //全部Application程序公用一個mPackageInfo對象 /*package*/ ActivityThread.PackageInfo mPackageInfo; @Override public Object getSystemService(String name){ ... else if (ACTIVITY_SERVICE.equals(name)) { return getActivityManager(); } else if (INPUT_METHOD_SERVICE.equals(name)) { return InputMethodManager.getInstance(this); } } @Override public void startActivity(Intent intent) { ... //開始啓動一個Activity mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, null, intent, -1); } }
名稱:ContextWrapper
路徑 :\frameworks\base\core\java\android\content\ContextWrapper.java
說明: 正如其名稱同樣,該類只是對Context類的一種包裝,該類的構造函數包含了一個真正的Context引用,即ContextIml
public class ContextWrapper extends Context { Context mBase; //該屬性指向一個ContextIml實例,通常在建立Application、Service、Activity時賦值 //建立Application、Service、Activity,會調用該方法給mBase屬性賦值 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 void startActivity(Intent intent) { mBase.startActivity(intent); //調用mBase實例方法 } }
名稱:ContextThemeWrapper
路徑:/frameworks/base/core/java/android/view/ContextThemeWrapper.java
說明:該類內部包含了主題(Theme)相關的接口,即android:theme屬性指定的。只有Activity須要主題,Service不須要主題,因此Service直接繼承於ContextWrapper類。
public class ContextThemeWrapper extends ContextWrapper { //該屬性指向一個ContextIml實例,通常在建立Application、Service、Activity時賦值 private Context mBase; //mBase賦值方式一樣有一下兩種 public ContextThemeWrapper(Context base, int themeres) { super(base); mBase = base; mThemeResource = themeres; } @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); mBase = newBase; } }
總結:context是一個抽象類,ContextWrapper是對Context的封裝,它包含一個Context類型的變 量,ContextWrapper的功能函數內部其實都是調用裏面的Context類型變量完成的。 Application,Service,Activity等都是直接或者間接繼承自ContextWrapper,可是並無真正的實現其中的功 能,Application,Service,Activity中關於Context的功能都是經過其內部的Context類型變量完成的,而這個變量的 真實對象一定是ContextImpl,因此沒建立一個Application,Activity,Servcice便會建立一個 ContextImpl,而且這些ContextImpl中的mPackages和mResources變量都是同樣的,因此無論使用Acitivty還 是Service調用getResources獲得相同的結果
那麼一個應用程序中到底有多少個Context呢?其實根據上面的Context類型咱們就已經能夠得出答案了。Context一共有Application、Activity和Service三種類型,所以一個應用程序中Context數量的計算公式就能夠這樣寫:
Context數量 = Activity數量 + Service數量 + 1
上面的1表明着Application的數量,由於一個應用程序中能夠有多個Activity和多個Service,可是隻能有一個Application。
1. 獲取Application context的不一樣方法間的區別
首先新建一個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" > ...... </applicatio
指定完成後,當咱們的程序啓動時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()方法,其實還有一個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); } }
運行結果:
咦?好像打印出的結果是同樣的呀,連後面的內存地址都是相同的,看來它們是同一個對象。其實這個結果也很好理解,由於前面已經說過了,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對象。 回顧ContextWrapper源碼,在attachBaseContext()方法中,這個方法中傳入了一個base參數,並把這個參數賦值給了mBase對象。而attachBaseContext()方法實際上是由系統來調用的,它會把ContextImpl對象做爲參數傳遞到attachBaseContext()方法當中,從而賦值給mBase對象。另外再看getBaseContext()方法,該方法只是了mBase對象而已,而mBase對象其實就是ContextImpl對象,所以剛纔的打印結果也獲得了印證。
2. Application容易發生的問題
Application是Context的其中一種類型,那麼是否就意味着,只要是Application的實例,就能隨時使用Context的各類方法呢?咱們來作個實驗試一下就知道了:
public class MyApplication extends Application { public MyApplication() { String packageName = getPackageName(); Log.d("TAG", "package name is " + packageName); } }
運行結果:
應用程序一啓動就馬上崩潰了,報的是一個空指針異常。看起來好像挺簡單的一段代碼,怎麼就會成空指針了呢?可是若是你嘗試把代碼改爲下面的寫法,就會發現一切正常了:
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); String packageName = getPackageName(); Log.d("TAG", "package name is " + packageName); } }
在構造方法中調用Context的方法就會崩潰,在onCreate()方法中調用Context的方法就一切正常,那麼這兩個方法之間到底發生了什麼事情呢?咱們從新回顧一下ContextWrapper類的源碼,ContextWrapper中有一個attachBaseContext()方法,這個方法會將傳入的一個Context參數賦值給mBase對象,以後mBase對象就有值了。而咱們又知道,全部Context的方法都是調用這個mBase對象的同名方法,那麼也就是說若是在mBase對象還沒賦值的狀況下就去調用Context中的任何一個方法時,就會出現空指針異常,上面的代碼就是這種狀況。Application中方法的執行順序以下圖所示:
Application中在onCreate()方法裏去初始化各類全局的變量數據是一種比較推薦的作法,可是若是你想把初始化的時間點提早到極致,也能夠去重寫attachBaseContext()方法,以下所示:
public class MyApplication extends Application { @Override protected void attachBaseContext(Context base) { // 在這裏調用Context的方法會崩潰 super.attachBaseContext(base); // 在這裏能夠正常調用Context的方法 } }
有一些項目,會把自定義Application和單例模式混合到一塊兒使用,一個很是典型的例子以下所示:
public class MyApplication extends Application { private static MyApplication app; public static MyApplication getInstance() { if (app == null) { app = new MyApplication(); } return app; } }
就像單例模式同樣,這裏提供了一個getInstance()方法,用於獲取MyApplication的實例,有了這個實例以後,就能夠調用MyApplication中的各類工具方法了。
可是這種寫法對嗎?大錯特錯!由於咱們知道Application是屬於系統組件,系統組件的實例是要由系統來去建立的,若是這裏咱們本身去new一個MyApplication的實例,它就只是一個普通的Java對象而已,而不具有任何Context的能力。
那麼若是真的想要提供一個獲取MyApplication實例的方法,比較標準的寫法又是什麼樣的呢?其實這裏咱們只需謹記一點,Application全局只有一個,它自己就已是單例了,無需再用單例模式去爲它作多重實例保護了,代碼以下所示:
public class MyApplication extends Application { private static MyApplication application; public static MyApplication getInstance() { return application; } @Override public void onCreate() { super.onCreate(); application = this; } }
getInstance()直接返回application對象就能夠了,由於在啓動應用Application生命週期執行到onCreate()方法時咱們將application對象賦值成this,this就是當前Application的實例。