前言
很高興遇見你~ 歡迎閱讀我的文章。
在文章Android全面解析之由淺及深Handler消息機制中討論到,Handler可以:
避免我們自己去手動寫 死循環(huán)和輸入阻塞 來不斷獲取用戶的輸入以及避免線程直接結(jié)束,而是采用事務(wù)驅(qū)動型設(shè)計,使用Handler消息機制,讓AMS可以控制整個程序的運行邏輯。
這是關(guān)于android程序在設(shè)計上更加重要的一部分,不太了解的讀者可以前往閱讀了解一下。而當(dāng)我們知道android程序的程序是通過main方法跑起來的,然后通過handler機制來控制程序的運行,那么四大組件和普通的Java類到底有什么區(qū)別?為什么同樣是Java類,而ActivityThread、Activity等等這些類就顯得那么特殊呢?我們的代碼、寫的布局是通過什么路徑使用系統(tǒng)資源把界面展示在屏幕上的?這一切就涉及到我們今天的主角:Context。
什么是Context
回想一下最初學(xué)習(xí)Android開發(fā)的時候,第一用到context是什么時候?如果你跟我一樣是通過郭霖的《第一行代碼》來入門android,那么一般是Toast。Toast的常規(guī)用法是:
Toast.makeText(this, "我是toast", Toast.LENGTH_SHORT).show()
當(dāng)初也不知道什么是Context,只知道他需要一個context類型,把activity對象傳進去即可。從此context貫穿在我開發(fā)過程的方方面面,但我始終不知道這個context到底有什么用?為什么要這個對象?我們首先來看官方對于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 {...}
關(guān)于應(yīng)用程序環(huán)境的全局信息的接口。 這是一個抽象類,它的實現(xiàn)是由Android系統(tǒng)提供。 它允許訪問特定應(yīng)用的資源和類,以及向上調(diào)用應(yīng)用程序級的操作,如啟動活動,廣播和接收Intent等。
可以看到Context最重要的作用就是獲取全局消息、訪問系統(tǒng)資源、調(diào)用應(yīng)用程序級的操作??赡軐τ谶@些作用沒什么印象,想一下,如果沒有context,我們?nèi)绾巫龅揭韵虏僮鳎?/p>
- 彈出一個toast
- 啟動一個activity
- 獲取程序布局文件、drawable文件等
- 訪問數(shù)據(jù)庫
這些平時看似簡單的操作,一旦失去了context將無法執(zhí)行。這些行為都有一個共同點:需要與系統(tǒng)交匯。四大組件為什么配為組件,而我們的寫的就只能叫做一個普通的Java類,正是因為context的這些功能讓四大組件有了不一樣的能力。簡單來說,context是:
應(yīng)用程序和系統(tǒng)之間的橋梁,應(yīng)用程序訪問系統(tǒng)各種資源的接口。
我們一般使用context最多的是兩種情景:直接調(diào)用context的方法和調(diào)用接口時需要context參數(shù)。這些行為都意味著我們需要訪問系統(tǒng)相關(guān)的資源。
那context是從哪里來的?AMS!AMS是系統(tǒng)級進程,擁有訪問系統(tǒng)級操作的權(quán)利,應(yīng)用程序的啟動受AMS的調(diào)控,在程序啟動的過程中,AMS會把一個“憑證”通過跨進程通信給到我們的應(yīng)用程序,我們的程序會把這個“憑證”封裝成context,并提供一系列的接口,這樣我們的程序也就可以很方便地訪問系統(tǒng)資源了。這樣的好處是:
系統(tǒng)可以對應(yīng)用程序級的操作進行調(diào)控,限制各種情景下的權(quán)限,同時也可以防止惡意攻擊。
如Application類的context和Activity的context權(quán)利是不一樣的,生命周期也不一樣。對于想要操作系統(tǒng)攻擊用戶的程序也進行了阻止,沒有獲得允許的Java類沒有任何權(quán)利,而Activity開放給用戶也只有部分有限的權(quán)利。而我們開發(fā)者獲取context的路徑,也只有從activity、application等組件獲取。
因而,什么是Context?Context是應(yīng)用程序與系統(tǒng)之間溝通的橋梁,是應(yīng)用程序訪問系統(tǒng)資源的接口,同時也是系統(tǒng)給應(yīng)用程序的一張“權(quán)限憑證”。有了context,一個Java類才可以被稱之為組件。
Context家族
上一部分我們了解什么是context以及context的重要性,這一部分就來了解一下context在源碼中的子類繼承情況。先看一個圖:

