讓你徹底搞懂Context到底是什么,如果沒弄明白歉铝,還怎么做Android開發(fā)盈简?

轉(zhuǎn)自:[干貨]讓你徹底搞懂Context到底是什么,如果沒弄明白太示,還怎么做Android開發(fā)柠贤?

Activity mActivity =new Activity()

作為Android開發(fā)者,不知道你有沒有思考過這個問題类缤,Activity可以new嗎臼勉?Android的應(yīng)用程序開發(fā)采用JAVA語言,Activity本質(zhì)上也是一個對象餐弱,那上面的寫法有什么問題呢宴霸?估計(jì)很多人說不清道不明。Android程序不像Java程序一樣膏蚓,隨便創(chuàng)建一個類瓢谢,寫個main()方法就能運(yùn)行,Android應(yīng)用模型是基于組件的應(yīng)用設(shè)計(jì)模式驮瞧,組件的運(yùn)行要有一個完整的Android工程環(huán)境氓扛,在這個環(huán)境下,Activity剧董、Service等系統(tǒng)組件才能夠正常工作幢尚,而這些組件并不能采用普通的Java對象創(chuàng)建方式,new一下就能創(chuàng)建實(shí)例了翅楼,而是要有它們各自的上下文環(huán)境尉剩,也就是我們這里討論的Context∫汶可以這樣講理茎,Context是維持Android程序中各組件能夠正常工作的一個核心功能類。

Context到底是什么

Context的中文翻譯為:語境; 上下文; 背景; 環(huán)境管嬉,在開發(fā)中我們經(jīng)常說稱之為“上下文”皂林,那么這個“上下文”到底是指什么意思呢?在語文中蚯撩,我們可以理解為語境础倍,在程序中,我們可以理解為當(dāng)前對象在程序中所處的一個環(huán)境胎挎,一個與系統(tǒng)交互的過程沟启。比如微信聊天忆家,此時的“環(huán)境”是指聊天的界面以及相關(guān)的數(shù)據(jù)請求與傳輸,Context在加載資源德迹、啟動Activity芽卿、獲取系統(tǒng)服務(wù)、創(chuàng)建View等操作都要參與胳搞。

那Context到底是什么呢卸例?一個Activity就是一個Context,一個Service也是一個Context肌毅。Android程序員把“場景”抽象為Context類筷转,他們認(rèn)為用戶和操作系統(tǒng)的每一次交互都是一個場景,比如打電話芽腾、發(fā)短信旦装,這些都是一個有界面的場景,還有一些沒有界面的場景摊滔,比如后臺運(yùn)行的服務(wù)(Service)阴绢。一個應(yīng)用程序可以認(rèn)為是一個工作環(huán)境,用戶在這個環(huán)境中會切換到不同的場景艰躺,這就像一個前臺秘書呻袭,她可能需要接待客人,可能要打印文件腺兴,還可能要接聽客戶電話左电,而這些就稱之為不同的場景,前臺秘書可以稱之為一個應(yīng)用程序页响。

如何生動形象的理解Context

上面的概念中采用了通俗的理解方式篓足,將Context理解為“上下文”或者“場景”,如果你仍然覺得很抽象闰蚕,不好理解栈拖。在這里我給出一個可能不是很恰當(dāng)?shù)谋扔鳎M兄诖蠹业睦斫猓阂粋€Android應(yīng)用程序没陡,可以理解為一部電影或者一部電視劇涩哟,Activity,Service盼玄,Broadcast Receiver贴彼,Content Provider這四大組件就好比是這部戲里的四個主角:胡歌,霍建華埃儿,詩詩器仗,Baby。他們是由劇組(系統(tǒng))一開始就定好了的童番,整部戲就是由這四位主演領(lǐng)銜擔(dān)綱的青灼,所以這四位主角并不是大街上隨隨便便拉個人(new 一個對象)都能演的暴心。有了演員當(dāng)然也得有攝像機(jī)拍攝啊妓盲,他們必須通過鏡頭(Context)才能將戲傳遞給觀眾杂拨,這也就正對應(yīng)說四大組件(四位主角)必須工作在Context環(huán)境下(攝像機(jī)鏡頭)。那Button悯衬,TextView弹沽,LinearLayout這些控件呢,就好比是這部戲里的配角或者說群眾演員筋粗,他們顯然沒有這么重用策橘,隨便一個路人甲路人乙都能演(可以new一個對象),但是他們也必須要面對鏡頭(工作在Context環(huán)境下)娜亿,所以Button mButton=new Button(Context)是可以的丽已。雖然不很恰當(dāng),但還是很容易理解的买决,希望有幫助沛婴。

