Context都沒弄明白,還怎么做Android開發(fā)束世?[轉(zhuǎn)]

Activity mActivity =new Activity()
作為Android開發(fā)者酝陈,不知道你有沒有思考過這個(gè)問題,Activity可以new嗎毁涉?Android的應(yīng)用程序開發(fā)采用JAVA語言沉帮,Activity本質(zhì)上也是一個(gè)對象,那上面的寫法有什么問題呢贫堰?估計(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對象創(chuàng)建方式熄云,new一下就能創(chuàng)建實(shí)例了,而是要有它們各自的上下文環(huán)境妙真,也就是我們這里討論的Context缴允。可以這樣講珍德,Context是維持Android程序中各組件能夠正常工作的一個(gè)核心功能類练般。
Context到底是什么
Context的中文翻譯為:語境; 上下文; 背景; 環(huán)境矗漾,在開發(fā)中我們經(jīng)常說稱之為“上下文”,那么這個(gè)“上下文”到底是指什么意思呢薄料?在語文中敞贡,我們可以理解為語境,在程序中都办,我們可以理解為當(dāng)前對象在程序中所處的一個(gè)環(huán)境嫡锌,一個(gè)與系統(tǒng)交互的過程。比如微信聊天琳钉,此時(shí)的“環(huán)境”是指聊天的界面以及相關(guān)的數(shù)據(jù)請求與傳輸,Context在加載資源蛛倦、啟動(dòng)Activity歌懒、獲取系統(tǒng)服務(wù)、創(chuàng)建View等操作都要參與溯壶。
那Context到底是什么呢及皂?一個(gè)Activity就是一個(gè)Context,一個(gè)Service也是一個(gè)Context且改。Android程序員把“場景”抽象為Context類验烧,他們認(rèn)為用戶和操作系統(tǒng)的每一次交互都是一個(gè)場景,比如打電話又跛、發(fā)短信碍拆,這些都是一個(gè)有界面的場景,還有一些沒有界面的場景慨蓝,比如后臺(tái)運(yùn)行的服務(wù)(Service)感混。一個(gè)應(yīng)用程序可以認(rèn)為是一個(gè)工作環(huán)境,用戶在這個(gè)環(huán)境中會(huì)切換到不同的場景礼烈,這就像一個(gè)前臺(tái)秘書弧满,她可能需要接待客人,可能要打印文件此熬,還可能要接聽客戶電話庭呜,而這些就稱之為不同的場景,前臺(tái)秘書可以稱之為一個(gè)應(yīng)用程序犀忱。
如何生動(dòng)形象的理解Context
上面的概念中采用了通俗的理解方式募谎,將Context理解為“上下文”或者“場景”,如果你仍然覺得很抽象峡碉,不好理解近哟。在這里我給出一個(gè)可能不是很恰當(dāng)?shù)谋扔鳎M兄诖蠹业睦斫猓阂粋€(gè)Android應(yīng)用程序鲫寄,可以理解為一部電影或者一部電視劇吉执,Activity疯淫,Service,Broadcast Receiver戳玫,Content Provider這四大組件就好比是這部戲里的四個(gè)主角:胡歌熙掺,霍建華,詩詩咕宿,Baby币绩。他們是由劇組(系統(tǒng))一開始就定好了的,整部戲就是由這四位主演領(lǐng)銜擔(dān)綱的府阀,所以這四位主角并不是大街上隨隨便便拉個(gè)人(new 一個(gè)對象)都能演的缆镣。有了演員當(dāng)然也得有攝像機(jī)拍攝啊,他們必須通過鏡頭(Context)才能將戲傳遞給觀眾试浙,這也就正對應(yīng)說四大組件(四位主角)必須工作在Context環(huán)境下(攝像機(jī)鏡頭)董瞻。那Button,TextView田巴,LinearLayout這些控件呢钠糊,就好比是這部戲里的配角或者說群眾演員,他們顯然沒有這么重用壹哺,隨便一個(gè)路人甲路人乙都能演(可以new一個(gè)對象)抄伍,但是他們也必須要面對鏡頭(工作在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)境全局信息的接口。它是一個(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)類咯浊洞,我們在Context的源碼中通過IDE可以查看到他的子類最終可以得到如下關(guān)系圖:


Context.png