最頂層是Context抽象類,他定義了一系列與系統(tǒng)交匯的接口。ContextWrapper繼承自Context,但是并沒有真正實現(xiàn)Context中的接口,而是把接口的實現(xiàn)都托管給ContextImpl,ContextImpl是Context接口的真正實現(xiàn)者,從AMS拿來的“憑證”也是封裝到了ContextImpl中,然后賦值給ContextWrapper,這里運用到了一種模式:裝飾者模式。Application和Service都繼承自ContextWrapper,那么他們也就擁有Context的接口方法且本身即是context,方便開發(fā)者的使用。Activity比較特殊,因為它是有界面的,所以他需要一個主題:Theme,ContextThemeWrapper在ContextWrapper的基礎(chǔ)上增加與主題相關(guān)的操作。
這樣的設(shè)計有這樣的優(yōu)點:
- Activity等可以更加方便地使用context,可以把自身當(dāng)成context來使用,遇到需要context的接口直接把自身傳進去即可。
- 運用裝飾者模式,向外屏蔽ContextImpl的內(nèi)部邏輯,同時當(dāng)需要更改ContextImpl的邏輯實現(xiàn),ContextWrapper的邏輯幾乎不需要更改。
- 更方便地擴展不同情景下的邏輯。如service和activity,情景不同,需要的接口方法也不同,但是與系統(tǒng)交互的接口是相同的,使用裝飾者模式可以拓展出很多的功能,同時只需要把ContextImpl對象賦值進去即可。
context的分類
前面講到Context的家族體系時,了解到他的最終實現(xiàn)類有:Application、Activity、Service,ContextImpl被前三者持有,是Context接口的真正實現(xiàn)。那么這里討論一下這三者有什么不同,和使用時需要注意的問題。
Application
Application是全局Context,整個應(yīng)用程序只有一個,他可以訪問到應(yīng)用程序的包信息等資源信息。獲取Application的方法一般有兩個:
context.getApplicationContext()
activity.getApplication()
通過context和activity都可以獲取到Application,那這兩個方法有什么區(qū)別?沒有區(qū)別。我們可以打印來看一下:
override fun onCreate(savedInstanceState: Bundle?) {
...
Log.d("一只修仙的猿", "application:$application")
Log.d("一只修仙的猿", "applicationContext:$applicationContext")
}

