一.簡(jiǎn)介
???????Android應(yīng)用模型是基于組件的應(yīng)用設(shè)計(jì)模式,組件的運(yùn)行要有一個(gè)完整的Android工程環(huán)境处硬,在這個(gè)環(huán)境下塌西,Activity掰邢、Service等系統(tǒng)組件才能夠正常工作,而這些組件并不能采用普通的Java對(duì)象創(chuàng)建方式丙唧,new一下就能創(chuàng)建實(shí)例了愈魏,而是要有它們各自的上下文環(huán)境,也就是我們這里討論的Context想际∨嗦可以這樣講,Context是維持Android程序中各組件能夠正常工作的一個(gè)核心功能類胡本。
???????源碼中的注釋是這么來解釋Context:Context提供了關(guān)于應(yīng)用環(huán)境全局信息的接口北苟。它是一個(gè)抽象類,它的執(zhí)行被Android系統(tǒng)所提供打瘪。它允許獲取以應(yīng)用為特征的資源和類型友鼻,是一個(gè)統(tǒng)領(lǐng)一些資源(應(yīng)用程序環(huán)境變量等)的上下文。
???????Context描述一個(gè)應(yīng)用程序環(huán)境的信息(即上下文)闺骚;Android提供了該抽象類的具體實(shí)現(xiàn)類彩扔;通過它我們可以獲取應(yīng)用程序的資源和類(包括應(yīng)用級(jí)別操作,如啟動(dòng)Activity僻爽,發(fā)廣播虫碉,接受Intent等)。
主要作用
??????a.四大組件的交互胸梆,包括啟動(dòng) Activity敦捧、Broadcast、Service碰镜,獲取 ContentResolver 等兢卵。
??????b.獲取系統(tǒng)/應(yīng)用資源,包括 AssetManager绪颖、PackageManager秽荤、Resources、System Service 以及 color柠横、string窃款、drawable 等。
??????c.文件牍氛,包括獲取緩存文件夾晨继、刪除文件、SharedPreference 相關(guān)等搬俊。
??????d.數(shù)據(jù)庫(kù)(SQLite)相關(guān)紊扬,包括打開數(shù)據(jù)庫(kù)曲饱、刪除數(shù)據(jù)庫(kù)、獲取數(shù)據(jù)庫(kù)路徑等珠月。
二.繼承關(guān)系
??????既然上面Context是一個(gè)抽象類扩淀,那么肯定有對(duì)應(yīng)的實(shí)現(xiàn)類,通過查看源碼啤挎,可以看到對(duì)應(yīng)的關(guān)系圖:
a.ContextImpl類
??????ContextImpl繼承Context抽象類驻谆,實(shí)現(xiàn)了Context類中的抽象方法,是Context的具體實(shí)現(xiàn)類庆聘;它為Activity和其他應(yīng)用程序組件提供基本上下文對(duì)象胜臊,應(yīng)用中使用 Context的時(shí)候的方法就是它實(shí)現(xiàn)的。
b.ContextWrapper
??????ContextWrapper繼承Context抽象類伙判,作為Context類的包裝類象对,其內(nèi)部維護(hù)了一個(gè)Context類型的成員變量mBase,mBase最終會(huì)指向一個(gè)ContextImpl對(duì)象宴抚,ContextWrapper的方法其內(nèi)部依賴mBase勒魔,ContextWrapper是Context類的修飾類(裝飾器模式),真正的實(shí)現(xiàn)類是 ContextImpl菇曲,ContextWrapper 里面的方法調(diào)用也是調(diào)用 ContextImpl 里面的方法冠绢。
c.ContextThemeWrapper
??????ContextThemeWrapper繼承ContextWrapper,因此也擁有一個(gè)Context類型的成員變量mBase常潮,mBase最終會(huì)指向一個(gè)ContextImpl對(duì)象弟胀,它的一個(gè)直接子類就是 Activity,所以Activity也就擁有了Context提供的所有功能喊式。
d.Context類型
??????通過 Context 的繼承關(guān)系圖可以看到孵户,Activity、Service岔留、Application都是Context的子類夏哭,可以認(rèn)為Context一共有三種類型,分別是 Application贸诚、Activity 和Service方庭,他們分別承擔(dān)不同的作用,但是都屬于 Context酱固,而他們具有 Context 的功能則是由ContextImpl 類實(shí)現(xiàn)的。
??????1.Application:繼承ContextWrapper头朱,因此也擁有一個(gè)Context類型的成員變量mBase运悲,mBase最終會(huì)指向一個(gè)ContextImpl對(duì)象,ContextImpl是Context的具體實(shí)現(xiàn)類项钮,所以Application也就擁有了Context提供的所有功能班眯。
??????2.Service:繼承ContextWrapper希停,因此也擁有一個(gè)Context類型的成員變量mBase,mBase最終會(huì)指向一個(gè)ContextImpl對(duì)象署隘,ContextImpl是Context的具體實(shí)現(xiàn)類宠能,所以Service也就擁有了Context提供的所有功能。
??????3.Activity:繼承ContextThemeWrapper磁餐,ContextThemeWrapper繼承ContextWrapper违崇,因此也擁有一個(gè)Context類型的成員變量mBase,mBase最終會(huì)指向一個(gè)ContextImpl對(duì)象诊霹,ContextImpl是Context的具體實(shí)現(xiàn)類羞延,所以Activity也就擁有了Context提供的所有功能。
三.作用域
??????只有 Activity 顯示界面脾还,正因?yàn)槿绱税槁幔珹ctivity 繼承的是 ContextThemeWrapper 提供一些關(guān)于主題,界面顯示的能力鄙漏,間接繼承了 ContextWrapper 嗤谚;凡是跟 UI 有關(guān)的,都應(yīng)該用 Activity 作為 Context 來處理怔蚌,否則要么會(huì)報(bào)錯(cuò)呵恢,要么 UI 會(huì)使用系統(tǒng)默認(rèn)的主題。
四.對(duì)應(yīng)Display的Context
??????該場(chǎng)景適用于多屏擴(kuò)展媚创,比如:在不同的Display上彈出Toast及addView()渗钉,Toast的顯示也是通過WindowManager.addView()來實(shí)現(xiàn)的,那么是如何區(qū)分在哪個(gè)Display上顯示呢钞钙?不兜彎子鳄橘,直接上代碼:
DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
Context context = context.createDisplayContext(displayManager.getDisplay(Display.SECOND_DISPLAY));
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
w.addView(view, params);
??????通過以上代碼就可以實(shí)現(xiàn)在對(duì)應(yīng)Display上顯示view,可以看到關(guān)鍵方法是createDisplayContext(Display display)芒炼,一起看一下該方法在 ContextImpl.java中的實(shí)現(xiàn):
@Override
public Context createDisplayContext(Display display) {
ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName,
mActivityToken, mUser, mFlags, mClassLoader);
final int displayId = display.getDisplayId();
context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
null, getDisplayAdjustments(displayId).getCompatibilityInfo()));
context.mDisplay = display;
return context;
}
??????ContextImpl對(duì)象有變量mDisplay瘫怜,記錄了對(duì)應(yīng)的Display,之前分析過系統(tǒng)服務(wù)調(diào)用分析本刽,通過Context.getSystemService()來獲取系統(tǒng)服務(wù)鲸湃,本文以WindowManager來舉例:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
??????可以看到,在調(diào)用SystemServiceRegistry.getSystemService(this, name)時(shí)子寓,會(huì)將this傳入暗挑,再接著往下看:
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
??????可以看到,在createService時(shí)會(huì)將ContextImpl作為參數(shù)傳入斜友,那么在獲取WindowManager時(shí)炸裆,返回的是其實(shí)現(xiàn)類WindowManagerImpl對(duì)象,接著往下看:
public WindowManagerImpl(Context context) {
this(context, null);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
??????在創(chuàng)建WindowManagerImpl對(duì)象時(shí)會(huì)保存mContext對(duì)象鲜屏,那么當(dāng)執(zhí)行WindowManager.addView()時(shí)烹看,看一下邏輯處理:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
??????可以看到国拇,在執(zhí)行addView()時(shí),最終會(huì)調(diào)用到WindowManagerGlobal的addView()方法惯殊,此時(shí)會(huì)通過mContext.getDisplay()獲取到創(chuàng)建Context時(shí)傳入的Display酱吝,從而將view添加到對(duì)應(yīng)的Display上。
??????還有一種場(chǎng)景土思,比如顯式配置應(yīng)用在指定Display上進(jìn)行啟動(dòng)务热,那么該如何指定Activity的context對(duì)應(yīng)指定的Display呢?
??????我們知道浪漠,在啟動(dòng)一個(gè)Activity時(shí)陕习,通過AMS會(huì)回調(diào)到應(yīng)用進(jìn)程的ActivityThread內(nèi)部的performLaunchActivty()方法,該方法內(nèi)部會(huì)創(chuàng)建ContextImpl實(shí)例址愿,通過attach()賦值給Actiivty內(nèi)部该镣,一起看一下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
.........................
ContextImpl appContext = createBaseContextForActivity(r);
...........................
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);
...................
}
??????通過createBaseContextForActivity()來創(chuàng)建ContextImpl實(shí)例appContext,看一下創(chuàng)建過程:
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
final int displayId;
try {
displayId = ActivityManager.getService().getActivityDisplayId(r.token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
// its content on a secondary display if there is one.
String pkgName = SystemProperties.get("debug.second-display.pkg");
if (pkgName != null && !pkgName.isEmpty()
&& r.packageInfo.mPackageName.contains(pkgName)) {
for (int id : dm.getDisplayIds()) {
if (id != Display.DEFAULT_DISPLAY) {
Display display =
dm.getCompatibleDisplay(id, appContext.getResources());
appContext = (ContextImpl) appContext.createDisplayContext(display);
break;
}
}
}
return appContext;
}
??????可以看到响谓,在該方法內(nèi)部會(huì)先獲取Activity在啟動(dòng)前設(shè)置的啟動(dòng)Display對(duì)應(yīng)的displayId损合,然后通過createActivityContext()將displayId作為參數(shù)傳入,此時(shí)ContextImpl會(huì)對(duì)應(yīng)了Display信息娘纷;另外如果是調(diào)試pkgName(啟動(dòng)到SecondDisplay)嫁审,那么會(huì)創(chuàng)建SecondDisplay對(duì)應(yīng)的ContextImpl實(shí)例并返回,此時(shí)ContextImpl也會(huì)對(duì)應(yīng)Display信息赖晶;
??????擴(kuò)展問題:Service中也需要ContextImpl對(duì)應(yīng)Display律适,那么可以在handleCreateService()內(nèi)部進(jìn)行跟上述相同的修改來使Service的ContextImpl也對(duì)應(yīng)Display信息。
private void handleCreateService(CreateServiceData data) {
.....................
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
//修改為對(duì)應(yīng)Display的ContextImpl
final DisplayManagerGlobal dmg = DisplayManagerGlobal.getInstance();
if (packageInfo.mPackageName != null && dmg.isShowSecondDisplay(packageInfo.mPackageName)) {
for (int id : dmg.getDisplayIds()) {
if (id != Display.DEFAULT_DISPLAY) {
Display display = dmg.getCompatibleDisplay(id, context.getResources());
context = (ContextImpl) context.createDisplayContext(display);
break;
}
}
}
............................
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
.................
}
五.正確使用Context
??????一般Context造成的內(nèi)存泄漏遏插,幾乎都是當(dāng)Context銷毀的時(shí)候捂贿,卻因?yàn)楸灰脤?dǎo)致銷毀失敗,而Application的Context對(duì)象可以理解為隨著進(jìn)程存在的胳嘲,所以我們總結(jié)出使用Context的正確姿勢(shì):
??????1:當(dāng)Application的Context能搞定的情況下厂僧,并且生命周期長(zhǎng)的對(duì)象,優(yōu)先使用Application的Context了牛。
??????2:不要讓生命周期長(zhǎng)于Activity的對(duì)象持有Activity的引用颜屠。
??????3:盡量不要在Activity中使用非靜態(tài)內(nèi)部類,因?yàn)榉庆o態(tài)內(nèi)部類會(huì)隱式持有外部類實(shí)例的引用鹰祸,如果使用靜態(tài)內(nèi)部類甫窟,將外部實(shí)例引用作為弱引用持有。
六.其他
a.在自定義MyApplication的構(gòu)造方法中使用Context
??????在自定義MyApplication的構(gòu)造方法中調(diào)用Context的getPackageName()[實(shí)際是在調(diào)用mBase.getPackageName()]時(shí)福荸,attachBaseContext(Context base) 還未被系統(tǒng)調(diào)用蕴坪,因此mBase為Null,出現(xiàn)空指針敬锐。
??????Application方法的執(zhí)行順序:構(gòu)造方法>attachBaseContext()方法>onCreate()方法背传。attachBaseContext(Context base) 是被系統(tǒng)調(diào)用的,為mBase賦值為ContextImpl類型的context台夺。
b.一個(gè)APP應(yīng)用Context數(shù)量
??????Context 一共有 Application 径玖、Activity 和 Service 三種類型,因此一個(gè)應(yīng)用程序中 Context 數(shù)量的計(jì)算公式就可以這樣寫:
??????Context 數(shù)量 = Activity 數(shù)量 + Service 數(shù)量 + 1
??????上面的1代表著 Application 的數(shù)量颤介,因?yàn)橐粋€(gè)應(yīng)用程序中可以有多個(gè)Activity和多個(gè)Service梳星,但是只能有一個(gè)Application。
c.ContextImpl實(shí)例什么時(shí)候生成
??????ContextImpl實(shí)例生成對(duì)應(yīng)著mBase的賦值過程:
??????在啟動(dòng)Activity時(shí)滚朵,在ActivityThread內(nèi)部通過handleLaunchActivity()方法一系列調(diào)用冤灾,在通過Instrucmentation創(chuàng)建完Activity后,會(huì)先調(diào)用Activity的attach()方法辕近,會(huì)傳入已創(chuàng)建好的ContextImpl對(duì)象韵吨,在Attach()方法內(nèi)部會(huì)先調(diào)用attachBaseContext(context)方法,會(huì)將ContextImpl通過super.attachBaseContext(context)一步一步最后賦值給ContextWrapper的mBase移宅,接下來再調(diào)用activity的onCreate()归粉。
d.ContentProvider里的Context初始化
??????ContentProvider本身不是Context ,但是它有一個(gè)成員變量 mContext 漏峰,是通過構(gòu)造函數(shù)傳入的糠悼。mContext初始化對(duì)應(yīng)著ContentProvider創(chuàng)建時(shí)機(jī)。
??????應(yīng)用創(chuàng)建Application是通過調(diào)用 ActivityThread.handleBindApplication方法浅乔,這個(gè)方法的相關(guān)流程有:
??????創(chuàng)建 Application
??????初始化 Application的Context
??????調(diào)用installContentProviders()并傳入剛創(chuàng)建好的context來創(chuàng)建ContentProvider
??????調(diào)用Application.onCreate()
??????ContentProvider的Context是在Applicaiton創(chuàng)建之后倔喂,但是 onCreate方法調(diào)用之前初始化的。
???????以上就是Context的詳解及使用中常見的問題靖苇!