一氓涣、Context 類繼承關(guān)系
上圖描述了 Context 類的主要繼承關(guān)系劳吠,Context 是抽象類,提供了應(yīng)用環(huán)境全局信息的接口痒玩;Context 有兩個(gè)直接子類 ContextWrapper 和 ContextImpl凰荚,顧名思義褒脯,ContextWrapper 是上下文功能的封裝類,ContextImpl 是上下文功能的實(shí)現(xiàn)類到涂;Applicaiton 和 Service 直接繼承自 ContextWrapper 類颁督,Activity 繼承自 ContextThemeWrapper 類沉御,因?yàn)?Activity 提供 UI 顯示,需要有主題。
- Context 類提供了一組通用的 API烂完;
- ContextImpl 實(shí)現(xiàn)了 Context 所有的功能抠蚣,為 Activity 等應(yīng)用組件提供 Context 對象履澳;
- ContextWrapper 包含一個(gè)真正的 ContextImpl 的引用 mBase,然后就是 ContextImpl 的裝飾者模式柄冲;
- ContextThemeWrapper 內(nèi)部包含了 Theme 相關(guān)的接口忠蝗,即 android:theme 指定的屬性;
二长赞、Context 的數(shù)量及作用域
Context 數(shù)量 = Activity 數(shù)量 + Service 數(shù)量 + 1(Application 數(shù)量)
由于 Context 的具體實(shí)例是由 ContextImpl 類去實(shí)現(xiàn)的得哆,因此在絕大多數(shù)場景下哟旗,Activity闸餐、Service 和 Application 這三種類型的 Context 都是可以通用的。
不過有幾種場景比較特殊:
- Dialog 則必須在一個(gè) Activity 上面彈出(除非是 System Alert類型的 Dialog)近上,因此在這種場景下拂铡,我們只能使用 Activity 類型的 Context,否則將會出錯(cuò)斗锭。
- 非 Activity 類型的 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危号。
- 在 Application 和 Service 中去 layout inflate 也是合法的,但是會使用系統(tǒng)默認(rèn)的主題樣式猪半,如果你自定義了某些樣式可能不會被使用偷线。所以這種方式也不推薦使用声邦。
三、獲取 Context 對象
- View 的 getContext 方法亥曹,返回 View 的 Context媳瞪,通常是當(dāng)前正在展示的 Activity 的對象,通常在自定義 View 時(shí)會用到句葵;
- Activity.this 返回當(dāng)前 Activity 實(shí)例龙巨,如果是 UI 控件需要使用 Activity 作為 Context 對象旨别,但是默認(rèn)的 Toast 實(shí)際上使用 ApplicationContext 也可以汗茄;
- Activity 和 Service 的 getApplication 方法,返回 Application 的實(shí)例递览;
- getApplicationContext 方法,獲取當(dāng)前應(yīng)用的 Context 對象镜雨,也就是 Application 的實(shí)例儿捧,與上一個(gè)方法不同的是依附的對象不同菲盾;
四、Context 引起的內(nèi)存泄漏
一般 Context 造成的內(nèi)存泄漏都是 Activity 被銷毀后還被其它對象強(qiáng)引用也就是長生命周期對象持有短生命周期對象的引用而造成 Activity 不能被 GC 回收而導(dǎo)致的诡挂。
造成內(nèi)存泄漏的原因主要有兩個(gè):
- 靜態(tài)的 Activity Context 或任何包含 Activity Context 的對象(如 View)沒有在該 Activity 銷毀的時(shí)候置空引用临谱;
- 非靜態(tài)內(nèi)部類或匿名內(nèi)部類持有外部類的引用,在 Activity 銷毀后依然執(zhí)行任務(wù)悉默;
常見的場景如下:
static 直接或間接修飾 Activity Context
解決方法:
- 在任何時(shí)候都不建議用 static 修飾 Activity麦牺,因?yàn)?static 變量和應(yīng)用的存活時(shí)間是一樣的,這樣 Activity 生命周期結(jié)束時(shí)魏颓,引用仍然被持有吱晒;
- 在適當(dāng)?shù)牡胤奖热?onDestroy 方法中置空 Activity Context 的引用;
- 使用軟引用解決叹话,確保在 Activity 銷毀時(shí)驼壶,垃圾回收機(jī)制可以將其回收喉酌。
單例引用 Activity Context
解決方法:
- 使用 Applicaion Context 代替 Activity Context 泵喘;
- 在調(diào)用的地方使用弱引用
非靜態(tài)或匿名內(nèi)部類執(zhí)行耗時(shí)任務(wù)
非靜態(tài)內(nèi)部類或匿名內(nèi)部類都會擁有所在外部類的引用
解決方法:
用 static 修飾內(nèi)部類或者匿名內(nèi)部類所在的方法纪铺;
靜態(tài)內(nèi)部類持有外部類靜態(tài)成員變量
雖然靜態(tài)內(nèi)部類的生命周期和外部類無關(guān)碟渺,但是如果在內(nèi)部類中想要引入外部成員變量的話苫拍,這個(gè)成員變量必須是靜態(tài)的了,這也可能導(dǎo)致內(nèi)存泄露浆洗。
解決方法:
使用弱引用
Handler 引起的內(nèi)存泄漏
這種情況和非靜態(tài)或匿名內(nèi)部類執(zhí)行耗時(shí)任務(wù)的原因一樣集峦。
總結(jié)一下:
- 當(dāng) Application 的 Context 能搞定的情況下塔淤,并且生命周期長的對象,優(yōu)先使用 Application 的 Context聪黎。
2:不要讓生命周期長于 Activity 的對象持有到 Activity 的引用备恤。引用了要及時(shí)置空。
3:盡量不要在 Activity 中使用非靜態(tài)內(nèi)部類喉镰,因?yàn)榉庆o態(tài)內(nèi)部類會隱式持有外部類實(shí)例的引用侣姆,如果使用靜態(tài)內(nèi)部類沉噩,將外部實(shí)例引用作為弱引用持有。
五蚜厉、各種 Context 在 ActivityThread 中實(shí)例化過程源碼分析
Context 的實(shí)現(xiàn)是 ContextImpl派歌,Activity胶果、Application 和 Service 的創(chuàng)建都是在 ActivityThread 中完成的。
Activity 中 ContextImpl 實(shí)例化源碼分析
startActivity 啟動(dòng)一個(gè)新的 Activity 時(shí)系統(tǒng)會回調(diào) ActivityThread 的 handleLaunchActivity() 方法霎烙,該方法內(nèi)部會調(diào)用 performLaunchActivity() 方法去創(chuàng)建一個(gè) Activity 實(shí)例蕊连,然后回調(diào) Activity 的 onCreate() 等方法甘苍。所以 Activity 的 ContextImpl 實(shí)例化是在 ActivityThread 類的 performLaunchActivity 方法中,如下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
//已經(jīng)創(chuàng)建好新的activity實(shí)例
if (activity != null) {
//創(chuàng)建一個(gè)Context對象
Context appContext = createBaseContextForActivity(r, activity);
......
//將上面創(chuàng)建的appContext傳入到activity的attach方法
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);
......
}
......
return activity;
}
上面是 performLaunchActivity 的核心代碼,在 performLaunchActivity 方法里面創(chuàng)建了 Activity 的實(shí)例囚聚,然后然后調(diào)用 createBaseContextForActivity 方法創(chuàng)建一個(gè) Context 對象顽铸,這個(gè)對象是 ContextImpl,將前面創(chuàng)建的 Context 對象傳入到 Activity 的 attach 方法中去星压。
private Context createBaseContextForActivity(ActivityClientRecord r,
final Activity activity) {
//實(shí)質(zhì)就是new一個(gè)ContextImpl對象鬼譬,調(diào)運(yùn)ContextImpl的有參構(gòu)造初始化一些參數(shù)
ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);
//特別特別留意這里E◆ぁ!贾富!
//ContextImpl中有一個(gè)Context的成員叫mOuterContext牺六,通過這條語句就可將當(dāng)前新Activity對象賦值到創(chuàng)建的ContextImpl的成員mOuterContext(也就是讓ContextImpl內(nèi)部持有Activity)淑际。
appContext.setOuterContext(activity);
//創(chuàng)建返回值并且賦值
Context baseContext = appContext;
......
//返回ContextImpl對象
return baseContext;
}
再看 createBaseContextForActivity 方法扇住,實(shí)質(zhì)就是通過 ContextImpl 的有參構(gòu)造初始化一些參數(shù)創(chuàng)建一個(gè) ContextImpl 對象艘蹋,創(chuàng)建一個(gè) Context 對象將創(chuàng)建的 ContextImpl 賦值給 Context 變量并返回票灰。
ContextImpl 有一個(gè)成員變量 mOuterContext屑迂,通過 ContextImpl 的 setOuterContext 方法將傳進(jìn)來的 Activity 對象賦值給 mOuterContext,這樣 ContextImpl 就持有了 Activity 的引用庸汗。
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
//特別特別留意這里r遣铡C劣铡!
//與上面createBaseContextForActivity方法中setOuterContext語句類似凶掰,不同的在于:
//通過ContextThemeWrapper類的attachBaseContext方法懦窘,將createBaseContextForActivity中實(shí)例化的ContextImpl對象傳入到ContextWrapper類的mBase變量稚配,這樣ContextWrapper(Context子類)類的成員mBase就被實(shí)例化為Context的實(shí)現(xiàn)類ContextImpl
attachBaseContext(context);
......
}
attach 里面關(guān)鍵的代碼是調(diào)用 ContextThemeWrapper 類的 attachBaseContext 方法將 createBaseContextForActivity 創(chuàng)建的 ContextImpl 實(shí)例賦值給 ContextWrapper 的 mBase 變量道川,這樣 ContextWrapper 的成員 mBase 就被實(shí)例化為 Context 的實(shí)現(xiàn)類 ContextImpl
Service中ContextImpl實(shí)例化源碼分析
startService 或者 bindService 方法創(chuàng)建一個(gè)新 Service 時(shí)就會回調(diào) ActivityThread 類的 handleCreateService() 方法完成相關(guān)數(shù)據(jù)操作:
private void handleCreateService(CreateServiceData data) {
......
//類似上面Activity的創(chuàng)建,這里創(chuàng)建service對象實(shí)例
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
......
}
try {
......
//不做過多解釋臊岸,創(chuàng)建一個(gè)Context對象
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
//特別特別留意這里KЫ洹Q录肌钟哥!
//ContextImpl中有一個(gè)Context的成員叫mOuterContext腻贰,通過這條語句就可將當(dāng)前新Service對象賦值到創(chuàng)建的ContextImpl的成員mOuterContext(也就是讓ContextImpl內(nèi)部持有Service)装诡。
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
//將上面創(chuàng)建的context傳入到service的attach方法
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
service.onCreate();
......
} catch (Exception e) {
......
}
}
與實(shí)例化 Activity 的過程相似鸦采,創(chuàng)建一個(gè) ContextImpl 對象賦值給 mBase 變量渔伯,同時(shí) ContextImpl 的 mOuterContext 變量也持有 Service 的引用肄程,這里創(chuàng)建 ContextImpl 對象是用的 ContextImpl.createAppContext 方法
public final void attach(
Context context,
ActivityThread thread, String className, IBinder token,
Application application, Object activityManager) {
//特別特別留意這里!P读恃!
//與上面handleCreateService方法中setOuterContext語句類似代态,不同的在于:
//通過ContextWrapper類的attachBaseContext方法,將handleCreateService中實(shí)例化的ContextImpl對象傳入到ContextWrapper類的mBase變量西雀,這樣ContextWrapper(Context子類)類的成員mBase就被實(shí)例化為Context的實(shí)現(xiàn)類ContextImpl
attachBaseContext(context);
......
}
Application中ContextImpl實(shí)例化源碼分析
創(chuàng)建 Application 的過程也在 ActivityThread 類的 handleBindApplication() 方法完成相關(guān)數(shù)據(jù)操作艇肴,而 ContextImpl 的創(chuàng)建是在該方法中調(diào)運(yùn) LoadedApk 類的 makeApplication 方法中實(shí)現(xiàn):
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
//只有新創(chuàng)建的APP才會走if代碼塊之后的剩余邏輯
if (mApplication != null) {
return mApplication;
}
//即將創(chuàng)建的Application對象
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
initializeJavaContextClassLoader();
}
//不做過多解釋再悼,創(chuàng)建一個(gè)Context對象
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
//將Context傳入Instrumentation類的newApplication方法
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
//特別特別留意這里H选C潭啤咖刃!
//ContextImpl中有一個(gè)Context的成員叫mOuterContext嚎杨,通過這條語句就可將當(dāng)前新Application對象賦值到創(chuàng)建的ContextImpl的成員mOuterContext(也就是讓ContextImpl內(nèi)部持有Application)氧腰。
appContext.setOuterContext(app);
} catch (Exception e) {
......
}
......
return app;
}