源碼中的Context

源碼中的注釋是這么來解釋Context的:Context提供了關(guān)于應(yīng)用環(huán)境全局信息的接口。它是一個抽象類督赤,它的執(zhí)行被Android系統(tǒng)所提供嘁灯。它允許獲取以應(yīng)用為特征的資源和類型,是一個統(tǒng)領(lǐng)一些資源(應(yīng)用程序環(huán)境變量等)的上下文躲舌。就是說丑婿,它描述一個應(yīng)用程序環(huán)境的信息(即上下文);是一個抽象類没卸,Android提供了該抽象類的具體實(shí)現(xiàn)類羹奉;通過它我們可以獲取應(yīng)用程序的資源和類(包括應(yīng)用級別操作,如啟動Activity约计,發(fā)廣播诀拭,接受Intent等)。既然上面Context是一個抽象類病蛉,那么肯定有他的實(shí)現(xiàn)類咯炫加,我們在Context的源碼中通過IDE可以查看到他的子類最終可以得到如下關(guān)系圖:

Context類本身是一個純abstract類,它有兩個具體的實(shí)現(xiàn)子類:ContextImpl和ContextWrapper铺然。其中ContextWrapper類俗孝,如其名所言,這只是一個包裝而已魄健,ContextWrapper構(gòu)造函數(shù)中必須包含一個真正的Context引用赋铝,同時ContextWrapper中提供了attachBaseContext()用于給ContextWrapper對象中指定真正的Context對象,調(diào)用ContextWrapper的方法都會被轉(zhuǎn)向其所包含的真正的Context對象沽瘦。ContextThemeWrapper類革骨,如其名所言农尖,其內(nèi)部包含了與主題(Theme)相關(guān)的接口,這里所說的主題就是指在AndroidManifest.xml中通過android:theme為Application元素或者Activity元素指定的主題良哲。當(dāng)然盛卡,只有Activity才需要主題,Service是不需要主題的筑凫,因?yàn)镾ervice是沒有界面的后臺場景滑沧,所以Service直接繼承于ContextWrapper,Application同理巍实。而ContextImpl類則真正實(shí)現(xiàn)了Context中的所以函數(shù)滓技,應(yīng)用程序中所調(diào)用的各種Context類的方法,其實(shí)現(xiàn)均來自于該類棚潦。一句話總結(jié):Context的兩個子類分工明確令漂,其中ContextImpl是Context的具體實(shí)現(xiàn)類,ContextWrapper是Context的包裝類丸边。Activity叠必,Application,Service雖都繼承自ContextWrapper(Activity繼承自ContextWrapper的子類ContextThemeWrapper)原环,但它們初始化的過程中都會創(chuàng)建ContextImpl對象挠唆,由ContextImpl實(shí)現(xiàn)Context中的方法。

一個應(yīng)用程序有幾個Context

