Android Context完全解析,你所不知道的Context的各種細(xì)節(jié)

Context相信所有的Android開(kāi)發(fā)人員基本上每天都在接觸,因?yàn)樗R?jiàn)了。但是這并不代表Context沒(méi)有什么東西好講的,實(shí)際上Context有太多小的細(xì)節(jié)并不被大家所關(guān)注,那么今天我們就來(lái)學(xué)習(xí)一下那些你所不知道的細(xì)節(jié)。

Context類(lèi)型

我們知道,Android應(yīng)用都是使用Java語(yǔ)言來(lái)編寫(xiě)的,那么大家可以思考一下,一個(gè)Android程序和一個(gè)Java程序,他們最大的區(qū)別在哪里?劃分界限又是什么呢?其實(shí)簡(jiǎn)單點(diǎn)分析,Android程序不像Java程序一樣,隨便創(chuàng)建一個(gè)類(lèi),寫(xiě)個(gè)main()方法就能跑了,而是要有一個(gè)完整的Android工程環(huán)境,在這個(gè)環(huán)境下,我們有像Activity、Service、BroadcastReceiver等系統(tǒng)組件,而這些組件并不是像一個(gè)普通的Java對(duì)象new一下就能創(chuàng)建實(shí)例的了,而是要有它們各自的上下文環(huán)境,也就是我們這里討論的Context。可以這樣講,Context是維持Android程序中各組件能夠正常工作的一個(gè)核心功能類(lèi)。

下面我們來(lái)看一下Context的繼承結(jié)構(gòu):

Context的繼承結(jié)構(gòu)還是稍微有點(diǎn)復(fù)雜的,可以看到,直系子類(lèi)有兩個(gè),一個(gè)是ContextWrapper,一個(gè)是ContextImpl。那么從名字上就可以看出,ContextWrapper是上下文功能的封裝類(lèi),而ContextImpl則是上下文功能的實(shí)現(xiàn)類(lèi)。而ContextWrapper又有三個(gè)直接的子類(lèi),ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個(gè)帶主題的封裝類(lèi),而它有一個(gè)直接子類(lèi)就是Activity。

那么在這里我們至少看到了幾個(gè)所比較熟悉的面孔,Activity、Service、還有Application。由此,其實(shí)我們就已經(jīng)可以得出結(jié)論了,Context一共有三種類(lèi)型,分別是Application、Activity和Service。這三個(gè)類(lèi)雖然分別各種承擔(dān)著不同的作用,但它們都屬于Context的一種,而它們具體Context的功能則是由ContextImpl類(lèi)去實(shí)現(xiàn)的。

那么Context到底可以實(shí)現(xiàn)哪些功能呢?這個(gè)就實(shí)在是太多了,彈出Toast、啟動(dòng)Activity、啟動(dòng)Service、發(fā)送廣播、操作數(shù)據(jù)庫(kù)等等等等都需要用到Context。由于Context的具體能力是由ContextImpl類(lèi)去實(shí)現(xiàn)的,因此在絕大多數(shù)場(chǎng)景下,Activity、Service和Application這三種類(lèi)型的Context都是可以通用的。不過(guò)有幾種場(chǎng)景比較特殊,比如啟動(dòng)Activity,還有彈出Dialog。出于安全原因的考慮,Android是不允許Activity或Dialog憑空出現(xiàn)的,一個(gè)Activity的啟動(dòng)必須要建立在另一個(gè)Activity的基礎(chǔ)之上,也就是以此形成的返回棧。而Dialog則必須在一個(gè)Activity上面彈出(除非是System Alert類(lèi)型的Dialog),因此在這種場(chǎng)景下,我們只能使用Activity類(lèi)型的Context,否則將會(huì)出錯(cuò)。

Context數(shù)量

那么一個(gè)應(yīng)用程序中到底有多少個(gè)Context呢?其實(shí)根據(jù)上面的Context類(lèi)型我們就已經(jīng)可以得出答案了。Context一共有Application、Activity和Service三種類(lèi)型,因此一個(gè)應(yīng)用程序中Context數(shù)量的計(jì)算公式就可以這樣寫(xiě):

[plain]view plaincopy

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

上面的1代表著Application的數(shù)量,因?yàn)橐粋€(gè)應(yīng)用程序中可以有多個(gè)Activity和多個(gè)Service,但是只能有一個(gè)Application。