可以看到確實是同個對象。但為什么要提供兩個一樣作用的方法?getApplication()方法更加直觀,但是只能在activity中調(diào)用。getApplicationContext()適用范圍更廣,任意一個context對象皆可以調(diào)用此方法。
Application類的Context的特點是生命周期長,在整個應(yīng)用程序運行的期間他都會存在。同時我們可以自定義Application,并在里面做一些全局的初始化操作,或者寫一個靜態(tài)的context供給全局獲取,不需要在方法中傳入context。如:
class MyApplication : Application(){
// 全局context
companion object{
lateinit var context: Context
}
override fun onCreate() {
super.onCreate()
// 做全局初始化操作
RetrofitManager.init(this)
context = this
}
}
這樣我們就可以在應(yīng)用啟動的時候?qū)σ恍┙M件進行初始化,同時可以通過MyApplication.context來獲取Application對象。
但是!??!請不要把Application當(dāng)成工具類使用。由于Application獲取的便利性,有開發(fā)者會在Application中編寫一些工具方法,全局獲取使用,這樣是不行的。自定義Application的目的是在程序啟動的時候做全局初始化工作,而不能拿來取代工具類,這嚴重違背谷歌設(shè)計Application的原則,也違背Java代碼規(guī)范的單一職責(zé)原則。
四大組件
Activity繼承自ContextThemeWrapper,是一個擁有主題的context對象。Activity常用于與UI有關(guān)的操作,如添加window等。常規(guī)使用可以直接用activity.this。
Service繼承自ContextWrapper,也可以和Activity一樣直接使用service.this來使用context。和activity不同的是,Service沒有界面,所以也不需要主題。
ContextProvider使用的是Application的context,Broadcast使用的是activity的context,這兩點在后面會進行源碼分析。
BaseContext
嗯?baseContext是什么?把這個拿出來單獨講,細心的讀者可能會發(fā)現(xiàn)activity中有一個方法:getBaseContext。這個是ContextWrapper中的mBase對象,也就是ContextImpl,也是context接口的真正邏輯實現(xiàn)。
context的使用問題
使用context最重要的問題之一是注意內(nèi)存泄露。不同的context的生命周期不同,Application是在應(yīng)用存在的期間會一直存在,而Activity是會隨著界面的銷毀而銷毀,如果當(dāng)我們的代碼長時間持有了activity的context,如靜態(tài)引用或者單例類,那么會導(dǎo)致activity無法被釋放。如下面的代碼:
object MyClass {
lateinit var mContext : Context
fun showToast(context : Context){
mContext = context
}
}
單例類在應(yīng)用持續(xù)的時間都會一直存在,這樣context也就會被一直被持有,activity無法被回收,導(dǎo)致內(nèi)存泄露。
那,我們就都換成Application不就可以了,如下:
object MyClass {
lateinit var mContext : Context
fun showToast(context : Context){
mContext = context.applicationContext
}
}
答案是:不可以。什么時候可以使用Application?不涉及UI以及啟動Activity操作。Activity的context是擁有主題屬性的,如果使用Application來操作UI,那么會丟失自定義的主題,采用系統(tǒng)默認的主題。同時,有些UI操作只有Activity可以執(zhí)行,如彈出dialog,這涉及到window的token問題,我在這篇文章token驗證進行了詳細的解答,有興趣的讀者可以去閱讀一下。這也是官方對于context不同權(quán)限的設(shè)計,沒有界面的context,就不應(yīng)該有操作界面的權(quán)利。使用Application啟動的Activity必須指定task以及標記為singleTask,因為Application是沒有任務(wù)棧的,需要重新開一個新的任務(wù)棧。因此,我們需要根據(jù)不同context的不同職責(zé)來執(zhí)行不同的任務(wù)。
Context的創(chuàng)建過程
經(jīng)過上面的討論,讀者對于context在心中有了一定的理解。但始終覺得少點什么:activity是什么時候被創(chuàng)建的,他的contextImpl是如何被賦值的?Application呢?為什么說ContextProvider的context是Application,Broadcast的context是Activity?contextImpl又是如何被創(chuàng)建的?解決這些疑惑,就必須閱讀源碼了。閱讀源碼的好處非常多,上面我的講述,都是基于我閱讀源碼之后的理解,而“一千個觀眾有一千個哈姆雷特”,閱讀源碼可以形成自己對整個機制自己的思考和理解,同時可以讓自己對context那些知識真正落實到代碼上,增強自己對知識的自信心。當(dāng)別人和你意見不同的時候,你可以拍拍胸脯說:我看過源碼,這個地方就是這樣。是不是非常自信且傲嬌?
然而閱讀源碼不是越多越好,而是把握整體的流程之后閱讀關(guān)鍵源碼,不要深入源碼堆中無法自拔。例如我覺得activity的contextImpl是在Activity創(chuàng)建的過程中被賦值的,那么我就會去找activity的啟動流程源碼,然后只看和context有關(guān)的部分。提高效率的同時,還可以切中我們學(xué)習(xí)的點。下面的源碼閱讀我們給出整體流程,然后重點理解關(guān)鍵代碼,其他的源碼讀者可自行下載源碼去跟蹤閱讀一下。
Application
Application應(yīng)用級別的context,是在應(yīng)用被創(chuàng)建的時候被創(chuàng)建的,是第一個被創(chuàng)建的context,也是最后一個被銷毀的context。因而追蹤Application的創(chuàng)建需要從應(yīng)用程序的啟動流程看起。應(yīng)用啟動的源碼流程如下(簡化版):

