Activity mActivity =new Activity()
作為Android開發(fā)者,不知道你有沒有思考過這個(gè)問題筋遭,Activity可以new嗎?Android的應(yīng)用程序開發(fā)采用JAVA語(yǔ)言漓滔,Activity本質(zhì)上也是一個(gè)對(duì)象编饺,那上面的寫法有什么問題呢响驴?估計(jì)很多人說不清道不明。Android程序不像Java程序一樣豁鲤,隨便創(chuàng)建一個(gè)類秽誊,寫個(gè)main()方法就能運(yùn)行鲸沮,Android應(yīng)用模型是基于組件的應(yīng)用設(shè)計(jì)模式锅论,組件的運(yùn)行要有一個(gè)完整的Android工程環(huán)境,在這個(gè)環(huán)境下棍厌,Activity肾胯、Service等系統(tǒng)組件才能夠正常工作耘纱,而這些組件并不能采用普通的Java對(duì)象創(chuàng)建方式,new一下就能創(chuàng)建實(shí)例了毕荐,而是要有它們各自的上下文環(huán)境,也就是我們這里討論的Context憎亚≡笨埽可以這樣講第美,Context是維持Android程序中各組件能夠正常工作的一個(gè)核心功能類。
Context到底是什么
Context的中文翻譯為:語(yǔ)境; 上下文; 背景; 環(huán)境什往,在開發(fā)中我們經(jīng)常說稱之為“上下文”扳缕,那么這個(gè)“上下文”到底是指什么意思呢别威?在語(yǔ)文中,我們可以理解為語(yǔ)境省古,在程序中粥庄,我們可以理解為當(dāng)前對(duì)象在程序中所處的一個(gè)環(huán)境豺妓,一個(gè)與系統(tǒng)交互的過程惜互。比如微信聊天琳拭,此時(shí)的“環(huán)境”是指聊天的界面以及相關(guān)的數(shù)據(jù)請(qǐng)求與傳輸,Context在加載資源臀栈、啟動(dòng)Activity蔫慧、獲取系統(tǒng)服務(wù)权薯、創(chuàng)建View等操作都要參與睡扬。
那Context到底是什么呢?一個(gè)Activity就是一個(gè)Context黍析,一個(gè)Service也是一個(gè)Context。Android程序員把“場(chǎng)景”抽象為Context類阐枣,他們認(rèn)為用戶和操作系統(tǒng)的每一次交互都是一個(gè)場(chǎng)景马靠,比如打電話蔼两、發(fā)短信,這些都是一個(gè)有界面的場(chǎng)景额划,還有一些沒有界面的場(chǎng)景妙啃,比如后臺(tái)運(yùn)行的服務(wù)(Service)俊戳。一個(gè)應(yīng)用程序可以認(rèn)為是一個(gè)工作環(huán)境揖赴,用戶在這個(gè)環(huán)境中會(huì)切換到不同的場(chǎng)景抑胎,這就像一個(gè)前臺(tái)秘書,她可能需要接待客人阿逃,可能要打印文件铭拧,還可能要接聽客戶電話盆昙,而這些就稱之為不同的場(chǎng)景,前臺(tái)秘書可以稱之為一個(gè)應(yīng)用程序淡喜。
/**
* 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)境全局信息的接口秕磷。它是一個(gè)抽象類炼团,它的執(zhí)行被Android系統(tǒng)所提供。它允許獲取以應(yīng)用為特征的資源和類型瘟芝,是一個(gè)統(tǒng)領(lǐng)一些資源(應(yīng)用程序環(huán)境變量等)的上下文易桃。就是說锌俱,它描述一個(gè)應(yīng)用程序環(huán)境的信息(即上下文)晤郑;是一個(gè)抽象類,Android提供了該抽象類的具體實(shí)現(xiàn)類磕洪;通過它我們可以獲取應(yīng)用程序的資源和類(包括應(yīng)用級(jí)別操作,如啟動(dòng)Activity诫龙,發(fā)廣播析显,接受Intent等)签赃。既然上面Context是一個(gè)抽象類,那么肯定有他的實(shí)現(xiàn)類咯锦聊,我們?cè)贑ontext的源碼中通過IDE可以查看到他的子類最終可以得到如下關(guān)系圖:
Context類本身是一個(gè)純abstract類歹嘹,它有兩個(gè)具體的實(shí)現(xiàn)子類:ContextImpl和ContextWrapper括丁。其中ContextWrapper類伶选,如其名所言史飞,這只是一個(gè)包裝而已,ContextWrapper構(gòu)造函數(shù)中必須包含一個(gè)真正的Context引用,同時(shí)ContextWrapper中提供了attachBaseContext()用于給ContextWrapper對(duì)象中指定真正的Context對(duì)象,調(diào)用ContextWrapper的方法都會(huì)被轉(zhuǎn)向其所包含的真正的Context對(duì)象庐冯。ContextThemeWrapper類彼哼,如其名所言河绽,其內(nèi)部包含了與主題(Theme)相關(guān)的接口,這里所說的主題就是指在AndroidManifest.xml中通過android:theme為Application元素或者Activity元素指定的主題耙饰。當(dāng)然纹笼,只有Activity才需要主題苟跪,Service是不需要主題的,因?yàn)镾ervice是沒有界面的后臺(tái)場(chǎng)景件已,所以Service直接繼承于ContextWrapper笋额,Application同理。而ContextImpl類則真正實(shí)現(xiàn)了Context中的所以函數(shù)兄猩,應(yīng)用程序中所調(diào)用的各種Context類的方法,其實(shí)現(xiàn)均來自于該類。一句話總結(jié):Context的兩個(gè)子類分工明確枢冤,其中ContextImpl是Context的具體實(shí)現(xiàn)類,ContextWrapper是Context的包裝類掏导。Activity享怀,Application趟咆,Service雖都繼承自ContextWrapper(Activity繼承自ContextWrapper的子類ContextThemeWrapper),但它們初始化的過程中都會(huì)創(chuàng)建ContextImpl對(duì)象值纱,由ContextImpl實(shí)現(xiàn)Context中的方法鳞贷。
一個(gè)應(yīng)用程序有幾個(gè)Context
其實(shí)這個(gè)問題本身并沒有什么意義虐唠,關(guān)鍵還是在于對(duì)Context的理解,從上面的關(guān)系圖我們已經(jīng)可以得出答案了疆偿,在應(yīng)用程序中Context的具體實(shí)現(xiàn)子類就是:Activity咱筛,Service,Application迅箩。那么Context數(shù)量=Activity數(shù)量+Service數(shù)量+1
。當(dāng)然如果你足夠細(xì)心处铛,可能會(huì)有疑問:我們常說四大組件,這里怎么只有Activity撤蟆,Service持有Context奕塑,那Broadcast Receiver家肯,Content Provider呢龄砰?Broadcast Receiver息楔,Content Provider并不是Context的子類,他們所持有的Context都是其他地方傳過去的值依,所以并不計(jì)入Context總數(shù)圃泡。上面的關(guān)系圖也從另外一個(gè)側(cè)面告訴我們Context類在整個(gè)Android系統(tǒng)中的地位是多么的崇高愿险,因?yàn)楹茱@然Activity价说,Service,Application都是其子類风秤,其地位和作用不言而喻鳖目。
Context能干什么
Context到底可以實(shí)現(xiàn)哪些功能呢缤弦?這個(gè)就實(shí)在是太多了,彈出Toast碍沐、啟動(dòng)Activity狸捅、啟動(dòng)Service累提、發(fā)送廣播、操作數(shù)據(jù)庫(kù)等等都需要用到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神通廣大朽褪,但并不是隨便拿到一個(gè)Context實(shí)例就可以為所欲為无虚,它的使用還是有一些規(guī)則限制的缔赠。由于Context的具體實(shí)例是由ContextImpl類去實(shí)現(xiàn)的骑科,因此在絕大多數(shù)場(chǎng)景下,Activity咆爽、Service和Application這三種類型的Context都是可以通用的。不過有幾種場(chǎng)景比較特殊置森,比如啟動(dòng)Activity,還有彈出Dialog凫海。出于安全原因的考慮呛凶,Android是不允許Activity或Dialog憑空出現(xiàn)的行贪,一個(gè)Activity的啟動(dòng)必須要建立在另一個(gè)Activity的基礎(chǔ)之上,也就是以此形成的返回棧建瘫。而Dialog則必須在一個(gè)Activity上面彈出(除非是System Alert類型的Dialog)崭捍,因此在這種場(chǎng)景下啰脚,我們只能使用Activity類型的Context殷蛇,否則將會(huì)出錯(cuò)。
從上圖我們可以發(fā)現(xiàn)Activity所持有的Context的作用域最廣亮航,無(wú)所不能。因?yàn)锳ctivity繼承自ContextThemeWrapper匀们,而Application和Service繼承自ContextWrapper,很顯然ContextThemeWrapper在ContextWrapper的基礎(chǔ)上又做了一些操作使得Activity變得更強(qiáng)大泄朴,這里我就不再貼源碼給大家分析了宴猾,有興趣的童鞋可以自己查查源碼。上圖中的YES和NO我也不再做過多的解釋了仇哆,這里我說一下上圖中Application和Service所不推薦的兩種使用情況。
1:如果我們用ApplicationContext去啟動(dòng)一個(gè)LaunchMode為standard的Activity的時(shí)候會(huì)報(bào)錯(cuò)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ù)棧夫植,所以待啟動(dòng)的Activity就找不到棧了。解決這個(gè)問題的方法就是為待啟動(dòng)的Activity指定FLAG_ACTIVITY_NEW_TASK標(biāo)記位详民,這樣啟動(dòng)的時(shí)候就為它創(chuàng)建一個(gè)新的任務(wù)棧延欠,而此時(shí)Activity是以singleTask模式啟動(dòng)的沈跨。所有這種用Application啟動(dòng)Activity的方式不推薦使用由捎,Service同Application饿凛。
2:在Application和Service中去layout inflate也是合法的,但是會(huì)使用系統(tǒng)默認(rèn)的主題樣式涧窒,如果你自定義了某些樣式可能不會(huì)被使用心肪。所以這種方式也不推薦使用纠吴。
一句話總結(jié):凡是跟UI相關(guān)的硬鞍,都應(yīng)該使用Activity做為Context來處理戴已;其他的一些操作,Service,Activity,Application等實(shí)例都可以糖儡,當(dāng)然了伐坏,注意Context引用的持有休玩,防止內(nèi)存泄漏著淆。
如何獲取Context
通常我們想要獲取Context對(duì)象,主要有以下四種方法
1:View.getContext,返回當(dāng)前View對(duì)象的Context對(duì)象永部,通常是當(dāng)前正在展示的Activity對(duì)象独泞。
2:Activity.getApplicationContext,獲取當(dāng)前Activity所在的(應(yīng)用)進(jìn)程的Context對(duì)象苔埋,通常我們使用Context對(duì)象時(shí),要優(yōu)先考慮這個(gè)全局的進(jìn)程Context组橄。
3:ContextWrapper.getBaseContext():用來獲取一個(gè)ContextWrapper進(jìn)行裝飾之前的Context荞膘,可以使用這個(gè)方法,這個(gè)方法在實(shí)際開發(fā)中使用并不多羽资,也不建議使用。
4:Activity.this 返回當(dāng)前的Activity實(shí)例遵班,如果是UI控件需要使用Activity作為Context對(duì)象,但是默認(rèn)的Toast實(shí)際上使用ApplicationContext也可以狭郑。
getApplication()和getApplicationContext()
上面說到獲取當(dāng)前Application對(duì)象用getApplicationContext腹暖,不知道你有沒有聯(lián)想到getApplication()翰萨,這兩個(gè)方法有什么區(qū)別脏答?相信這個(gè)問題會(huì)難倒不少開發(fā)者亩鬼。
程序是不會(huì)騙人的,我們通過上面的代碼辛孵,打印得出兩者的內(nèi)存地址都是相同的丛肮,看來它們是同一個(gè)對(duì)象魄缚。其實(shí)這個(gè)結(jié)果也很好理解,因?yàn)榍懊嬉呀?jīng)說過了冶匹,Application本身就是一個(gè)Context,所以這里獲取getApplicationContext()得到的結(jié)果就是Application本身的實(shí)例咆瘟。那么問題來了,既然這兩個(gè)方法得到的結(jié)果都是相同的袒餐,那么Android為什么要提供兩個(gè)功能重復(fù)的方法呢飞蛹?實(shí)際上這兩個(gè)方法在作用域上有比較大的區(qū)別谤狡。getApplication()方法的語(yǔ)義性非常強(qiáng)卧檐,一看就知道是用來獲取Application實(shí)例的,但是這個(gè)方法只有在Activity和Service中才能調(diào)用的到霉囚。那么也許在絕大多數(shù)情況下我們都是在Activity或者Service中使用Application的捕仔,但是如果在一些其它的場(chǎng)景盈罐,比如BroadcastReceiver中也想獲得Application的實(shí)例榜跌,這時(shí)就可以借助getApplicationContext()方法了盅粪。
publicclassMyReceiverextendsBroadcastReceiver{
@Override
publicvoidonReceive(Contextcontext,Intentintent){
ApplicationmyApp=(Application)context.getApplicationContext();
}
}
Context引起的內(nèi)存泄露
但Context并不能隨便亂用,用的不好有可能會(huì)引起內(nèi)存泄露的問題票顾,下面就示例兩種錯(cuò)誤的引用方式础浮。
錯(cuò)誤的單例模式
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;
}
}
這是一個(gè)非線程安全的單例模式库物,instance作為靜態(tài)對(duì)象霸旗,其生命周期要長(zhǎng)于普通的對(duì)象戚揭,其中也包含Activity,假如Activity A去getInstance獲得instance對(duì)象民晒,傳入this精居,常駐內(nèi)存的Singleton保存了你傳入的Activity A對(duì)象,并一直持有靴姿,即使Activity被銷毀掉,但因?yàn)樗囊眠€存在于一個(gè)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);
}
}
有一個(gè)靜態(tài)的Drawable對(duì)象當(dāng)ImageView設(shè)置這個(gè)Drawable時(shí)维雇,ImageView保存了mDrawable的引用晒他,而ImageView傳入的this是MainActivity的mContext吱型,因?yàn)楸籹tatic修飾的mDrawable是常駐內(nèi)存的陨仅,MainActivity是它的間接引用铝侵,MainActivity被銷毀時(shí),也不能被GC掉触徐,所以造成內(nèi)存泄漏咪鲜。
正確使用Context
一般Context造成的內(nèi)存泄漏锌介,幾乎都是當(dāng)Context銷毀的時(shí)候,卻因?yàn)楸灰脤?dǎo)致銷毀失敗孔祸,而Application的Context對(duì)象可以理解為隨著進(jìn)程存在的隆敢,所以我們總結(jié)出使用Context的正確姿勢(shì):
1:當(dāng)Application的Context能搞定的情況下崔慧,并且生命周期長(zhǎng)的對(duì)象拂蝎,優(yōu)先使用Application的Context惶室。
2:不要讓生命周期長(zhǎng)于Activity的對(duì)象持有到Activity的引用。
3:盡量不要在Activity中使用非靜態(tài)內(nèi)部類皇钞,因?yàn)榉庆o態(tài)內(nèi)部類會(huì)隱式持有外部類實(shí)例的引用悼泌,如果使用靜態(tài)內(nèi)部類,將外部實(shí)例引用作為弱引用持有馆里。
作者:Z_蕭曉
鏈接:http://www.reibang.com/p/b68de4c95b05
來源:簡(jiǎn)書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)可柿,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處鸠踪。