其實(shí)這個問題本身并沒有什么意義嘱吗,關(guān)鍵還是在于對Context的理解玄组,從上面的關(guān)系圖我們已經(jīng)可以得出答案了,在應(yīng)用程序中Context的具體實(shí)現(xiàn)子類就是:Activity谒麦,Service俄讹,Application。那么Context數(shù)量=Activity數(shù)量+Service數(shù)量+1绕德。當(dāng)然如果你足夠細(xì)心患膛,可能會有疑問:我們常說四大組件,這里怎么只有Activity耻蛇,Service持有Context踪蹬,那Broadcast Receiver,Content Provider呢臣咖?Broadcast Receiver跃捣,Content Provider并不是Context的子類,他們所持有的Context都是其他地方傳過去的夺蛇,所以并不計(jì)入Context總數(shù)疚漆。上面的關(guān)系圖也從另外一個側(cè)面告訴我們Context類在整個Android系統(tǒng)中的地位是多么的崇高,因?yàn)楹茱@然Activity,Service娶聘,Application都是其子類闻镶,其地位和作用不言而喻。

Context能干什么

Context到底可以實(shí)現(xiàn)哪些功能呢丸升?這個就實(shí)在是太多了铆农,彈出Toast、啟動Activity发钝、啟動Service顿涣、發(fā)送廣播、操作數(shù)據(jù)庫等等都需要用到Context酝豪。

TextView tv = new TextView(getContext());ListAdapter adapter = new SimpleCursorAdapter(getApplicationContext(), ...);AudioManager am = (AudioManager) getContext().

getSystemService(Context.AUDIO_SERVICE);

getApplicationContext().getSharedPreferences(name, mode);getApplicationContext().getContentResolver().query(uri, ...);getContext().getResources().getDisplayMetrics().widthPixels * 5 / 8;getContext().startActivity(intent);getContext().startService(intent);getContext().sendBroadcast(intent);

Context作用域

雖然Context神通廣大,但并不是隨便拿到一個Context實(shí)例就可以為所欲為精堕,它的使用還是有一些規(guī)則限制的孵淘。由于Context的具體實(shí)例是由ContextImpl類去實(shí)現(xiàn)的,因此在絕大多數(shù)場景下歹篓,Activity瘫证、Service和Application這三種類型的Context都是可以通用的。不過有幾種場景比較特殊憨栽,比如啟動Activity米奸,還有彈出Dialog力图。出于安全原因的考慮,Android是不允許Activity或Dialog憑空出現(xiàn)的毡庆,一個Activity的啟動必須要建立在另一個Activity的基礎(chǔ)之上,也就是以此形成的返回棧烙如。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog)么抗,因此在這種場景下,我們只能使用Activity類型的Context亚铁,否則將會出錯蝇刀。

從上圖我們可以發(fā)現(xiàn)Activity所持有的Context的作用域最廣,無所不能徘溢。因?yàn)锳ctivity繼承自ContextThemeWrapper吞琐,而Application和Service繼承自ContextWrapper,很顯然ContextThemeWrapper在ContextWrapper的基礎(chǔ)上又做了一些操作使得Activity變得更強(qiáng)大然爆,這里我就不再貼源碼給大家分析了站粟,有興趣的童鞋可以自己查查源碼。上圖中的YES和NO我也不再做過多的解釋了施蜜,這里我說一下上圖中Application和Service所不推薦的兩種使用情況卒蘸。

1:如果我們用ApplicationContext去啟動一個LaunchMode為standard的Activity的時候會報(bào)錯android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?這是因?yàn)榉茿ctivity類型的Context并沒有所謂的任務(wù)棧,所以待啟動的Activity就找不到棧了。解決這個問題的方法就是為待啟動的Activity指定FLAG_ACTIVITY_NEW_TASK標(biāo)記位缸沃,這樣啟動的時候就為它創(chuàng)建一個新的任務(wù)棧恰起,而此時Activity是以singleTask模式啟動的。所有這種用Application啟動Activity的方式不推薦使用趾牧,Service同Application检盼。

2:在Application和Service中去layout inflate也是合法的,但是會使用系統(tǒng)默認(rèn)的主題樣式翘单,如果你自定義了某些樣式可能不會被使用吨枉。所以這種方式也不推薦使用。