應(yīng)用程序從ActivityThread的main方法開始執(zhí)行,從Handler消息機制中我們知道m(xù)ain方法主要是開啟線程的Looper以及handler,然后由AMS向主線程發(fā)送message控制應(yīng)用的啟動過程。因而我們可以把目標鎖定在圖中的最后一個方法:handleBindApplication,Application最有可能在這里被創(chuàng)建:
- ActivityThread.class (api29)
private void handleBindApplication(AppBindData data) {
...
// 創(chuàng)建LoadedApk對象
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
...
Application app;
...
try {
// 創(chuàng)建Application
app = data.info.makeApplication(data.restrictedBackupMode, null);
...
}
try {
...
// 回調(diào)Application的onCreate方法
mInstrumentation.callApplicationOnCreate(app);
}
...
}
handleBindApplication的參數(shù)AppBindData是AMS給應(yīng)用程序的啟動信息,其中就包含了“權(quán)限憑證”——ApplicationInfo等。LoadedApk就是通過這些對象來創(chuàng)建獲取對系統(tǒng)資源的訪問權(quán)限,然后通過LoadApk來創(chuàng)建ContextImpl以及Application。
這里我們只關(guān)注和context創(chuàng)建有關(guān)的邏輯,前面啟動程序的源碼以及AMS如何處理,這里就不講了,讀者有興趣可以讀ContextProvider啟動流程這篇文章,其中對ContextProvider的啟動過程就有對上述源碼進行追蹤詳解。
那么接下來我們繼續(xù)關(guān)注Application是如何創(chuàng)建的:
-LoadeApk.class(api29)
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
// 如果application已經(jīng)存在則直接返回
if (mApplication != null) {
return mApplication;
}
...
Application app = null;
String appClass = mApplicationInfo.className;
...
try {
java.lang.ClassLoader cl = getClassLoader();
...
// 創(chuàng)建ContextImpl
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
// 利用類加載器加載我們在AndroidMenifest指定的Application類
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
// 把Application的引用給comtextImpl,這樣contextImpl也可以很方便地訪問Application
appContext.setOuterContext(app);
}
...
mActivityThread.mAllApplications.add(app);
// 把app設(shè)置為mApplication,當(dāng)我們調(diào)用context.getApplicationContext就是獲取這個對象
mApplication = app;
if (instrumentation != null) {
try {
// 回調(diào)Application的onCreate方法
instrumentation.callApplicationOnCreate(app);
}
...
}
...
return app;
}
代碼的邏輯也不復(fù)雜,首先判斷LoadedApk對象中的mApplication是否存在,否則創(chuàng)建ContextImpl,再利用類加載器和contextImpl創(chuàng)建Application,最后把Application對象賦值給LoadedApk的mApplication,再回調(diào)Application的onCreate方法。我們先來看一下contextImpl是如何創(chuàng)建的:
- ContextImpl.class(api29)
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
String opPackageName) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null, opPackageName);
context.setResources(packageInfo.getResources());
return context;
}
這里直接new了一個ContextImpl,同時給ContextImpl賦值訪問系統(tǒng)資源相關(guān)的“權(quán)限”對象——ActivityThread,LoadedApk等。讓我們再回到Application的創(chuàng)建過程。我們可以猜測,在newApplication包含的邏輯肯定有:利用反射創(chuàng)建Application,再把contextImpl賦值給Application。原因是每個人自定義的Application類不同,需要利用反射來創(chuàng)建對象,其次Application中的mBase屬性是對ContextImpl的引用??丛创a:
- Instrumentation.class(api29)
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);
app.attach(context);
return app;
}
- Application.class(api29)
final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
- ContextWrapper.class(api29)
Context mBase;
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
結(jié)果非常符合我們的猜測,先創(chuàng)建Application對象,再把ContextImpl通過Application的attach方法賦值給Application。然后Application的attach方法調(diào)用了ContextWrapper的attachBaseContext方法,因為Application也是繼承自ContextWrapper。這樣,就把ContextImpl賦值給Application的mBase屬性了。
再回到前面的邏輯,創(chuàng)建了Application之后需要回調(diào)onCreate方法:
- Instrumentation.class(api29)
public void callApplicationOnCreate(Application app) {
app.onCreate();
}
簡單粗暴,直接回調(diào)。到這里,Application的創(chuàng)建以及context的創(chuàng)建流程就走完了。但是需要注意的是,全局初始化需要在onCreate中進行,而不要在Application的構(gòu)造器中執(zhí)行。從代碼中我們可以看到ContextImpl是在Application被創(chuàng)建之后再賦值的。
Activity
Activity的context也是在Activity創(chuàng)建的過程中被創(chuàng)建的,這個就涉及到Activity的啟動流程,這里涉及到三個流程:應(yīng)用程序請求AMS,AMS處理請求,應(yīng)用程序響應(yīng)Activity創(chuàng)建事務(wù):

