咱們知道,Android應用都是使用Java語言來編寫的,那麼你們能夠思考一下,一個Android程序和一個Java程序,他們最大的區別在哪裏?劃分界限又是什麼呢?其實簡單點分析,Android程序不像Java程序同樣,隨便建立一個類,寫個main()方法就能跑了,而是要有一個完整的Android工程環境,在這個環境下,咱們有像Activity、Service、BroadcastReceiver等系統組件,而這些組件並非像一個普通的Java對象new一下就能建立實例的了,而是要有它們各自的上下文環境,也就是咱們這裏討論的Context。能夠這樣講,Context是維持Android程序中各組件可以正常工做的一個核心功能類。html
下面咱們來看一下Context的繼承結構:java
Context的繼承結構仍是稍微有點複雜的,能夠看到,直系子類有兩個,一個是ContextWrapper,一個是ContextImpl。那麼從名字上就能夠看出,ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實現類。而ContextWrapper又有三個直接的子類,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity。android
那麼在這裏咱們至少看到了幾個所比較熟悉的面孔,Activity、Service、還有Application。由此,其實咱們就已經能夠得出結論了,Context一共有三種類型,分別是Application、Activity和Service。這三個類雖然分別各類承擔着不一樣的做用,但它們都屬於Context的一種,而它們具體Context的功能則是由ContextImpl類去實現的。git
那麼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,不然將會出錯。github
你們注意看到有一些NO上添加了一些數字,其實這些從能力上來講是YES,可是爲何說是NO呢?下面一個一個解釋:數據庫
數字1:啓動Activity在這些類中是能夠的,可是須要建立一個新的task。通常狀況不推薦。安全
數字2:在這些類中去layout inflate是合法的,可是會使用系統默認的主題樣式,若是你自定義了某些樣式可能不會被使用。網絡
數字3:在receiver爲null時容許,在4.2或以上的版本中,用於獲取黏性廣播的當前值。(能夠無視)app
注:ContentProvider、BroadcastReceiver之因此在上述表格中,是由於在其內部方法中都有一個context用於使用。框架
好了,這裏咱們看下錶格,重點看Activity和Application,能夠看到,和UI相關的方法基本都不建議或者不可以使用Application,而且,前三個操做基本不可能在Application中出現。實際上,只要把握住一點,凡是跟UI相關的,都應該使用Activity作爲Context來處理;其餘的一些操做,Service,Activity,Application等實例均可以,固然了,注意Context引用的持有,防止內存泄漏。
那麼一個應用程序中到底有多少個Context呢?其實根據上面的Context類型咱們就已經能夠得出答案了。Context一共有Application、Activity和Service三種類型,所以一個應用程序中Context數量的計算公式就能夠這樣寫:
上面的1表明着Application的數量,由於一個應用程序中能夠有多個Activity和多個Service,可是隻能有一個Application。
1. getApplicationContext() :
這個函數返回的這個Application的上下文,因此是與app掛鉤的,因此在整個生命週期裏面都是不變的,這個好理解,可是使用的時候要注意,該context是和引用的生命週期一致的,因此和activity生命週期掛鉤的任務不要使用該context,好比網絡訪問,防止內存泄露
2. getBasecontext():
stackoverflow上面寫的是,這個函數不該該被使用,用Context代替,而Context是與activity相關連,因此當activity死亡後可能會被destroyed,我舉個我本身寫的例子
public Dialog displayDialog(int choice) { switch(choice){ case 0: AlertDialog aDialog = new AlertDialog.Builder(this) .setIcon(R.drawable.ic_launcher) .setTitle("Hello World") .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { Toast.makeText(getBaseContext(), "OK clicked", Toast.LENGTH_SHORT).show(); } }); } }
這個例子中的getBaseContext()就不能被this代替,由於上面的this返回的是這個activity的context,而在這個onClick函數中若是使用this的話,則返回的是這個AlertDialog的context,因此要使用的是當前activity名.this 去使用,好比當前activity爲 TestActivity,那麼在裏面就是用TestActivity.this便可
3. getApplication():
getApplication只能被Activity和Services使用,雖然在如今的Android的實現中,getApplication和getApplicationContext返回同樣的對象,但也不能保證這兩個函數同樣(例如在特殊的提供者來講),因此若是你想獲得你在Manifest文件裏面註冊的App class,你不要去調用getApplicationContext,覺得你可能得不到你所要的app實例(你顯然有測試框架的經驗)。。。。
翻譯完成,一目瞭然(哪裏翻譯錯誤,請指出,水B一隻),原文:
getApplication() is available to Activity and Services only. Although in current Android Activity and Service implementations, getApplication() and getApplicationContext() return the same object, there is no guarantee that this will always be the case (for example, in a specific vendor implementation). So if you want the Application class you registered in the Manifest, you should never call getApplicationContext() and cast it to your application, because it may not be the application instance (which you obviously experienced with the test framework).
4. getParent() :
返回activity的上下文,若是這個子視圖的話,換句話說,就是當在子視圖裏面調用的話就返回一個帶有子視圖的activity對象,一目瞭然。。。
5.getActivity():
在fragment中使用,返回該fragment所依附的activity上下文
6.this
記住Activity,Service類,Application類是繼承自Context類的,因此在有的時候須要上下文,只須要使用this關鍵字便可,可是有的時候再線程裏面,this關鍵字的意義就改變了,但這個時候若是須要上下文,則須要使用 類名.this,這樣就能夠了
這裏有點注意的:
作項目時碰見的,提一下吧,動態註冊廣播,在調用registerBroadcast函數的時候,須要傳入一個上下文和broadcastReceiver,查看源碼能夠知道,存儲的時候context是做爲一個key的做用,因此使用同一個context來註冊同一個廣播,onreceive只會調用一次,可是若是使用不一樣的context,則會調用屢次,雖然不調用unregisterBroadcast有時也沒事,不會報錯,可是必定不要忘記取消註銷
後續:爲了簡化context的使用方法,如今有這麼一種方法,就是在Application類裏面維護一個弱引用:
/** 用來保存當前該Application的context */ private static Context instance; /** 用來保存最新打開頁面的context */ private volatile static WeakReference<Context> instanceRef = null;
再寫一個方法,
最後在應用的Activity基類中(這個應該有的吧)加上兩個語句:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); RootApplication.setInstanceRef(this); }
protected void onResume() { super.onResume(); //也要在onresume函數裏面進行設置,保證弱引用一直引用當前的可見頁面 RootApplication.setInstanceRef(this); }
這樣每次調用application的getInstance()方法必定可以返回一個context,並且是當前惟一可見activity的context,其餘地方就能夠直接使用了,不用處處傳遞context,再此處統一維護便可,
1 public class SampleActivity extends Activity { 2 3 private final Handler mLeakyHandler = new Handler() { 4 @Override 5 public void handleMessage(Message msg) { 6 // ... 7 } 8 } 9 }
若是沒有仔細觀察,上面的代碼可能致使嚴重的內存泄露。Android Lint會給出下面的警告:
In Android, Handler classes should be static or leaks might occur.
可是究竟是泄漏,如何發生的?讓咱們肯定問題的根源,先寫下咱們所知道的
一、當一個Android應用程序第一次啓動時,Android框架爲應用程序的主線程建立一個Looper對象。一個Looper實現了一個簡單的消息隊列,在一個循環中處理Message對象。全部主要的應用程序框架事件(如活動生命週期方法調用,單擊按鈕,等等)都包含在Message對象,它被添加到Looper的消息隊列而後一個個被處理。主線程的Looper在應用程序的整個生命週期中存在。
二、當一個Handle在主線程被實例化,它就被關聯到Looper的消息隊列。被髮送到消息隊列的消息會持有一個Handler的引用,以便Android框架能夠在Looper最終處理這個消息的時候,調用Handler#handleMessage(Message)。
三、在Java中,非靜態的內部類和匿名類會隱式地持有一個他們外部類的引用。靜態內部類則不會。
那麼,究竟是內存泄漏?好像很難懂,讓咱們如下面的代碼做爲一個例子
1 public class SampleActivity extends Activity { 2 3 private final Handler mLeakyHandler = new Handler() { 4 @Override 5 public void handleMessage(Message msg) { 6 // ... 7 } 8 } 9 10 @Override 11 protected void onCreate(Bundle savedInstanceState) { 12 super.onCreate(savedInstanceState); 13 14 // 延時10分鐘發送一個消息 15 mLeakyHandler.postDelayed(new Runnable() { 16 @Override 17 public void run() { } 18 }, 60 * 10 * 1000); 19 20 // 返回前一個Activity 21 finish(); 22 } 23 }
當這個Activity被finished後,延時發送的消息會繼續在主線程的消息隊列中存活10分鐘,直到他們被處理。這個消息持有這個Activity的Handler引用,這個Handler有隱式地持有他的外部類(在這個例子中是SampleActivity)。直到消息被處理前,這個引用都不會被釋放。所以Activity不會被垃圾回收機制回收,泄露他所持有的應用程序資源。注意,第15行的匿名Runnable類也同樣。匿名類的非靜態實例持有一個隱式的外部類引用,所以context將被泄露。
爲了解決這個問題,Handler的子類應該定義在一個新文件中或使用靜態內部類。靜態內部類不會隱式持有外部類的引用。因此不會致使它的Activity泄露。若是你須要在Handle內部調用外部Activity的方法,那麼讓Handler持有一個Activity的弱引用(WeakReference)以便你不會意外致使context泄露。爲了解決咱們實例化匿名Runnable類可能致使的內存泄露,咱們將用一個靜態變量來引用他(由於匿名類的靜態實例不會隱式持有他們外部類的引用)。
1 public class SampleActivity extends Activity { 2 /** 3 * 匿名類的靜態實例不會隱式持有他們外部類的引用 4 */ 5 private static final Runnable sRunnable = new Runnable() { 6 @Override 7 public void run() { 8 } 9 }; 10 11 private final MyHandler mHandler = new MyHandler(this); 12 13 @Override 14 protected void onCreate(Bundle savedInstanceState) { 15 super.onCreate(savedInstanceState); 16 17 // 延時10分鐘發送一個消息. 18 mHandler.postDelayed(sRunnable, 60 * 10 * 1000); 19 20 // 返回前一個Activity 21 finish(); 22 } 23 24 /** 25 * 靜態內部類的實例不會隱式持有他們外部類的引用。 26 */ 27 private static class MyHandler extends Handler { 28 private final WeakReference<SampleActivity> mActivity; 29 30 public MyHandler(SampleActivity activity) { 31 mActivity = new WeakReference<SampleActivity>(activity); 32 } 33 34 @Override 35 public void handleMessage(Message msg) { 36 SampleActivity activity = mActivity.get(); 37 38 if (activity != null) { 39 // ... 40 } 41 } 42 } 43 }
靜態和非靜態內部類的區別是比較難懂的,但每個Android開發人員都應該瞭解。開發中不能碰的雷區是什麼?不在一個Activity中使用非靜態內部類, 以防它的生命週期比Activity長。相反,儘可能使用持有Activity弱引用的靜態內部類。
基本上每個應用程序都會有一個本身的Application,並讓它繼承自系統的Application類,而後在本身的Application類中去封裝一些通用的操做。其實這並非Google所推薦的一種作法,由於這樣咱們只是把Application當成了一個通用工具類來使用的,而實際上使用一個簡單的單例類也能夠實現一樣的功能。可是根據個人觀察,有太多的項目都是這樣使用Application的。固然這種作法也並無什麼反作用,只是說明仍是有很多人對於Application理解的還有些欠缺。那麼這裏咱們先來對Application的設計進行分析,講一些你們所不知道的細節,而後再看一下平時使用Application的問題。
首先新建一個MyApplication並讓它繼承自Application,而後在AndroidManifest.xml文件中對MyApplication進行指定,以下所示:
指定完成後,當咱們的程序啓動時Android系統就會建立一個MyApplication的實例,若是這裏不指定的話就會默認建立一個Application的實例。
前面提到過,如今不少的Application都是被看成通用工具類來使用的,那麼既然做爲一個通用工具類,咱們要怎樣才能獲取到它的實例呢?以下所示:
能夠看到,代碼很簡單,只須要調用getApplication()方法就能拿到咱們自定義的Application的實例了,打印結果以下所示:
那麼除了getApplication()方法,其實還有一個getApplicationContext()方法,這兩個方法看上去好像有點關聯,那麼它們的區別是什麼呢?咱們將代碼修改一下:
一樣,咱們把getApplicationContext()的結果打印了出來,如今從新運行代碼,結果以下圖所示:
咦?好像打印出的結果是同樣的呀,連後面的內存地址都是相同的,看來它們是同一個對象。其實這個結果也很好理解,由於前面已經說過了,Application自己就是一個Context,因此這裏獲取getApplicationContext()獲得的結果就是MyApplication自己的實例。
那麼有的朋友可能就會問了,既然這兩個方法獲得的結果都是相同的,那麼Android爲何要提供兩個功能重複的方法呢?實際上這兩個方法在做用域上有比較大的區別。getApplication()方法的語義性很是強,一看就知道是用來獲取Application實例的,可是這個方法只有在Activity和Service中才能調用的到。那麼也許在絕大多數狀況下咱們都是在Activity或者Service中使用Application的,可是若是在一些其它的場景,好比BroadcastReceiver中也想得到Application的實例,這時就能夠藉助getApplicationContext()方法了,以下所示:
也就是說,getApplicationContext()方法的做用域會更廣一些,任何一個Context的實例,只要調用getApplicationContext()方法均可以拿到咱們的Application對象。
那麼更加細心的朋友會發現,除了這兩個方法以外,其實還有一個getBaseContext()方法,這個baseContext又是什麼東西呢?咱們仍是經過打印的方式來驗證一下:
哦?此次獲得的是不一樣的對象了,getBaseContext()方法獲得的是一個ContextImpl對象。這個ContextImpl是否是感受有點似曾相識?回去看一下Context的繼承結構圖吧,ContextImpl正是上下文功能的實現類。也就是說像Application、Activity這樣的類其實並不會去具體實現Context的功能,而僅僅是作了一層接口封裝而已,Context的具體功能都是由ContextImpl類去完成的。那麼這樣的設計究竟是怎麼實現的呢?咱們仍是來看一下源碼吧。由於Application、Activity、Service都是直接或間接繼承自ContextWrapper的,咱們就直接看ContextWrapper的源碼,以下所示:
因爲ContextWrapper中的方法仍是很是多的,我就進行了一些篩選,只貼出來了部分方法。那麼上面的這些方法相信你們都是很是熟悉的,getResources()、getPackageName()、getSystemService()等等都是咱們常常要用到的方法。那麼全部這些方法的實現又是什麼樣的呢?其實全部ContextWrapper中方法的實現都很是統一,就是調用了mBase對象中對應當前方法名的方法。
那麼這個mBase對象又是什麼呢?咱們來看第16行的attachBaseContext()方法,這個方法中傳入了一個base參數,並把這個參數賦值給了mBase對象。而attachBaseContext()方法實際上是由系統來調用的,它會把ContextImpl對象做爲參數傳遞到attachBaseContext()方法當中,從而賦值給mBase對象,以後ContextWrapper中的全部方法其實都是經過這種委託的機制交由ContextImpl去具體實現的,因此說ContextImpl是上下文功能的實現類是很是準確的。
那麼另外再看一下咱們剛剛打印的getBaseContext()方法,在第26行。這個方法只有一行代碼,就是返回了mBase對象而已,而mBase對象其實就是ContextImpl對象,所以剛纔的打印結果也獲得了印證。
雖然說Application的用法確實很是簡單,可是咱們平時的開發工做當中也着實存在着很多Application誤用的場景,那麼今天就來看一看有哪些比較容易犯錯的地方是咱們應該注意的。
Application是Context的其中一種類型,那麼是否就意味着,只要是Application的實例,就能隨時使用Context的各類方法呢?咱們來作個實驗試一下就知道了:
這是一個很是簡單的自定義Application,咱們在MyApplication的構造方法當中獲取了當前應用程序的包名,並打印出來。獲取包名使用了getPackageName()方法,這個方法就是由Context提供的。那麼上面的代碼能正常運行嗎?跑一下就知道了,你將會看到以下所示的結果:
應用程序一啓動就馬上崩潰了,報的是一個空指針異常。看起來好像挺簡單的一段代碼,怎麼就會成空指針了呢?可是若是你嘗試把代碼改爲下面的寫法,就會發現一切正常了:
運行結果以下所示:
在構造方法中調用Context的方法就會崩潰,在onCreate()方法中調用Context的方法就一切正常,那麼這兩個方法之間到底發生了什麼事情呢?咱們從新回顧一下ContextWrapper類的源碼,ContextWrapper中有一個attachBaseContext()方法,這個方法會將傳入的一個Context參數賦值給mBase對象,以後mBase對象就有值了。而咱們又知道,全部Context的方法都是調用這個mBase對象的同名方法,那麼也就是說若是在mBase對象還沒賦值的狀況下就去調用Context中的任何一個方法時,就會出現空指針異常,上面的代碼就是這種狀況。Application中方法的執行順序以下圖所示:
Application中在onCreate()方法裏去初始化各類全局的變量數據是一種比較推薦的作法,可是若是你想把初始化的時間點提早到極致,也能夠去重寫attachBaseContext()方法,以下所示:
以上是咱們平時在使用Application時須要注意的一個點,下面再來介紹另一種很是廣泛的Application誤用狀況。
其實Android官方並不太推薦咱們使用自定義的Application,基本上只有須要作一些全局初始化的時候可能才須要用到自定義Application,官方文檔描述以下:
可是就個人觀察而言,如今自定義Application的使用狀況基本上能夠達到100%了,也就是咱們平時本身寫測試demo的時候可能不會使用,正式的項目幾乎所有都會使用自定義Application。但是使用歸使用,有很多項目對自定義Application的用法並不到位,正如官方文檔中所表述的同樣,多數項目只是把自定義Application當成了一個通用工具類,而這個功能並不須要藉助Application來實現,使用單例多是一種更加標準的方式。
不過自定義Application也並無什麼反作用,它和單例模式二選一均可以實現一樣的功能,可是我見過有一些項目,會把自定義Application和單例模式混合到一塊兒使用,這就讓人大跌眼鏡了。一個很是典型的例子以下所示:
就像單例模式同樣,這裏提供了一個getInstance()方法,用於獲取MyApplication的實例,有了這個實例以後,就能夠調用MyApplication中的各類工具方法了。
可是這種寫法對嗎?這種寫法是大錯特錯!由於咱們知道Application是屬於系統組件,系統組件的實例是要由系統來去建立的,若是這裏咱們本身去new一個MyApplication的實例,它就只是一個普通的Java對象而已,而不具有任何Context的能力。有不少人向我反饋使用 LitePal 時發生了空指針錯誤其實都是因爲這個緣由,由於你提供給LitePal的只是一個普通的Java對象,它沒法經過這個對象來進行Context操做。
那麼若是真的想要提供一個獲取MyApplication實例的方法,比較標準的寫法又是什麼樣的呢?其實這裏咱們只需謹記一點,Application全局只有一個,它自己就已是單例了,無需再用單例模式去爲它作多重實例保護了,代碼以下所示:
getInstance()方法能夠照常提供,可是裏面不要作任何邏輯判斷,直接返回app對象就能夠了,而app對象又是什麼呢?在onCreate()方法中咱們將app對象賦值成this,this就是當前Application的實例,那麼app也就是當前Application的實例了。