Android上下文Context全解析

1. 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.
 */

以上是Android源碼對Context的描述:

  • 它是應用程序環(huán)境的全局信息的接口质礼。
  • 這是一個抽象類膳帕,由Android系統(tǒng)提供履腋。
  • 它允許訪問特定于應用程序的資源和類,以及調(diào)用應用程序級操作矮瘟,如啟動活動,廣播和接收意圖等塑娇。

綜上:Context的第一個作用是獲取應用程序的資源和類澈侠,第二個作用是調(diào)用應用程序級操作,比如啟動活動埋酬、廣播和接收意圖等哨啃。

補充:理解以上內(nèi)容的朋友,下面的內(nèi)容可以忽略写妥。下面的內(nèi)容都是對Context概念和作用有一個更好的理解所做的補充拳球。

2. 如何理解Context是一個環(huán)境

Context是維持Android程序各組件能夠正常工作的一個核心功能類,而這個核心功能類相當于一個大的環(huán)境珍特,只有在這個環(huán)境下祝峻,Android的資源才能被獲取以及Android的各項組件才能被調(diào)用。

3. 如何理解Context環(huán)境下各項組件才能被調(diào)用

Android雖然采用java語言開發(fā)扎筒,但是Android程序并非和java程序那樣使用main()方法就可以運行莱找。Android應用模型是基于組件的應用設計模式,組件的運行一定要有一個完整的Android環(huán)境嗜桌。在這個環(huán)境下:Actvity奥溺、Service、BroadCast症脂、ContentProvider才可以正常創(chuàng)建和運行谚赎。所以這些組件并不能按照java對象創(chuàng)建方式,new一下就能創(chuàng)建實例诱篷!而是需要一個環(huán)境壶唤,而這個環(huán)境就是我們所需要了解的Context。

通俗的方式來說:Android應用程序就像一部電影棕所,Android的四大組件相當于電影的四大主角闸盔,而Context就相當于是攝像鏡頭,只有通過攝像鏡頭我們才能看到四大主角琳省。主角是被內(nèi)定好的迎吵,不能隨便new出來躲撰。而其他跑龍?zhí)椎牟⒉皇莾?nèi)定的,也就是說不是那么重要击费,他們可以被new出來拢蛋,但是他們也需要通過攝像鏡頭才可以看到,所以才有了Button mButton=new Button(Context)蔫巩。

4. Context作用的具體體現(xiàn)

有了Context這個環(huán)境谆棱,Android組件才可以正常被創(chuàng)建和調(diào)用,所以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);

5. Context的繼承結(jié)構(gòu)

image
  • 從繼承結(jié)構(gòu)可以看出:Context有兩個子類:ContextWrapper和ContextImpl圆仔。
  • 從名稱上可以看出:ContextWrapper是Context的封裝類垃瞧;ContextImpl是Context的實現(xiàn)類。
  • ContextWrapper有三個子類:Application坪郭、Service和ContextThemeWrapper个从。
  • ContextThemeWrapper是一個帶主題的封裝類,它的直接子類就是Activity歪沃。
  • Context環(huán)境一共有三種類型:Activity嗦锐、Service和Application,它們承擔不同的作用绸罗,而具體Context功能由ContextImpl類實現(xiàn)意推。

綜上:

  • Activity、Service和Application這三種類型的Context珊蟀,在多數(shù)情況下菊值,都是可以通用的,而為了安全考慮育灸,在某些情況下腻窒,是不可以通用的,諸如啟動Activity磅崭、彈出Dialog等儿子。一個Activity的啟動必須建立在另一個Actvity的基礎(chǔ)上;Dialog也必須在Activity上面彈出砸喻,這些情況下柔逼,也只能使用Activity的Context

  • 也就是說:凡是跟UI相關(guān)的,都應該使用Activity做為Context來處理割岛;其他的一些操作愉适,Service,Activity,Application等實例都可以,如下圖所示:


    image

6. Context的數(shù)量

Context數(shù)量 = Activity數(shù)量 + Service數(shù)量 + 1(Application)