Application Context的設(shè)計(jì)

基本上每一個(gè)應(yīng)用程序都會(huì)有一個(gè)自己的Application,并讓它繼承自系統(tǒng)的Application類(lèi),然后在自己的Application類(lèi)中去封裝一些通用的操作。其實(shí)這并不是Google所推薦的一種做法,因?yàn)檫@樣我們只是把Application當(dāng)成了一個(gè)通用工具類(lèi)來(lái)使用的,而實(shí)際上使用一個(gè)簡(jiǎn)單的單例類(lèi)也可以實(shí)現(xiàn)同樣的功能。但是根據(jù)我的觀察,有太多的項(xiàng)目都是這樣使用Application的。當(dāng)然這種做法也并沒(méi)有什么副作用,只是說(shuō)明還是有不少人對(duì)于Application理解的還有些欠缺。那么這里我們先來(lái)對(duì)Application的設(shè)計(jì)進(jìn)行分析,講一些大家所不知道的細(xì)節(jié),然后再看一下平時(shí)使用Application的問(wèn)題。

首先新建一個(gè)MyApplication并讓它繼承自Application,然后在AndroidManifest.xml文件中對(duì)MyApplication進(jìn)行指定,如下所示:

[html]view plaincopy

android:name=".MyApplication"

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme">

......

指定完成后,當(dāng)我們的程序啟動(dòng)時(shí)Android系統(tǒng)就會(huì)創(chuàng)建一個(gè)MyApplication的實(shí)例,如果這里不指定的話就會(huì)默認(rèn)創(chuàng)建一個(gè)Application的實(shí)例。

前面提到過(guò),現(xiàn)在很多的Application都是被當(dāng)作通用工具類(lèi)來(lái)使用的,那么既然作為一個(gè)通用工具類(lèi),我們要怎樣才能獲取到它的實(shí)例呢?如下所示:

[java]view plaincopy

publicclassMainActivityextendsActivity?{

@Override

protectedvoidonCreate(Bundle?savedInstanceState)?{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

MyApplication?myApp?=?(MyApplication)?getApplication();

Log.d("TAG","getApplication?is?"+?myApp);

}

}

可以看到,代碼很簡(jiǎn)單,只需要調(diào)用getApplication()方法就能拿到我們自定義的Application的實(shí)例了,打印結(jié)果如下所示:

那么除了getApplication()方法,其實(shí)還有一個(gè)getApplicationContext()方法,這兩個(gè)方法看上去好像有點(diǎn)關(guān)聯(lián),那么它們的區(qū)別是什么呢?我們將代碼修改一下:

[java]view plaincopy

publicclassMainActivityextendsActivity?{

@Override

protectedvoidonCreate(Bundle?savedInstanceState)?{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

MyApplication?myApp?=?(MyApplication)?getApplication();

Log.d("TAG","getApplication?is?"+?myApp);

Context?appContext?=?getApplicationContext();

Log.d("TAG","getApplicationContext?is?"+?appContext);

}

}

同樣,我們把getApplicationContext()的結(jié)果打印了出來(lái),現(xiàn)在重新運(yùn)行代碼,結(jié)果如下圖所示:

咦?好像打印出的結(jié)果是一樣的呀,連后面的內(nèi)存地址都是相同的,看來(lái)它們是同一個(gè)對(duì)象。其實(shí)這個(gè)結(jié)果也很好理解,因?yàn)榍懊嬉呀?jīng)說(shuō)過(guò)了,Application本身就是一個(gè)Context,所以這里獲取getApplicationContext()得到的結(jié)果就是MyApplication本身的實(shí)例。

那么有的朋友可能就會(huì)問(wèn)了,既然這兩個(gè)方法得到的結(jié)果都是相同的,那么Android為什么要提供兩個(gè)功能重復(fù)的方法呢?實(shí)際上這兩個(gè)方法在作用域上有比較大的區(qū)別。getApplication()方法的語(yǔ)義性非常強(qiáng),一看就知道是用來(lái)獲取Application實(shí)例的,但是這個(gè)方法只有在Activity和Service中才能調(diào)用的到。那么也許在絕大多數(shù)情況下我們都是在Activity或者Service中使用Application的,但是如果在一些其它的場(chǎng)景,比如BroadcastReceiver中也想獲得Application的實(shí)例,這時(shí)就可以借助getApplicationContext()方法了,如下所示:

[java]view plaincopy

publicclassMyReceiverextendsBroadcastReceiver?{

@Override

publicvoidonReceive(Context?context,?Intent?intent)?{

MyApplication?myApp?=?(MyApplication)?context.getApplicationContext();

Log.d("TAG","myApp?is?"+?myApp);

}

}

也就是說(shuō),getApplicationContext()方法的作用域會(huì)更廣一些,任何一個(gè)Context的實(shí)例,只要調(diào)用getApplicationContext()方法都可以拿到我們的Application對(duì)象。

那么更加細(xì)心的朋友會(huì)發(fā)現(xiàn),除了這兩個(gè)方法之外,其實(shí)還有一個(gè)getBaseContext()方法,這個(gè)baseContext又是什么東西呢?我們還是通過(guò)打印的方式來(lái)驗(yàn)證一下:

哦?這次得到的是不同的對(duì)象了,getBaseContext()方法得到的是一個(gè)ContextImpl對(duì)象。這個(gè)ContextImpl是不是感覺(jué)有點(diǎn)似曾相識(shí)?回去看一下Context的繼承結(jié)構(gòu)圖吧,ContextImpl正是上下文功能的實(shí)現(xiàn)類(lèi)。也就是說(shuō)像Application、Activity這樣的類(lèi)其實(shí)并不會(huì)去具體實(shí)現(xiàn)Context的功能,而僅僅是做了一層接口封裝而已,Context的具體功能都是由ContextImpl類(lèi)去完成的。那么這樣的設(shè)計(jì)到底是怎么實(shí)現(xiàn)的呢?我們還是來(lái)看一下源碼吧。因?yàn)锳pplication、Activity、Service都是直接或間接繼承自ContextWrapper的,我們就直接看ContextWrapper的源碼,如下所示:

[java]view plaincopy

/**

*?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.

*/

publicclassContextWrapperextendsContext?{

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.

*/

protectedvoidattachBaseContext(Context?base)?{

if(mBase?!=null)?{

thrownewIllegalStateException("Base?context?already?set");

}

mBase?=?base;

}

/**

*?@return?the?base?context?as?set?by?the?constructor?or?setBaseContext

*/

publicContext?getBaseContext()?{

returnmBase;

}

@Override

publicAssetManager?getAssets()?{

returnmBase.getAssets();

}

@Override

publicResources?getResources()?{

returnmBase.getResources();

}

@Override

publicContentResolver?getContentResolver()?{

returnmBase.getContentResolver();

}

@Override

publicLooper?getMainLooper()?{

returnmBase.getMainLooper();

}

@Override

publicContext?getApplicationContext()?{

returnmBase.getApplicationContext();

}

@Override

publicString?getPackageName()?{

returnmBase.getPackageName();

}

@Override

publicvoidstartActivity(Intent?intent)?{

mBase.startActivity(intent);

}

@Override

publicvoidsendBroadcast(Intent?intent)?{

mBase.sendBroadcast(intent);

}

@Override

publicIntent?registerReceiver(

BroadcastReceiver?receiver,?IntentFilter?filter)?{

returnmBase.registerReceiver(receiver,?filter);

}

@Override

publicvoidunregisterReceiver(BroadcastReceiver?receiver)?{

mBase.unregisterReceiver(receiver);

}

@Override

publicComponentName?startService(Intent?service)?{

returnmBase.startService(service);

}

@Override

publicbooleanstopService(Intent?name)?{

returnmBase.stopService(name);

}

@Override

publicbooleanbindService(Intent?service,?ServiceConnection?conn,

intflags)?{

returnmBase.bindService(service,?conn,?flags);

}

@Override

publicvoidunbindService(ServiceConnection?conn)?{

mBase.unbindService(conn);

}

@Override

publicObject?getSystemService(String?name)?{

returnmBase.getSystemService(name);

}

......

}

由于ContextWrapper中的方法還是非常多的,我就進(jìn)行了一些篩選,只貼出來(lái)了部分方法。那么上面的這些方法相信大家都是非常熟悉的,getResources()、getPackageName()、getSystemService()等等都是我們經(jīng)常要用到的方法。那么所有這些方法的實(shí)現(xiàn)又是什么樣的呢?其實(shí)所有ContextWrapper中方法的實(shí)現(xiàn)都非常統(tǒng)一,就是調(diào)用了mBase對(duì)象中對(duì)應(yīng)當(dāng)前方法名的方法。