Context類本身是一個(gè)純abstract類牵敷,它有兩個(gè)具體的實(shí)現(xiàn)子類:ContextImpl和ContextWrapper。其中ContextWrapper類法希,如其名所言枷餐,這只是一個(gè)包裝而已,ContextWrapper構(gòu)造函數(shù)中必須包含一個(gè)真正的Context引用苫亦,同時(shí)ContextWrapper中提供了attachBaseContext()用于給ContextWrapper對象中指定真正的Context對象毛肋,調(diào)用ContextWrapper的方法都會(huì)被轉(zhuǎn)向其所包含的真正的Context對象。ContextThemeWrapper類屋剑,如其名所言润匙,其內(nèi)部包含了與主題(Theme)相關(guān)的接口,這里所說的主題就是指在AndroidManifest.xml中通過android:theme為Application元素或者Activity元素指定的主題唉匾。當(dāng)然趁桃,只有Activity才需要主題,Service是不需要主題的肄鸽,因?yàn)镾ervice是沒有界面的后臺(tái)場景,所以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對象,由ContextImpl實(shí)現(xiàn)Context中的方法冻河。
一個(gè)應(yīng)用程序有幾個(gè)Context
其實(shí)這個(gè)問題本身并沒有什么意義箍邮,關(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ì)心,可能會(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ù)庫等等都需要用到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ù)場景下扣癣,Activity惰帽、Service和Application這三種類型的Context都是可以通用的。不過有幾種場景比較特殊父虑,比如啟動(dòng)Activity该酗,還有彈出Dialog。出于安全原因的考慮频轿,Android是不允許Activity或Dialog憑空出現(xiàn)的垂涯,一個(gè)Activity的啟動(dòng)必須要建立在另一個(gè)Activity的基礎(chǔ)之上,也就是以此形成的返回棧航邢。而Dialog則必須在一個(gè)Activity上面彈出(除非是System Alert類型的Dialog)耕赘,因此在這種場景下,我們只能使用Activity類型的Context膳殷,否則將會(huì)出錯(cuò)操骡。

Context作用域.png

從上圖我們可以發(fā)現(xiàn)Activity所持有的Context的作用域最廣九火,無所不能。因?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對象,主要有以下四種方法1:View.getContext,返回當(dāng)前View對象的Context對象麻养,通常是當(dāng)前正在展示的Activity對象褐啡。2:Activity.getApplicationContext,獲取當(dāng)前Activity所在的(應(yīng)用)進(jìn)程的Context對象,通常我們使用Context對象時(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對象莉恼,但是默認(rèn)的Toast實(shí)際上使用ApplicationContext也可以。
getApplication()和getApplicationContext()
上面說到獲取當(dāng)前Application對象用getApplicationContext,不知道你有沒有聯(lián)想到getApplication()俐银,這兩個(gè)方法有什么區(qū)別尿背?相信這個(gè)問題會(huì)難倒不少開發(fā)者。

getApplication()&getApplicationContext().png

程序是不會(huì)騙人的捶惜,我們通過上面的代碼田藐,打印得出兩者的內(nèi)存地址都是相同的,看來它們是同一個(gè)對象吱七。其實(shí)這個(gè)結(jié)果也很好理解汽久,因?yàn)榍懊嬉呀?jīng)說過了,Application本身就是一個(gè)Context陪捷,所以這里獲取getApplicationContext()得到的結(jié)果就是Application本身的實(shí)例回窘。那么問題來了,既然這兩個(gè)方法得到的結(jié)果都是相同的市袖,那么Android為什么要提供兩個(gè)功能重復(fù)的方法呢啡直?實(shí)際上這兩個(gè)方法在作用域上有比較大的區(qū)別。getApplication()方法的語義性非常強(qiáng)苍碟,一看就知道是用來獲取Application實(shí)例的酒觅,但是這個(gè)方法只有在Activity和Service中才能調(diào)用的到。那么也許在絕大多數(shù)情況下我們都是在Activity或者Service中使用Application的微峰,但是如果在一些其它的場景舷丹,比如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)對象,其生命周期要長于普通的對象早芭,其中也包含Activity彼城,假如Activity A去getInstance獲得instance對象,傳入this退个,常駐內(nèi)存的Singleton保存了你傳入的Activity A對象募壕,并一直持有,即使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對象當(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對象可以理解為隨著進(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)部類會(huì)隱式持有外部類實(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/47028975http://blog.csdn.net/yanbober/article/details/45967639

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末傲醉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子呻率,更是在濱河造成了極大的恐慌需频,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筷凤,死亡現(xiàn)場離奇詭異,居然都是意外死亡苞七,警方通過查閱死者的電腦和手機(jī)藐守,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蹂风,“玉大人卢厂,你說我怎么就攤上這事』葑模” “怎么了慎恒?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵任内,是天一觀的道長。 經(jīng)常有香客問我融柬,道長死嗦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任粒氧,我火速辦了婚禮越除,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘外盯。我一直安慰自己摘盆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布饱苟。 她就那樣靜靜地躺著孩擂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪箱熬。 梳的紋絲不亂的頭發(fā)上类垦,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音坦弟,去河邊找鬼护锤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛酿傍,可吹牛的內(nèi)容都是我干的烙懦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赤炒,長吁一口氣:“原來是場噩夢啊……” “哼氯析!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起莺褒,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤掩缓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后遵岩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體你辣,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年尘执,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了舍哄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡誊锭,死狀恐怖表悬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情丧靡,我是刑警寧澤蟆沫,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布籽暇,位于F島的核電站,受9級(jí)特大地震影響饭庞,放射性物質(zhì)發(fā)生泄漏戒悠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一但绕、第九天 我趴在偏房一處隱蔽的房頂上張望救崔。 院中可真熱鬧,春花似錦捏顺、人聲如沸六孵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽劫窒。三九已至,卻和暖如春拆座,著一層夾襖步出監(jiān)牢的瞬間主巍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工挪凑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留孕索,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓躏碳,卻偏偏與公主長得像搞旭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子菇绵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

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