7. Application入口類作為工具類癣漆,這種做法是否可行维咸?

強力不推薦!Application入口類的職責就是為了初始化!根據(jù)面向?qū)ο蟮膯我宦氊熢瓌t癌蓖,Application不推薦作為工具類使用瞬哼。

8. getApplication()、getApplicationContext()和getBaseContext()的關(guān)系

我們在Activity中獲取分別打印這個三個方法:

MyApplication myApp = (MyApplication) getApplication();  

Context appContext = getApplicationContext();  

Context baseContext = getBaseContext();

打印結(jié)果:

getApplication::::com.beidou.mvptest.MyApplication@53502fac

getApplicationContext::::com.beidou.mvptest.MyApplication@53502fac

baseContext::::android.app.ContextImpl@53505ce4

結(jié)論:

  • getApplication和getApplicationContext得到的是一個對象MyApplication租副。
  • getBaseContext得到的是ContextImpl坐慰。

疑問:

  • 第一問:getApplication和getApplicationContext得到的對象是一樣的,那為何設計兩個方法呢用僧?
  • 答:兩者范圍不同讨越,后者比前者適用范圍更廣。getApplication只適用于Activity和Service永毅,而getApplicationContext還用于其他場景,比如BroadcastReceiver中人弓。
  • 第二問:ContextImpl是什么東東沼死?
  • 答:

①:ContextImpl是Context功能的實現(xiàn)類。Application和Service崔赌、Activity并不會去實現(xiàn)Context的功能意蛀,只是做了接口的封裝,具體的功能由ContextImpl完成健芭。

②:因為Application县钥、Activity、Service都是直接或間接繼承自ContextWrapper的慈迈,我們就直接看ContextWrapper的源碼若贮,就會發(fā)現(xiàn)所有ContextWrapper中方法的實現(xiàn)都非常統(tǒng)一,就是調(diào)用了mBase對象中對應當前方法名的方法痒留。

③:那么這個mBase對象又是什么呢谴麦?我們來看第16行的attachBaseContext()方法,這個方法中傳入了一個base參數(shù)伸头,并把這個參數(shù)賦值給了mBase對象匾效。而attachBaseContext()方法其實是由系統(tǒng)來調(diào)用的,它會把ContextImpl對象作為參數(shù)傳遞到attachBaseContext()方法當中恤磷,從而賦值給mBase對象面哼,之后ContextWrapper中的所有方法其實都是通過這種委托的機制交由ContextImpl去具體實現(xiàn)的,所以說ContextImpl是上下文功能的實現(xiàn)類扫步。

④:再看一下我們剛剛打印的getBaseContext()方法魔策,在第26行。這個方法只有一行代碼锌妻,就是返回了mBase對象而已代乃,而mBase對象其實就是ContextImpl對象,因此剛才的打印結(jié)果也得到了印證。

/** 
 * Proxying implementation of Context that simply delegates all of its calls to 
 * another Context.  Can be subclassed to modify behavior without changing 
 * the original Context. 
 */  
public class ContextWrapper extends Context {  
    Context mBase;  
      
    /** 
     * Set the base context for this ContextWrapper.  All calls will then be 
     * delegated to the base context.  Throws 
     * IllegalStateException if a base context has already been set. 
     *  
     * @param base The new base context for this wrapper. 
     */  
    protected void attachBaseContext(Context base) {  
        if (mBase != null) {  
            throw new IllegalStateException("Base context already set");  
        }  
        mBase = base;  
    }  
  
    /** 
     * @return the base context as set by the constructor or setBaseContext 
     */  
    public Context getBaseContext() {  
        return mBase;  
    }  
  
    @Override  
    public AssetManager getAssets() {  
        return mBase.getAssets();  
    }  
  
    @Override  
    public Resources getResources() {  
        return mBase.getResources();  
    }  
  
    @Override  
    public ContentResolver getContentResolver() {  
        return mBase.getContentResolver();  
    }  
  