那么這個(gè)mBase對(duì)象又是什么呢?我們來(lái)看第16行的attachBaseContext()方法,這個(gè)方法中傳入了一個(gè)base參數(shù),并把這個(gè)參數(shù)賦值給了mBase對(duì)象。而attachBaseContext()方法其實(shí)是由系統(tǒng)來(lái)調(diào)用的,它會(huì)把ContextImpl對(duì)象作為參數(shù)傳遞到attachBaseContext()方法當(dāng)中,從而賦值給mBase對(duì)象,之后ContextWrapper中的所有方法其實(shí)都是通過(guò)這種委托的機(jī)制交由ContextImpl去具體實(shí)現(xiàn)的,所以說(shuō)ContextImpl是上下文功能的實(shí)現(xiàn)類(lèi)是非常準(zhǔn)確的。

那么另外再看一下我們剛剛打印的getBaseContext()方法,在第26行。這個(gè)方法只有一行代碼,就是返回了mBase對(duì)象而已,而mBase對(duì)象其實(shí)就是ContextImpl對(duì)象,因此剛才的打印結(jié)果也得到了印證。

使用Application的問(wèn)題

雖說(shuō)Application的用法確實(shí)非常簡(jiǎn)單,但是我們平時(shí)的開(kāi)發(fā)工作當(dāng)中也著實(shí)存在著不少Application誤用的場(chǎng)景,那么今天就來(lái)看一看有哪些比較容易犯錯(cuò)的地方是我們應(yīng)該注意的。

Application是Context的其中一種類(lèi)型,那么是否就意味著,只要是Application的實(shí)例,就能隨時(shí)使用Context的各種方法呢?我們來(lái)做個(gè)實(shí)驗(yàn)試一下就知道了:

[java]view plaincopy

publicclassMyApplicationextendsApplication?{

publicMyApplication()?{

String?packageName?=?getPackageName();

Log.d("TAG","package?name?is?"+?packageName);

}

}

這是一個(gè)非常簡(jiǎn)單的自定義Application,我們?cè)贛yApplication的構(gòu)造方法當(dāng)中獲取了當(dāng)前應(yīng)用程序的包名,并打印出來(lái)。獲取包名使用了getPackageName()方法,這個(gè)方法就是由Context提供的。那么上面的代碼能正常運(yùn)行嗎?跑一下就知道了,你將會(huì)看到如下所示的結(jié)果:

應(yīng)用程序一啟動(dòng)就立刻崩潰了,報(bào)的是一個(gè)空指針異常??雌饋?lái)好像挺簡(jiǎn)單的一段代碼,怎么就會(huì)成空指針了呢?但是如果你嘗試把代碼改成下面的寫(xiě)法,就會(huì)發(fā)現(xiàn)一切正常了:

[java]view plaincopy

publicclassMyApplicationextendsApplication?{

@Override

publicvoidonCreate()?{

super.onCreate();

String?packageName?=?getPackageName();

Log.d("TAG","package?name?is?"+?packageName);

}

}

運(yùn)行結(jié)果如下所示:

在構(gòu)造方法中調(diào)用Context的方法就會(huì)崩潰,在onCreate()方法中調(diào)用Context的方法就一切正常,那么這兩個(gè)方法之間到底發(fā)生了什么事情呢?我們重新回顧一下ContextWrapper類(lèi)的源碼,ContextWrapper中有一個(gè)attachBaseContext()方法,這個(gè)方法會(huì)將傳入的一個(gè)Context參數(shù)賦值給mBase對(duì)象,之后mBase對(duì)象就有值了。而我們又知道,所有Context的方法都是調(diào)用這個(gè)mBase對(duì)象的同名方法,那么也就是說(shuō)如果在mBase對(duì)象還沒(méi)賦值的情況下就去調(diào)用Context中的任何一個(gè)方法時(shí),就會(huì)出現(xiàn)空指針異常,上面的代碼就是這種情況。Application中方法的執(zhí)行順序如下圖所示:

Application中在onCreate()方法里去初始化各種全局的變量數(shù)據(jù)是一種比較推薦的做法,但是如果你想把初始化的時(shí)間點(diǎn)提前到極致,也可以去重寫(xiě)attachBaseContext()方法,如下所示:

[java]view plaincopy

