Activity mActivity =new Activity()
作為Android開發(fā)者镰吆,不知道你有沒有思考過這個問題,Activity可以new嗎跑慕?Android的應(yīng)用程序開發(fā)采用JAVA語言万皿,Activity本質(zhì)上也是一個對象,那上面的寫法有什么問題呢核行?估計很多人說不清道不明牢硅。Android程序不像Java程序一樣,隨便創(chuàng)建一個類芝雪,寫個main()方法就能運(yùn)行唤衫,Android應(yīng)用模型是基于組件的應(yīng)用設(shè)計模式,組件的運(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
/**
* Interface to global information about an application environment. This is
* an abstract class whose implementation is provided by
* the Android system. It
* allows access to application-specific resources and classes, as well as
* up-calls for application-level operations such as launching activities,
* broadcasting and receiving intents, etc.
*/
public abstract class Context {
/**
* File creation mode: the default mode, where the created file can only
* be accessed by the calling application (or all applications sharing the
* same user ID).
* @see #MODE_WORLD_READABLE
* @see #MODE_WORLD_WRITEABLE
*/
public static final int MODE_PRIVATE = 0x0000;
public static final int MODE_WORLD_WRITEABLE = 0x0002;
public static final int MODE_APPEND = 0x8000;
public static final int MODE_MULTI_PROCESS = 0x0004;
.
.
.
}
源碼中的注釋是這么來解釋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是不需要主題的,因為Service是沒有界面的后臺場景来破,所以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都是其他地方傳過去的,所以并不計入Context總數(shù)碑定。上面的關(guān)系圖也從另外一個側(cè)面告訴我們Context類在整個Android系統(tǒng)中的地位是多么的崇高流码,因為很顯然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的作用域最廣虏等,無所不能弄唧。因為Activity繼承自ContextThemeWrapper,而Application和Service繼承自ContextWrapper博其,很顯然ContextThemeWrapper在ContextWrapper的基礎(chǔ)上又做了一些操作使得Activity變得更強(qiáng)大套才,這里我就不再貼源碼給大家分析了,有興趣的童鞋可以自己查查源碼慕淡。上圖中的YES和NO我也不再做過多的解釋了背伴,這里我說一下上圖中Application和Service所不推薦的兩種使用情況。
1:如果我們用ApplicationContext去啟動一個LaunchMode為standard的Activity的時候會報錯
android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
這是因為非Activity類型的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é)果也很好理解督暂,因為前面已經(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){
ApplicationmyApp=(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被銷毀掉,但因為它的引用還存在于一個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假勿,因為被static修飾的mDrawable是常駐內(nèi)存的,MainActivity是它的間接引用态鳖,MainActivity被銷毀時转培,也不能被GC掉,所以造成內(nèi)存泄漏浆竭。
正確使用Context
一般Context造成的內(nèi)存泄漏浸须,幾乎都是當(dāng)Context銷毀的時候,卻因為被引用導(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)部類包蓝,因為非靜態(tài)內(nèi)部類會隱式持有外部類實(shí)例的引用驶社,如果使用靜態(tài)內(nèi)部類,將外部實(shí)例引用作為弱引用持有测萎。
總結(jié)
總之Context在Android系統(tǒng)中的地位很重要亡电,它幾乎無所不能,但它也不是你想用就能隨便用的硅瞧,謹(jǐn)防使用不當(dāng)引起的內(nèi)存問題份乒。如果還有疑問或者想了解更多內(nèi)容,可以去看我的:視頻課程
本文參考閱讀:
http://blog.csdn.net/guolin_blog/article/details/47028975
http://blog.csdn.net/yanbober/article/details/45967639