依然,我們專注于Activity的創(chuàng)建流程,其他的讀者可閱讀Activity啟動流程這篇文章了解。和Application一樣,Activity的創(chuàng)建時由AMS來控制的,AMS向應(yīng)用程序進程發(fā)送消息來執(zhí)行具體的啟動邏輯。最后會執(zhí)行到handleLaunchActivity這個方法:
- ActivityThread.class(api29)
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
...
final Activity a = performLaunchActivity(r, customIntent);
...
return a;
}
最終的就是中間這句代碼,進入看源碼:
- ActivityThread.class(api29)
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
// 創(chuàng)建Activity的ContextImpl
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
// 利用類加載創(chuàng)建activity實例
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
}
try {
// 創(chuàng)建Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
...
// 把activity設(shè)置給context,這樣context也可以訪問到activity了
appContext.setOuterContext(activity);
// 調(diào)用activity的attach方法把contextImpl設(shè)置給activity
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
// 設(shè)置主題
activity.setTheme(theme);
}
...
// 回調(diào)onCreate方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
}
...
}
...
return activity;
}
代碼的邏輯不是很復(fù)雜,首先創(chuàng)建Activity的ContextImpl,利用類加載創(chuàng)建activity實例,然后再通過LoadedApk創(chuàng)建Application,這個方法在前面我們講過,如果Application已經(jīng)創(chuàng)建會直接返回已經(jīng)創(chuàng)建的對象。然后把activity設(shè)置給context,這樣context也可以訪問到activity了。這里要注意,前面講到使用Activity的context會造成內(nèi)存泄露,那么可不可以用Activity的contextImpl對象呢?答案是不可以,因為ContextImpl也會持有Activity的引用,需要特別注意一下。隨后再調(diào)用activity的attach方法把contextImpl設(shè)置給activity。后面是設(shè)置主題和回調(diào)onCreate方法,我們就不深入了,主要看看attach方法:
- Activity.class(api29)
final void attach(Context context,...) {
attachBaseContext(context);
...
}
這里省略了大量的代碼,只保留關(guān)鍵一句:attachBaseContext,是不是很熟悉?調(diào)用ContextWrapper的方法來給mBase屬性賦值,和前面Application是一樣的,就不再贅述。
Service
依然只關(guān)注關(guān)鍵代碼流程,先看Service的啟動流程圖:

Service的創(chuàng)建過程也是受AMS的控制,同樣我們看到創(chuàng)建Service的那一步,最終會調(diào)用到handleCreateService這個方法:
private void handleCreateService(CreateServiceData data) {
...
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
}
...
try {
...
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
service.onCreate();
mServices.put(data.token, service);
...
}
...
}
Service的邏輯就相對簡單了,同樣創(chuàng)建service實例,再創(chuàng)建contextImpl,最后把contextImpl通過Service的attach方法賦值給mBase屬性,最后回調(diào)Service的onCreate方法。過程和上面的很像,這里就不再深入講了,感興趣的讀者可自行去閱讀源碼,也可以閱讀Android中Service的啟動與綁定過程詳解(基于api29)這篇文章了解Service的詳細內(nèi)容。
Broadcast
Broadcast和上面的組件不同,他并不是繼承自Context,所以他的Context是需要通過上述三者來給予。我們一般使用廣播的context是在接受器中,如:
class MyClass :BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
TODO("use context")
}
}
那么onReceive的context對象是從哪里來的呢?同樣我們先看廣播接收器的注冊流程:

