一、Context 概念html
Context是個抽象類,經過類的結構能夠看到:Activity、Service、Application都是Context的子類;java
從Android系統的角度來理解:Context是一個場景,描述的是一個應用程序環境的信息,即上下文,表明與操做系統的交互的一種過程。android
從程序的角度上來理解:Context是個抽象類,而Activity、Service、Application等都是該類的一個實現。設計模式
查看類的繼承關係:ctrl + H (Windows系統)安全
應用在三種狀況下會建立Context對象(即一般說的context):
1> 建立Application 對象時,即第一次啓動app時。 整個App共一個Application對象,因此也只有一個Application 的Context,Application銷燬,它也銷燬;
2> 建立Activity對象時。Activity銷燬,它也銷燬;
3> 建立Service對象時。Service銷燬,它也銷燬。app
由此能夠獲得應用程序App能夠建立的Context(Activity和Service沒啓動就不會建立)個數公式通常爲:
總Context實例個數 = Service個數 + Activity個數 + 1(Application對應的Context對象) eclipse
2.Context 繼承結構異步
Context的繼承結構仍是稍微有點複雜的,能夠看到,直接子類有兩個,一個是ContextWrapper,一個是ContextImpl。那麼從名字上就能夠看出,ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實現類。ContextWrapper又有三個直接的子類,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity。在這裏咱們看到了幾個所比較熟悉的面孔,Activity、Service、還有Application。由此,咱們能夠大體得出結論,Context一共有三種類型,分別是Application、Activity和Service。這三個類雖然分別各類承擔着不一樣的做用,但它們都屬於Context的一種,而它們具體Context的功能則是由ContextImpl類去實現的。其實Context還有一個直接子類MockContext,,該類能夠理解爲模擬context,源碼位於android.test.mock包中,API文檔中找不到。當咱們要測試一個模塊A,他依賴於其它模塊B,可是模塊B還沒實現或如今根本沒有,這時就要使用MockContext和其餘一樣位於android.test.mock包中的類。經過它能夠注入其餘依賴,模擬Context,或者監聽測試的類。主要是在TDD中使用這些MOCK類來代替真實的類,用法可參考Mock在Android TDD中的使用。想查看ContextImpl的源碼時,沒法找到ContextImpl這個類。因爲ContextImpl是抽象類Context的實現類。然而查看Context類的繼承結構,以下圖:沒有發現ContextImpl。後來查到緣由是:這個文件是保護文件,就是註解了是內部保護文件,因此在eclipse,Androidstudio中都是不顯示的。因此能夠去SDk的安裝目錄中的sources文件夾中直接找那個Java文件,/android-sdk/sources/android-19/android/app/ContextImpl.java。Mockcontext同理。ide
三、Context 經常使用方法函數
// 獲取應用程序包的AssetManager實例 public abstract AssetManager getAssets(); // 獲取應用程序包的Resources實例 public abstract Resources getResources(); // 獲取PackageManager實例,以查看全局package信息 public abstract PackageManager getPackageManager(); // 獲取應用程序包的ContentResolver實例 public abstract ContentResolver getContentResolver(); // 它返回當前進程的主線程的Looper,此線程分發調用給應用組件(activities, services等) public abstract Looper getMainLooper(); // 返回當前進程的單實例全局Application對象的Context public abstract Context getApplicationContext(); // 從string表中獲取本地化的、格式化的字符序列 public final CharSequence getText(int resId) { return getResources().getText(resId); } // 從string表中獲取本地化的字符串 public final String getString(int resId) { return getResources().getString(resId); } public final String getString(int resId, Object... formatArgs) { return getResources().getString(resId, formatArgs); } // 返回一個可用於獲取包中類信息的class loader public abstract ClassLoader getClassLoader(); // 返回應用程序包名 public abstract String getPackageName(); // 返回應用程序信息 public abstract ApplicationInfo getApplicationInfo(); // 根據文件名獲取SharedPreferences public abstract SharedPreferences getSharedPreferences(String name, int mode); // 其根目錄爲: Environment.getExternalStorageDirectory() public abstract File getExternalFilesDir(String type); // 返回應用程序obb文件路徑 public abstract File getObbDir(); // 啓動一個新的activity public abstract void startActivity(Intent intent); // 啓動一個新的activity public void startActivityAsUser(Intent intent, UserHandle user) { throw new RuntimeException("Not implemented. Must override in a subclass."); } // 啓動一個新的activity // intent: 將被啓動的activity的描述信息 // options: 描述activity將如何被啓動 public abstract void startActivity(Intent intent, Bundle options); // 啓動多個新的activity public abstract void startActivities(Intent[] intents); // 啓動多個新的activity public abstract void startActivities(Intent[] intents, Bundle options); // 廣播一個intent給全部感興趣的接收者,異步機制 public abstract void sendBroadcast(Intent intent); // 廣播一個intent給全部感興趣的接收者,異步機制 public abstract void sendBroadcast(Intent intent,String receiverPermission); //發送有序廣播 public abstract void sendOrderedBroadcast(Intent intent,String receiverPermission); public abstract void sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras); public abstract void sendBroadcastAsUser(Intent intent, UserHandle user); public abstract void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission); // 註冊一個BroadcastReceiver,且它將在主activity線程中運行 public abstract Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter); //取消註冊BroadcastReceiver public abstract Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler); public abstract void unregisterReceiver(BroadcastReceiver receiver); // 請求啓動一個application service public abstract ComponentName startService(Intent service); // 請求中止一個application service public abstract boolean stopService(Intent service); // 鏈接一個應用服務,它定義了application和service間的依賴關係 public abstract boolean bindService(Intent service, ServiceConnection conn, int flags); // 斷開一個應用服務,當服務從新開始時,將再也不接收到調用, // 且服務容許隨時中止 public abstract void unbindService(ServiceConnection conn); // 返回系統級service public abstract Object getSystemService(String name); //檢查權限 public abstract int checkPermission(String permission, int pid, int uid); // 返回一個新的與application name對應的Context對象 public abstract Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException; // 返回基於當前Context對象的新對象,其資源與display相匹配 public abstract Context createDisplayContext(Display display);
Context的主要功能爲:
1)啓動Activity 2)啓動和中止Service 3)發送廣播消息(Intent) 4)註冊廣播消息(Intent)接收者 5)能夠訪問APK中各類資源(如Resources和AssetManager等) 6)能夠訪問Package的相關信息 7)APK的各類權限管理
Context幾乎算是對APK包無所不知的大管家,你們須要什麼,Context子類裏(一般在Activity和Service)直接調用就能夠了。
四、Context 如何獲取
一般咱們想要獲取Context對象,主要有如下四種方法
1:View.getContext,返回當前View對象的Context對象,一般是當前正在展現的Activity對象。
2:Activity.getApplicationContext,獲取當前Activity所在的(應用)進程的Context對象,一般咱們使用Context對象時,要優先考慮這個全局的進程Context。
3:ContextWrapper.getBaseContext():用來獲取一個ContextWrapper進行裝飾以前的Context,可使用這個方法,這個方法在實際開發中使用並很少,也不建議使用。
4:Activity.this 返回當前的Activity實例,若是是UI控件須要使用Activity做爲Context對象,可是默認的Toast實際上使用ApplicationContext也能夠。
public class MyActivity extends Activity { Context mContext; public void method() { mContext = this; //獲取當前Activity的上下文,若是須要綁定Activity的生命週期,使用它 mContext=MyActivity.this;//獲取當前MyActivity的上下文,不方便使用this的時候推薦使用這種方式 //調用Activity.getApplicationContext() mContext = getApplicationContext();//獲取當前Application的上下文,若是須要綁定應用的生命週期,使用它 //Activity.getApplication() mContext = getApplication();//獲取當前Application的上下文, //調用ContextWrapper.getBaseContext() mContext = getBaseContext();//從上下文A內上下文訪問上下文A,不建議使用,若是須要,推薦使用XxxClass.this直接指出上下文 } } public class MyView extends View { Context mContext; public void method() { //調用View.getContext() mContext = getContext(); //獲取這個View運行所在地的上下文 } }
1)this和getBaseContext()
Spinner spinner = (Spinner) findViewById(R.id.spinner); spinner.setAdapter(adapter); spinner.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?>arg0, View arg1, int arg2, long arg3){
//Toast.makeText(getBaseContext(),"SELECTED", Toast.LENGTH_SHORT).show();//可使用它,可是不建議
Toast.makeText(MainActivity.this,"SELECTED", Toast.LENGTH_SHORT).show();//推薦使用XxxClass.this直接指出了使用的是誰的上下文,更簡捷。
//Toast.makeText(this,"SELECTED", Toast.LENGTH_SHORT).show();//不可用,這裏this指的不是Activity,而是spinner這個類。
}
}
2)getApplicationContext()和getApplication()
getApplication()只能在Activity和Service裏使用,指向的是Application對象,由於Application也是Context的一個子類,因此getApplication()能夠被用來指向Context。
好比若是想要獲取在應用清單文件中聲明的類,最好不要使用getApplicationContext(),而且最好使用強制轉換爲本身自定義的Application,由於那樣可能會得不到Application對象。
Log.i("dyl", "getApplication is = " + myApp);
Log.i("dyl", "getApplicationContext is = " + appContext);
經過上面的代碼,打印得出二者的內存地址都是相同的,看來它們是同一個對象。其實這個結果也很好理解,由於前面已經說過了,Application自己就是一個Context,因此這裏獲取getApplicationContext()獲得的結果就是Application自己的實例。那麼問題來了,既然這兩個方法獲得的結果都是相同的,那麼Android爲何要提供兩個功能重複的方法呢?實際上這兩個方法在做用域上有比較大的區別。getApplication()方法的語義性很是強,一看就知道是用來獲取Application實例的,可是這個方法只有在Activity和Service中才能調用的到。那麼也許在絕大多數狀況下咱們都是在Activity或者Service中使用Application的,可是若是在一些其它的場景,好比BroadcastReceiver中也想得到Application的實例,這時就能夠藉助getApplicationContext()方法了。
5.Context 應用場景
由於Context的具體能力是由ContextImpl類去實現的,因此在絕大多數場景下,Activity、Service和Application這三種類型的Context都是能夠通用的。不過有幾種場景比較特殊,好比啓動Activity,還有彈出Dialog。出於安全緣由的考慮,Android是不容許Activity或Dialog憑空出現的,一個Activity的啓動必需要創建在另外一個Activity的基礎之上,也就是以此造成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),所以在這種場景下,咱們只能使用Activity類型的Context,不然將會出錯。
Context的應用場景圖
你們注意看到有一些NO上添加了一些數字,其實這些從能力上來講是YES,可是爲何說是NO呢?下面一個一個解釋:
數字1:啓動Activity在這些類中是能夠的,可是須要建立一個新的task。通常狀況不推薦。
數字2:在這些類中去layout inflate是合法的,可是會使用系統默認的主題樣式,若是你自定義了某些樣式可能不會被使用。
數字3:在receiver爲null時容許,在4.2或以上的版本中,用於獲取黏性廣播的當前值。(能夠無視)
注:ContentProvider、BroadcastReceiver之因此在上述表格中,是由於在其內部方法中都有一個context用於使用。
好了,這裏咱們看下錶格,重點看Activity和Application,能夠看到,和UI相關的方法基本都不建議或者不可以使用Application,而且,前三個操做基本不可能在Application中出現。實際上,只要把握住一點,凡是跟UI相關的,都應該使用Activity作爲Context來處理;其餘的一些操做,Service,Activity,Application等實例均可以,固然了,注意Context引用的持有,防止內存泄漏。
六、Context 使用過程當中的注意項
1)Activity mActivity =new Activity()
這樣寫語法上沒有任何錯誤,Android的應用程序開發採用JAVA語言,Activity本質上也是一個對象。可是,
2)你們在編寫一些類時,例如工具類,可能會編寫成單例的方式,這些工具類大多須要去訪問資源,也就說須要Context的參與。
在這樣的狀況下,就須要注意Context的引用問題。
public class CustomManager { private static CustomManager sInstance; private Context mContext; private CustomManager(Context context) { this.mContext = context; } public static synchronized CustomManager getInstance(Context context) { if (sInstance == null) { sInstance = new CustomManager(context); } return sInstance; } }
對於上述的單例,你們應該都不陌生(請別計較getInstance的效率問題),內部保持了一個Context的引用;這麼寫是沒有問題的,問題在於,這個Context哪來的咱們不能肯定,很大的可能性,你在某個Activity裏面爲了方便,直接傳了個this;這樣問題就來了,咱們的這個類中的sInstance是一個static且強引用的,在其內部引用了一個Activity做爲Context,也就是說,咱們的這個Activity只要咱們的項目活着,就沒有辦法進行內存回收。而咱們的Activity的生命週期確定沒這麼長,因此形成了內存泄漏。那麼,咱們如何才能避免這樣的問題呢?有人會說,咱們能夠軟引用,嗯,軟引用,假如被回收了,你不怕NullPointException麼。把上述代碼作下修改:
public static synchronized CustomManager getInstance(Context context) { if (sInstance == null) { sInstance = new CustomManager(context.getApplicationContext()); } return sInstance; }
這樣,咱們就解決了內存泄漏的問題,由於咱們引用的是一個ApplicationContext,它的生命週期和咱們的單例對象一致。
3)Intent也要求指出上下文,若是想啓動一個新的Activity,就必須在Intent中使用Activity的上下文,這樣新啓動的Activity才能和當前Activity有關聯(在activity棧);也可使用application的context,可是須要在Intent中添加 Intent.FLAG_ACTIVITY_NEW_TASK標誌,看成一個新任務。ApplicationContext去啓動一個LaunchMode爲standard的Activity的時候會報錯,非Activity類型的Context並無所謂的任務棧,因此待啓動的Activity就找不到棧了。解決這個問題的方法就是爲待啓動的Activity指定FLAG_ACTIVITY_NEW_TASK標記位,這樣啓動的時候就爲它建立一個新的任務棧,而此時Activity是以singleTask模式啓動的。因此這種用Application啓動Activity的方式不推薦使用,Service同Application。
public static void openActivity(Context context){ Intent intent = new Intent(context.getApplicationContext(), SecondActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.getApplicationContext().startActivity(intent); }
參考博文:
http://blog.csdn.net/guolin_blog/article/details/47028975
http://www.jianshu.com/p/94e0f9ab3f1d