一句話總結(jié):凡是跟UI相關(guān)的哄芜,都應(yīng)該使用Activity做為Context來處理貌亭;其他的一些操作,Service,Activity,Application等實(shí)例都可以认臊,當(dāng)然了圃庭,注意Context引用的持有,防止內(nèi)存泄漏失晴。

如何獲取Context

通常我們想要獲取Context對象剧腻,主要有以下四種方法

1:View.getContext,返回當(dāng)前View對象的Context對象,通常是當(dāng)前正在展示的Activity對象涂屁。

2:Activity.getApplicationContext,獲取當(dāng)前Activity所在的(應(yīng)用)進(jìn)程的Context對象书在,通常我們使用Context對象時,要優(yōu)先考慮這個全局的進(jìn)程Context拆又。

3:ContextWrapper.getBaseContext():用來獲取一個ContextWrapper進(jìn)行裝飾之前的Context儒旬,可以使用這個方法,這個方法在實(shí)際開發(fā)中使用并不多遏乔,也不建議使用义矛。

4:Activity.this 返回當(dāng)前的Activity實(shí)例,如果是UI控件需要使用Activity作為Context對象盟萨,但是默認(rèn)的Toast實(shí)際上使用ApplicationContext也可以凉翻。

getApplication()和getApplicationContext()

上面說到獲取當(dāng)前Application對象用getApplicationContext,不知道你有沒有聯(lián)想到getApplication()捻激,這兩個方法有什么區(qū)別制轰?相信這個問題會難倒不少開發(fā)者。

程序是不會騙人的胞谭,我們通過上面的代碼垃杖,打印得出兩者的內(nèi)存地址都是相同的,看來它們是同一個對象丈屹。其實(shí)這個結(jié)果也很好理解调俘,因?yàn)榍懊嬉呀?jīng)說過了伶棒,Application本身就是一個Context,所以這里獲取getApplicationContext()得到的結(jié)果就是Application本身的實(shí)例彩库。那么問題來了肤无,既然這兩個方法得到的結(jié)果都是相同的,那么Android為什么要提供兩個功能重復(fù)的方法呢骇钦?實(shí)際上這兩個方法在作用域上有比較大的區(qū)別宛渐。getApplication()方法的語義性非常強(qiáng),一看就知道是用來獲取Application實(shí)例的眯搭,但是這個方法只有在Activity和Service中才能調(diào)用的到窥翩。那么也許在絕大多數(shù)情況下我們都是在Activity或者Service中使用Application的,但是如果在一些其它的場景鳞仙,比如BroadcastReceiver中也想獲得Application的實(shí)例寇蚊,這時就可以借助getApplicationContext()方法了。

publicclassMyReceiverextendsBroadcastReceiver{

@Override

publicvoidonReceive(Contextcontext,Intentintent){

Application myApp =

(Application)context.getApplicationContext(); ? ?}}

Context引起的內(nèi)存泄露

但Context并不能隨便亂用繁扎,用的不好有可能會引起內(nèi)存泄露的問題幔荒,下面就示例兩種錯誤的引用方式。

錯誤的單例模式

public class Singleton {

private static Singleton instance;

private Context mContext;

private Singleton(Context context) {

this.mContext = context; ? ?}

public static Singleton getInstance(Context context) {

if (instance == null) { ? ? ? ? ? ? instance = new Singleton(context); ? ? ? ?}

return instance; ? ?}}

這是一個非線程安全的單例模式梳玫,instance作為靜態(tài)對象,其生命周期要長于普通的對象右犹,其中也包含Activity提澎,假如Activity A去getInstance獲得instance對象,傳入this念链,常駐內(nèi)存的Singleton保存了你傳入的Activity A對象盼忌,并一直持有,即使Activity被銷毀掉掂墓,但因?yàn)樗囊眠€存在于一個Singleton中谦纱,就不可能被GC掉,這樣就導(dǎo)致了內(nèi)存泄漏君编。