同樣,詳細的廣播相關(guān)工作流程可以閱讀Android廣播Broadcast的注冊與廣播源碼過程詳解(基于api29)這篇文章了解。因為在創(chuàng)建Receiver的時候并沒有傳入context,所以我們需要追蹤他的注冊流程,看看在哪里獲取了context。我們先看到ContextImpl的registerReceiver方法:
- ContextImpl.class(api29)
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
// 注意參數(shù)
return registerReceiverInternal(receiver, getUserId(),
filter, broadcastPermission, scheduler, getOuterContext(), 0);
}
registerReceiver方法最終會來到這個重載方法,我們可以注意到,這里有個getOuterContext,這個是什么?還記得Activity的context創(chuàng)建過程嗎?這個方法獲取的就是activity本身。我們繼續(xù)看下去:
- ContextImpl.class(api29)
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
IntentFilter filter, String broadcastPermission,
Handler scheduler, Context context, int flags) {
IIntentReceiver rd = null;
if (receiver != null) {
if (mPackageInfo != null && context != null) {
...
rd = mPackageInfo.getReceiverDispatcher(
receiver, context, scheduler,
mMainThread.getInstrumentation(), true);
}
...
}
...
}
這里利用context創(chuàng)建了ReceiverDispatcher,我們繼續(xù)深入看:
- LoadedApk.class(api29)
public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
Context context, Handler handler,
Instrumentation instrumentation, boolean registered) {
synchronized (mReceivers) {
LoadedApk.ReceiverDispatcher rd = null;
...
if (rd == null) {
rd = new ReceiverDispatcher(r, context, handler,
instrumentation, registered);
...
}
...
}
}
- ReceiverDispatcher.class(api29)
ReceiverDispatcher(..., Context context,...) {
...
mContext = context;
...
}
這里確實把receiver和context創(chuàng)建了ReceiverDispatcher,嗯?怎么沒有給Receiver?其實這涉及到廣播的內(nèi)部設(shè)計結(jié)構(gòu)。Receiver是沒有跨進程通信能力的,而廣播需要AMS的調(diào)控,所以必須有一個可以跟AMS溝通的對象,這個對象是InnerReceiver,而ReceiverDispatcher就是負責(zé)維護他們兩個的聯(lián)系,如下圖:

而onReceive方法也是由ReceiverDispatcher回調(diào)的,最后我們再看到回調(diào)onReceive的那部分代碼:
- ReceiverDispatcher.java/Args.class;
public final Runnable getRunnable() {
return () -> {
...;
try {
...;
// 可以看到這里回調(diào)了receiver的方法,這樣整個接收廣播的流程就走完了。
receiver.onReceive(mContext, intent);
}
}
}
Args是Receiver的內(nèi)部類,mContext就是在創(chuàng)建ReceiverDispatcher時傳入的對象,到這里我們就知道這個對象確實是Activity了。
但是,,不一定每個都是Activity。在源碼中我們知道是通過getOuterContext來獲取context,如果是通過別的context注冊廣播,那么對應(yīng)的對象也就不同了,只是我們一般都是在Activity中創(chuàng)建廣播,所以這個context一般是activity對象。
ContentProvider
ContextProvider我們用的就比較少了,內(nèi)容提供器主要是用于應(yīng)用間內(nèi)容共享的。雖然ContentProvider是由系統(tǒng)創(chuàng)建的,但是他本身并不屬于Context家族體系內(nèi),所以他的context也是從其他獲取的。老樣子,先看ContentProvider的創(chuàng)建流程:

咦?這不是Application創(chuàng)建的流程圖嗎?是的,ContentProvider是伴隨著應(yīng)用啟動被創(chuàng)建的,來看一張更加詳細的流程圖:

