編輯:Android編程入門
我們知道,Android應用都是使用Java語言來編寫的,那麼大家可以思考一下,一個Android程序和一個Java程序,他們最大的區別在哪裡?劃分界限又是什麼呢?其實簡單點分析,Android程序不像Java程序一樣,隨便創建一個類,寫個main()方法就能跑了,而是要有一個完整的Android工程環境,在這個環境下,我們有像Activity、Service、BroadcastReceiver等系統組件,而這些組件並不是像一個普通的Java對象new一下就能創建實例的了,而是要有它們各自的上下文環境,也就是我們這裡討論的Context。可以這樣講,Context是維持Android程序中各組件能夠正常工作的一個核心功能類。
下面我們來看一下Context的繼承結構:
Context的繼承結構還是稍微有點復雜的,可以看到,直系子類有兩個,一個是ContextWrapper,一個是ContextImpl。那麼從名字上就可以看出,ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實現類。而ContextWrapper又有三個直接的子類,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity。
那麼在這裡我們至少看到了幾個所比較熟悉的面孔,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,否則將會出錯。
上面的1代表著Application的數量,因為一個應用程序中可以有多個Activity和多個Service,但是只能有一個Application。
指定完成後,當我們的程序啟動時Android系統就會創建一個MyApplication的實例,如果這裡不指定的話就會默認創建一個Application的實例。
前面提到過,現在很多的Application都是被當作通用工具類來使用的,那麼既然作為一個通用工具類,我們要怎樣才能獲取到它的實例呢?如下所示:
[java] view plain copy可以看到,代碼很簡單,只需要調用getApplication()方法就能拿到我們自定義的Application的實例了,打印結果如下所示:
那麼除了getApplication()方法,其實還有一個getApplicationContext()方法,這兩個方法看上去好像有點關聯,那麼它們的區別是什麼呢?我們將代碼修改一下:
[java] view plain copy同樣,我們把getApplicationContext()的結果打印了出來,現在重新運行代碼,結果如下圖所示:
咦?好像打印出的結果是一樣的呀,連後面的內存地址都是相同的,看來它們是同一個對象。其實這個結果也很好理解,因為前面已經說過了,Application本身就是一個Context,所以這裡獲取getApplicationContext()得到的結果就是MyApplication本身的實例。
那麼有的朋友可能就會問了,既然這兩個方法得到的結果都是相同的,那麼Android為什麼要提供兩個功能重復的方法呢?實際上這兩個方法在作用域上有比較大的區別。getApplication()方法的語義性非常強,一看就知道是用來獲取Application實例的,但是這個方法只有在Activity和Service中才能調用的到。那麼也許在絕大多數情況下我們都是在Activity或者Service中使用Application的,但是如果在一些其它的場景,比如BroadcastReceiver中也想獲得Application的實例,這時就可以借助getApplicationContext()方法了,如下所示:
[java] view plain copy也就是說,getApplicationContext()方法的作用域會更廣一些,任何一個Context的實例,只要調用getApplicationContext()方法都可以拿到我們的Application對象。
那麼更加細心的朋友會發現,除了這兩個方法之外,其實還有一個getBaseContext()方法,這個baseContext又是什麼東西呢?我們還是通過打印的方式來驗證一下:
哦?這次得到的是不同的對象了,getBaseContext()方法得到的是一個ContextImpl對象。這個ContextImpl是不是感覺有點似曾相識?回去看一下Context的繼承結構圖吧,ContextImpl正是上下文功能的實現類。也就是說像Application、Activity這樣的類其實並不會去具體實現Context的功能,而僅僅是做了一層接口封裝而已,Context的具體功能都是由ContextImpl類去完成的。那麼這樣的設計到底是怎麼實現的呢?我們還是來看一下源碼吧。因為Application、Activity、Service都是直接或間接繼承自ContextWrapper的,我們就直接看ContextWrapper的源碼,如下所示:
[java] view plain copy由於ContextWrapper中的方法還是非常多的,我就進行了一些篩選,只貼出來了部分方法。那麼上面的這些方法相信大家都是非常熟悉的,getResources()、getPackageName()、getSystemService()等等都是我們經常要用到的方法。那麼所有這些方法的實現又是什麼樣的呢?其實所有ContextWrapper中方法的實現都非常統一,就是調用了mBase對象中對應當前方法名的方法。
那麼這個mBase對象又是什麼呢?我們來看第16行的attachBaseContext()方法,這個方法中傳入了一個base參數,並把這個參數賦值給了mBase對象。而attachBaseContext()方法其實是由系統來調用的,它會把ContextImpl對象作為參數傳遞到attachBaseContext()方法當中,從而賦值給mBase對象,之後ContextWrapper中的所有方法其實都是通過這種委托的機制交由ContextImpl去具體實現的,所以說ContextImpl是上下文功能的實現類是非常准確的。
那麼另外再看一下我們剛剛打印的getBaseContext()方法,在第26行。這個方法只有一行代碼,就是返回了mBase對象而已,而mBase對象其實就是ContextImpl對象,因此剛才的打印結果也得到了印證。
這是一個非常簡單的自定義Application,我們在MyApplication的構造方法當中獲取了當前應用程序的包名,並打印出來。獲取包名使用了getPackageName()方法,這個方法就是由Context提供的。那麼上面的代碼能正常運行嗎?跑一下就知道了,你將會看到如下所示的結果:
應用程序一啟動就立刻崩潰了,報的是一個空指針異常。看起來好像挺簡單的一段代碼,怎麼就會成空指針了呢?但是如果你嘗試把代碼改成下面的寫法,就會發現一切正常了:
[java] view plain copy運行結果如下所示:
在構造方法中調用Context的方法就會崩潰,在onCreate()方法中調用Context的方法就一切正常,那麼這兩個方法之間到底發生了什麼事情呢?我們重新回顧一下ContextWrapper類的源碼,ContextWrapper中有一個attachBaseContext()方法,這個方法會將傳入的一個Context參數賦值給mBase對象,之後mBase對象就有值了。而我們又知道,所有Context的方法都是調用這個mBase對象的同名方法,那麼也就是說如果在mBase對象還沒賦值的情況下就去調用Context中的任何一個方法時,就會出現空指針異常,上面的代碼就是這種情況。Application中方法的執行順序如下圖所示:
Application中在onCreate()方法裡去初始化各種全局的變量數據是一種比較推薦的做法,但是如果你想把初始化的時間點提前到極致,也可以去重寫attachBaseContext()方法,如下所示:
[java] view plain copy以上是我們平時在使用Application時需要注意的一個點,下面再來介紹另外一種非常普遍的Application誤用情況。
其實Android官方並不太推薦我們使用自定義的Application,基本上只有需要做一些全局初始化的時候可能才需要用到自定義Application,官方文檔描述如下:
但是就我的觀察而言,現在自定義Application的使用情況基本上可以達到100%了,也就是我們平時自己寫測試demo的時候可能不會使用,正式的項目幾乎全部都會使用自定義Application。可是使用歸使用,有不少項目對自定義Application的用法並不到位,正如官方文檔中所表述的一樣,多數項目只是把自定義Application當成了一個通用工具類,而這個功能並不需要借助Application來實現,使用單例可能是一種更加標准的方式。
不過自定義Application也並沒有什麼副作用,它和單例模式二選一都可以實現同樣的功能,但是我見過有一些項目,會把自定義Application和單例模式混合到一起使用,這就讓人大跌眼鏡了。一個非常典型的例子如下所示:
[java] view plain copy就像單例模式一樣,這裡提供了一個getInstance()方法,用於獲取MyApplication的實例,有了這個實例之後,就可以調用MyApplication中的各種工具方法了。
但是這種寫法對嗎?這種寫法是大錯特錯!因為我們知道Application是屬於系統組件,系統組件的實例是要由系統來去創建的,如果這裡我們自己去new一個MyApplication的實例,它就只是一個普通的Java對象而已,而不具備任何Context的能力。有很多人向我反饋使用 LitePal 時發生了空指針錯誤其實都是由於這個原因,因為你提供給LitePal的只是一個普通的Java對象,它無法通過這個對象來進行Context操作。
那麼如果真的想要提供一個獲取MyApplication實例的方法,比較標准的寫法又是什麼樣的呢?其實這裡我們只需謹記一點,Application全局只有一個,它本身就已經是單例了,無需再用單例模式去為它做多重實例保護了,代碼如下所示:
[java] view plain copygetInstance()方法可以照常提供,但是裡面不要做任何邏輯判斷,直接返回app對象就可以了,而app對象又是什麼呢?在onCreate()方法中我們將app對象賦值成this,this就是當前Application的實例,那麼app也就是當前Application的實例了。
好了,關於Context的介紹就到這裡吧,內容還是比較簡單易懂的,希望大家通過這篇文章可以理解Context更多的細節,並且不要去犯使用Context時的一些低級錯誤。
上一章講述了Android界面開發中的Widget,Service,BroadcastReceiver基本知識點,本章以一個實際案例-後台音樂播放器解析各個知識點之間的關
在QQ和微信等應用都會有設置頭像,一般都是從本地圖庫選取或相機拍照,然後再截圖自己喜歡的部分,然後設置。最後一步把截取好的圖片再保存到本地,來保存頭像。為了大家使用方便,
1.5版本的android studio上傳本地項目到git@osc上:1、在git上創建一個項目;2、在studio上導入git上新創建的項目:把git上的地址復制下來
每一個Android應用在啟動的時候都會創建一個線程,這個線程被稱為主線程或者UI線程,Android應用的所有操作默認都會運行在這個線程中。但是當我們想要進行數據請求,