    @Override  
    public Looper getMainLooper() {  
        return mBase.getMainLooper();  
    }  
      
    @Override  
    public Context getApplicationContext() {  
        return mBase.getApplicationContext();  
    }  
  
    @Override  
    public String getPackageName() {  
        return mBase.getPackageName();  
    }  
  
    @Override  
    public void startActivity(Intent intent) {  
        mBase.startActivity(intent);  
    }  
      
    @Override  
    public void sendBroadcast(Intent intent) {  
        mBase.sendBroadcast(intent);  
    }  
  
    @Override  
    public Intent registerReceiver(  
        BroadcastReceiver receiver, IntentFilter filter) {  
        return mBase.registerReceiver(receiver, filter);  
    }  
  
    @Override  
    public void unregisterReceiver(BroadcastReceiver receiver) {  
        mBase.unregisterReceiver(receiver);  
    }  
  
    @Override  
    public ComponentName startService(Intent service) {  
        return mBase.startService(service);  
    }  
  
    @Override  
    public boolean stopService(Intent name) {  
        return mBase.stopService(name);  
    }  
  
    @Override  
    public boolean bindService(Intent service, ServiceConnection conn,  
            int flags) {  
        return mBase.bindService(service, conn, flags);  
    }  
  
    @Override  
    public void unbindService(ServiceConnection conn) {  
        mBase.unbindService(conn);  
    }  
  
    @Override  
    public Object getSystemService(String name) {  
        return mBase.getSystemService(name);  
    }  
  
    ......  
}  

9. 使用Application出現(xiàn)的問題和解決辦法

9.1 問題一:在Application的構(gòu)造方法中去獲取Context實現(xiàn)類的各種方法

public class MyApplication extends Application {  
      
    public MyApplication() {  
        String packageName = getPackageName();  
        Log.d("TAG", "package name is " + packageName);  
    }  
      
}

運行結(jié)果:

空指針:

java.lang.RuntimeException: Unable to instantiate application 

com.example.test.MyApplication: java.lang.NullPointerException

修改代碼如下:

public class MyApplication extends Application {  
      
    @Override  
    public void onCreate() {  
        super.onCreate();  
        String packageName = getPackageName();  
        Log.d("TAG", "package name is " + packageName);  
    }  
      
}  

運行結(jié)果正常搁吓!發(fā)生了什么事情呢原茅?回顧ContextWrapper源碼,ContextWrapper中有一個attachBaseContext()方法堕仔,這個方法會將傳入的一個Context參數(shù)賦值給mBase對象擂橘,之后mBase對象就有值了。而我們又知道摩骨,所有Context的方法都是調(diào)用這個mBase對象的同名方法通贞,那么也就是說如果在mBase對象還沒賦值的情況下就去調(diào)用Context中的任何一個方法時,就會出現(xiàn)空指針異常恼五。Application中方法的執(zhí)行順序:


image

在onCreate()方法中去初始化全局的變量數(shù)據(jù)是一種比較推薦的做法昌罩,假如你想把初始化提前到極致,也可以重寫attachBaseContext()方法:

public class MyApplication extends Application {  
      
    @Override  
    protected void attachBaseContext(Context base) {  
        // 在這里調(diào)用Context的方法會崩潰  
        super.attachBaseContext(base);  
        // 在這里調(diào)用Context的方法就沒問題
    }  
}  

9.2 問題二:把Application當做工具類使用時獲取實例采用new的方式

public class MyApplication extends Application {  
      
    private static MyApplication app;  
      
    public static MyApplication getInstance() {  
        if (app == null) {  
            app = new MyApplication();  
        }  
        return app;  
    }  
}  

我們已經(jīng)在上文指出了:new MyApplication實例的方式灾馒,得到的對象并不具備Context的能力茎用,如果進行Context操作就會報空指針,因為它只是一個java對象睬罗。而我們知道Application本身就是一個單例了轨功,所以我們直接返回本身即可,不用再去new對象獲取實例容达,否則弄巧成拙古涧。