我們把目光聚集到ContentProvider的創(chuàng)建上,也就是installContentProviders方法。同樣,詳細的ContentProvider工作流程可以訪問Android中ContentProvider的啟動與請求源碼流程詳解(基于api29)這篇文章。installContentProviders是在handleBindApplication中被調(diào)用的,我們看到調(diào)用這個方法的地方:
private void handleBindApplication(AppBindData data) {
try {
// 創(chuàng)建Application
app = data.info.makeApplication(data.restrictedBackupMode, null);
...
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
// 安裝ContentProvider
installContentProviders(app, data.providers);
}
}
}
可以看到這里傳入了application對象,我們繼續(xù)看下去:
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<ContentProviderHolder> results = new ArrayList<>();
for (ProviderInfo cpi : providers) {
...
ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
...
}
...
}
這里調(diào)用了installProvider,繼續(xù)往下看:
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
if (holder == null || holder.provider == null) {
...
// 這里c最終是由context構(gòu)造的
Context c = null;
ApplicationInfo ai = info.applicationInfo;
if (context.getPackageName().equals(ai.packageName)) {
c = context;
}
...
try {
// 創(chuàng)建ContentProvider
final java.lang.ClassLoader cl = c.getClassLoader();
LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
...
localProvider = packageInfo.getAppFactory()
.instantiateProvider(cl, info.name);
provider = localProvider.getIContentProvider();
...
// 把context設(shè)置給ContentProvider
localProvider.attachInfo(c, info);
}
...
}
...
}
這里最重要的一行代碼是localProvider.attachInfo(c, info);,在這里把context設(shè)置給了ContentProvider,我們再深入一點看看:
- ContentProvider.class(api29)
public void attachInfo(Context context, ProviderInfo info) {
attachInfo(context, info, false);
}
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
...
if (mContext == null) {
mContext = context;
...
}
...
}
這里確實把context賦值給了ContentProvider的內(nèi)部變量mContext,這樣ContentProvider就可以使用Context了。而這個context正是一開始傳進來的Application。
從源碼設(shè)計角度看Context
到這里關(guān)于Context的知識也講得差不多了。研究Framework層知識,不能只停留在他是什么,有什么作用即可。Framework層他是一個整體,構(gòu)成了android這個龐大的體系,還需要看Context,在其中扮演著什么樣的角色,解決了什么樣的問題。在window機制中我講到window的存在是為了解決屏幕上view的顯示邏輯與觸摸反饋問題,在Hanlder機制中我寫到整個android程序都是基于Handler機制來驅(qū)動執(zhí)行的,而Context呢?
Android系統(tǒng)是一個完整的生態(tài),他搭建了一個環(huán)境,讓各種程序可以運行在上面。而任何一個程序,想要運行在這個環(huán)境上,必須得到系統(tǒng)的允許,也就是軟件安裝。安卓與電腦不同的是,他不是任意一個程序就可以直接訪問到系統(tǒng)的資源。我們在window上可以寫一個java程序,然后直接開啟一個文件流就可以讀取和修改文件了。而Android沒這么簡單,他任意一個程序的運行都必須經(jīng)過系統(tǒng)的調(diào)控。也就是,即時程序獲得允許(安裝在手機上了),程序本身要運行,還得是系統(tǒng)來控制程序運行,程序無法自發(fā)地執(zhí)行在Android環(huán)境中。我們通過源碼可以知道程序的main方法,僅僅只是開啟了線程的Looper循環(huán),而后續(xù)的一切,都必須等待AMS來控制。
那應(yīng)用程序自己硬要執(zhí)行可不可以?可以,但是沒卵用。想要獲得系統(tǒng)資源,如啟動四大組件、讀取布局文件、讀寫數(shù)據(jù)庫、調(diào)用系統(tǒng)柜攝像頭等等,都必須要通過Context,而context必須要通過AMS來獲取。這就區(qū)分了一個程序是一個普通的Java程序,還是android程序。
Context承受的兩大重要職責(zé)是:身份權(quán)限、程序訪問系統(tǒng)的接口。一個Java類,如果沒有context那么就是一個普通的Java類,而當(dāng)他獲得context那么他就可以稱之為一個組件了,因為它獲得了訪問系統(tǒng)的權(quán)限,他不再是一個普通的身份,是屬于android“公民”了。而“公民”并不是無法無天,系統(tǒng)也可以通過context來封裝以及限制程序的權(quán)限。要想彈出一個通知,你必須通過這個api,用戶關(guān)閉你的通知權(quán)限,你就別想通過第二條路來彈出通知了。同時 程序也無需知道底層到底是如何實現(xiàn),只管調(diào)用api即可。四大組件為何稱為四大組件,因為他們生來就有了context,特別是activity和service,包括Application。而我們寫的一切程序,都必須間接或者直接從其中獲取context。
總而言之,context就是負責(zé)區(qū)分android內(nèi)外程序的一個機制,限制程序訪問系統(tǒng)資源的權(quán)限。
總結(jié)
文章從什么是context開始介紹,再針對context的不同子類進行解析,最后結(jié)合源碼深入地講解了context的創(chuàng)建過程。最后再談了我對context的設(shè)計理解。
關(guān)于context想說的就已經(jīng)說完了。雖然這些內(nèi)容日常很少用得到,但是非常有助于我們對Android整個系統(tǒng)框架的理解。而當(dāng)我們對系統(tǒng)有更加深入的理解后,寫出來的程序也就會更加健壯。
希望文章對你有幫助。
全文到此,原創(chuàng)不易,覺得有幫助可以點贊收藏評論轉(zhuǎn)發(fā)。
筆者能力有限,有任何想法歡迎評論區(qū)交流指正。
如需轉(zhuǎn)載請私信交流。
另外歡迎光臨筆者的個人博客:傳送門
————————————————
版權(quán)聲明:本文為CSDN博主「一只修仙的猿」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_43766753/article/details/109017196