publicclassMyApplicationextendsApplication?{

@Override

protectedvoidattachBaseContext(Context?base)?{

//?在這里調(diào)用Context的方法會(huì)崩潰

super.attachBaseContext(base);

//?在這里可以正常調(diào)用Context的方法

}

}

以上是我們平時(shí)在使用Application時(shí)需要注意的一個(gè)點(diǎn),下面再來(lái)介紹另外一種非常普遍的Application誤用情況。

其實(shí)Android官方并不太推薦我們使用自定義的Application,基本上只有需要做一些全局初始化的時(shí)候可能才需要用到自定義Application,官方文檔描述如下:

但是就我的觀察而言,現(xiàn)在自定義Application的使用情況基本上可以達(dá)到100%了,也就是我們平時(shí)自己寫(xiě)測(cè)試demo的時(shí)候可能不會(huì)使用,正式的項(xiàng)目幾乎全部都會(huì)使用自定義Application??墒鞘褂脷w使用,有不少項(xiàng)目對(duì)自定義Application的用法并不到位,正如官方文檔中所表述的一樣,多數(shù)項(xiàng)目只是把自定義Application當(dāng)成了一個(gè)通用工具類(lèi),而這個(gè)功能并不需要借助Application來(lái)實(shí)現(xiàn),使用單例可能是一種更加標(biāo)準(zhǔn)的方式。

不過(guò)自定義Application也并沒(méi)有什么副作用,它和單例模式二選一都可以實(shí)現(xiàn)同樣的功能,但是我見(jiàn)過(guò)有一些項(xiàng)目,會(huì)把自定義Application和單例模式混合到一起使用,這就讓人大跌眼鏡了。一個(gè)非常典型的例子如下所示:

[java]view plaincopy

publicclassMyApplicationextendsApplication?{

privatestaticMyApplication?app;

publicstaticMyApplication?getInstance()?{

if(app?==null)?{

app?=newMyApplication();

}

returnapp;

}

}

就像單例模式一樣,這里提供了一個(gè)getInstance()方法,用于獲取MyApplication的實(shí)例,有了這個(gè)實(shí)例之后,就可以調(diào)用MyApplication中的各種工具方法了。

但是這種寫(xiě)法對(duì)嗎?這種寫(xiě)法是大錯(cuò)特錯(cuò)!因?yàn)槲覀冎繟pplication是屬于系統(tǒng)組件,系統(tǒng)組件的實(shí)例是要由系統(tǒng)來(lái)去創(chuàng)建的,如果這里我們自己去new一個(gè)MyApplication的實(shí)例,它就只是一個(gè)普通的Java對(duì)象而已,而不具備任何Context的能力。有很多人向我反饋使用LitePal時(shí)發(fā)生了空指針錯(cuò)誤其實(shí)都是由于這個(gè)原因,因?yàn)槟闾峁┙oLitePal的只是一個(gè)普通的Java對(duì)象,它無(wú)法通過(guò)這個(gè)對(duì)象來(lái)進(jìn)行Context操作。

那么如果真的想要提供一個(gè)獲取MyApplication實(shí)例的方法,比較標(biāo)準(zhǔn)的寫(xiě)法又是什么樣的呢?其實(shí)這里我們只需謹(jǐn)記一點(diǎn),Application全局只有一個(gè),它本身就已經(jīng)是單例了,無(wú)需再用單例模式去為它做多重實(shí)例保護(hù)了,代碼如下所示:

[java]view plaincopy

publicclassMyApplicationextendsApplication?{

privatestaticMyApplication?app;

publicstaticMyApplication?getInstance()?{

returnapp;

}

@Override

publicvoidonCreate()?{

super.onCreate();

app?=this;

}

}

getInstance()方法可以照常提供,但是里面不要做任何邏輯判斷,直接返回app對(duì)象就可以了,而app對(duì)象又是什么呢?在onCreate()方法中我們將app對(duì)象賦值成this,this就是當(dāng)前Application的實(shí)例,那么app也就是當(dāng)前Application的實(shí)例了。

好了,關(guān)于Context的介紹就到這里吧,內(nèi)容還是比較簡(jiǎn)單易懂的,希望大家通過(guò)這篇文章可以理解Context更多的細(xì)節(jié),并且不要去犯使用Context時(shí)的一些低級(jí)錯(cuò)誤。

喜歡的點(diǎn)個(gè)贊吧。


轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/guolin_blog/article/details/47028975

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容