public class MyApplication extends Application {  
      
    private static MyApplication app;  
      
    public static MyApplication getInstance() {  
        return app;  
    }  
      
    @Override  
    public void onCreate() {  
        super.onCreate();  
        app = this;  
    }  
      
}  

10. Context亂用導致的內(nèi)存泄漏的問題和解決辦法

public class CustomManager  
{  
    private static CustomManager sInstance;  
    private Context mContext;  
  
    private CustomManager(Context context)  
    {  
        this.mContext = context;  
    }  
  
    public static synchronized CustomManager getInstance(Context context)  
    {  
        if (sInstance == null)  
        {  
            sInstance = new CustomManager(context);  
        }  
        return sInstance;  
    }  
      
    //some methods   
    private void someOtherMethodNeedContext()  
    {  
          
    }  
}  

假如:sInstance作為靜態(tài)對象,其生命周期要長于普通的對象花盐,其中也包含Activity羡滑,假如Activity A去getInstance獲得sInstance對象,傳入this算芯,常駐內(nèi)存的CustomManager保存了你傳入的ActivityA 對象(也就是Context)啄栓,并一直持有,即使Activity(Context)被銷毀掉也祠,但因為它的引用還存在于一個CustomManager中昙楚,就不可能被GC掉,這樣就導致了內(nèi)存泄漏诈嘿。也就是所以解決辦法是:

public static synchronized CustomManager getInstance(Context context)  
    {  
        if (sInstance == null)  
        {  
            sInstance = new CustomManager(context.getApplicationContext());  
        }  
        return sInstance;  
    }  

11. 正確使用Context

一般Context造成的內(nèi)存泄漏堪旧,幾乎都是當Context銷毀的時候,卻因為被引用導致銷毀失敗奖亚,而Application的Context對象可以理解為隨著進程存在的淳梦,所以我們總結(jié)出使用Context的正確姿勢:

  • 當Application的Context能搞定的情況下,并且生命周期長的對象昔字,優(yōu)先使用Application的Context爆袍。

  • 不要讓生命周期長于Activity的對象持有到Activity的引用首繁。

  • 盡量不要在Activity中使用非靜態(tài)內(nèi)部類,因為非靜態(tài)內(nèi)部類會隱式持有外部類實例的引用陨囊,如果使用靜態(tài)內(nèi)部類弦疮,將外部實例引用作為弱引用持有。

12. 參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜘醋,一起剝皮案震驚了整個濱河市胁塞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌压语,老刑警劉巖啸罢,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異胎食,居然都是意外死亡扰才,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門厕怜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來训桶,“玉大人,你說我怎么就攤上這事酣倾。” “怎么了谤专?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵躁锡,是天一觀的道長。 經(jīng)常有香客問我置侍,道長映之,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任蜡坊,我火速辦了婚禮杠输,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秕衙。我一直安慰自己蠢甲,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布据忘。 她就那樣靜靜地躺著鹦牛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勇吊。 梳的紋絲不亂的頭發(fā)上曼追,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音汉规,去河邊找鬼礼殊。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的晶伦。 我是一名探鬼主播碟狞,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坝辫!你這毒婦竟也來了篷就?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤近忙,失蹤者是張志新(化名)和其女友劉穎竭业,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體及舍,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡未辆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了锯玛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咐柜。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖攘残,靈堂內(nèi)的尸體忽然破棺而出拙友,到底是詐尸還是另有隱情,我是刑警寧澤歼郭,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布遗契,位于F島的核電站,受9級特大地震影響病曾,放射性物質(zhì)發(fā)生泄漏牍蜂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一泰涂、第九天 我趴在偏房一處隱蔽的房頂上張望鲫竞。 院中可真熱鬧,春花似錦逼蒙、人聲如沸从绘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顶考。三九已至,卻和暖如春妖泄,著一層夾襖步出監(jiān)牢的瞬間驹沿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工蹈胡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留渊季,地道東北人朋蔫。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像却汉,于是被迫代替她去往敵國和親驯妄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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