View持有Activity引用

public class MainActivity extends Activity { ? ?private static Drawable mDrawable;

@Override ? ?protected void onCreate(Bundle saveInstanceState) {

super.onCreate(saveInstanceState); ? ? ? ?setContentView(R.layout.activity_main);

ImageView iv = new ImageView(this);mDrawable = getResources().getDrawable(R.drawable.ic_launcher);iv.setImageDrawable(mDrawable); ? ?}}

有一個靜態(tài)的Drawable對象當(dāng)ImageView設(shè)置這個Drawable時跨嘉,ImageView保存了mDrawable的引用,而ImageView傳入的this是MainActivity的mContext吃嘿,因?yàn)楸籹tatic修飾的mDrawable是常駐內(nèi)存的祠乃,MainActivity是它的間接引用,MainActivity被銷毀時兑燥,也不能被GC掉亮瓷,所以造成內(nèi)存泄漏。

正確使用Context

一般Context造成的內(nèi)存泄漏降瞳,幾乎都是當(dāng)Context銷毀的時候嘱支,卻因?yàn)楸灰脤?dǎo)致銷毀失敗,而Application的Context對象可以理解為隨著進(jìn)程存在的,所以我們總結(jié)出使用Context的正確姿勢:

1:當(dāng)Application的Context能搞定的情況下除师,并且生命周期長的對象沛膳,優(yōu)先使用Application的Context。

2:不要讓生命周期長于Activity的對象持有到Activity的引用馍盟。

3:盡量不要在Activity中使用非靜態(tài)內(nèi)部類于置,因?yàn)榉庆o態(tài)內(nèi)部類會隱式持有外部類實(shí)例的引用,如果使用靜態(tài)內(nèi)部類贞岭,將外部實(shí)例引用作為弱引用持有八毯。

總結(jié)

總之Context在Android系統(tǒng)中的地位很重要,它幾乎無所不能瞄桨,但它也不是你想用就能隨便用的话速,謹(jǐn)防使用不當(dāng)引起的內(nèi)存問題。

本文參考閱讀:

http://blog.csdn.net/guolin_blog/article/details/47028975

http://blog.csdn.net/yanbober/article/details/45967639

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芯侥,一起剝皮案震驚了整個濱河市泊交,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柱查,老刑警劉巖廓俭,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異唉工,居然都是意外死亡研乒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門淋硝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雹熬,“玉大人,你說我怎么就攤上這事谣膳「捅ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵继谚,是天一觀的道長烈菌。 經(jīng)常有香客問我,道長犬庇,這世上最難降的妖魔是什么僧界? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮臭挽,結(jié)果婚禮上捂襟,老公的妹妹穿的比我還像新娘。我一直安慰自己欢峰,他們只是感情好葬荷,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布涨共。 她就那樣靜靜地躺著,像睡著了一般宠漩。 火紅的嫁衣襯著肌膚如雪举反。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天扒吁,我揣著相機(jī)與錄音火鼻,去河邊找鬼。 笑死雕崩,一個胖子當(dāng)著我的面吹牛魁索,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盼铁,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼粗蔚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了饶火?” 一聲冷哼從身側(cè)響起鹏控,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肤寝,沒想到半個月后当辐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鲤看,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年瀑构,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刨摩。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖世吨,靈堂內(nèi)的尸體忽然破棺而出澡刹,到底是詐尸還是另有隱情,我是刑警寧澤耘婚,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布罢浇,位于F島的核電站,受9級特大地震影響沐祷,放射性物質(zhì)發(fā)生泄漏嚷闭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一赖临、第九天 我趴在偏房一處隱蔽的房頂上張望胞锰。 院中可真熱鬧,春花似錦兢榨、人聲如沸嗅榕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凌那。三九已至兼雄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間帽蝶,已是汗流浹背赦肋。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留励稳,地道東北人佃乘。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像麦锯,于是被迫代替她去往敵國和親恕稠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容