深入解析Android App開(kāi)發(fā)中Context的用法
Context在開(kāi)發(fā)Android應(yīng)用的過(guò)程中扮演著非常重要的角色,比如啟動(dòng)一個(gè)Activity需要使用context.startActivity方法,將一個(gè)xml文件轉(zhuǎn)換為一個(gè)View對(duì)象也需要使用Context對(duì)象,可以這么說(shuō),離開(kāi)了這個(gè)類(lèi),Android開(kāi)發(fā)寸步難行,對(duì)于這樣一個(gè)類(lèi),我們又對(duì)他了解多少呢。我就說(shuō)說(shuō)我的感受吧,在剛開(kāi)始學(xué)習(xí)Android開(kāi)發(fā)時(shí),感覺(jué)使用Context的地方一直就是傳入一個(gè)Activity對(duì)象,久而久之感覺(jué)只要是Context的地方就傳入一個(gè)Activity就行了,那么我們現(xiàn)在就來(lái)詳細(xì)的分析一下Context和Activity的關(guān)系吧!
在開(kāi)始本文之前我們先放置一個(gè)問(wèn)題在這里:
我們平時(shí)在獲取項(xiàng)目資源時(shí)使用context.getResources()的時(shí)候?yàn)槭裁捶呕氐氖峭粋€(gè)值,明明是使用不同的Activity調(diào)用getResources返回結(jié)果卻是一樣的。

Context本身是一個(gè)純的abstract類(lèi),ContextWrapper是對(duì)Context的一個(gè)包裝而已,它的內(nèi)部包含了一個(gè)Context對(duì)象,其實(shí)對(duì)ContextWrapper的方法調(diào)用最終都是調(diào)用其中的Context對(duì)象完成的,至于ContextThremeWrapper,很明顯和Theme有關(guān),所以Activity從ContextThemmWrapper繼承,而Service從ContextWrapper繼承,ContextImpl是唯一一個(gè)真正實(shí)現(xiàn)了Context中方法的類(lèi)。
從上面的繼承關(guān)系來(lái)看,每一個(gè)Activity就是一個(gè)Context,每一個(gè)Service就是一個(gè)Context,這也就是為什么使用Context的地方可以被Activity或者Service替換了。
創(chuàng)建Context
根據(jù)前面所說(shuō),由于實(shí)現(xiàn)了Context的只有ContextImpl類(lèi),Activity和Service本沒(méi)有真正的實(shí)現(xiàn),他們只是內(nèi)部包含了一個(gè)真實(shí)的Context對(duì)象而已,也就是在在創(chuàng)建Activity或者Service的時(shí)候肯定要?jiǎng)?chuàng)建愛(ài)你一個(gè)ContextImpl對(duì)象,并賦值到Activity中的Context類(lèi)型變量中。那我們就來(lái)看看Andorid源碼中有哪些地方創(chuàng)建了ContextImpl.
據(jù)統(tǒng)計(jì)Android中創(chuàng)建ContextImpl的地方一共有7處:
- 在PackageInfo.makeApplication()中
- 在performLaunchActivity()中
- 在handleCreateBackupAgent()中
- 在handleCreateService()中
- 2次在hanldBinderAppplication()中
- 在attach()方法中
由于創(chuàng)建ContextImpl的基本原理類(lèi)似,所以這里只會(huì)分析幾個(gè)比較有代表性的地方:
1、 Application對(duì)應(yīng)的Context
在應(yīng)用程序啟動(dòng)時(shí),都會(huì)創(chuàng)建一個(gè)Application對(duì)象,所以輾轉(zhuǎn)調(diào)用到handleBindApplication()方法。
private final void handleBindApplication(AppBindData data) {
mBoundApplication = data;
mConfiguration = new Configuration(data.config);
....
data.info = getPackageInfoNoCheck(data.appInfo);
...
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
....
}
其中data.info是LoadedApk類(lèi)型的,到getPackageInfoNoCheck中看看源碼
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai) {
return getPackageInfo(ai, null, false, true);
}
里面其實(shí)調(diào)用的是getPackageInfo,繼續(xù)跟進(jìn):
if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
: "Loading resource-only package ") + aInfo.packageName
+ " (in " + (mBoundApplication != null
? mBoundApplication.processName : null)
+ ")");
packageInfo =
new LoadedApk(this, aInfo, this, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
if (includeCode) {
mPackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
}
由于includeCode傳入的是true,所以首先從mPackages中獲取,如果沒(méi)有,則new一個(gè)出來(lái),并放入mPackages里面去,注意,這里的mPackages是ActivityThread中的屬性。
下面繼續(xù)分析一下LoadedApk這個(gè)類(lèi)中的makeApplication函數(shù)
try {
java.lang.ClassLoader cl = getClassLoader();
//創(chuàng)建一個(gè)ContextImpl對(duì)象
ContextImpl appContext = new ContextImpl();
appContext.init(this, null, mActivityThread);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
這里創(chuàng)建了一個(gè)ContextImpl對(duì)象,并調(diào)用了它的init方法,現(xiàn)在進(jìn)入init方法。
mPackageInfo = packageInfo; mResources = mPackageInfo.getResources(mainThread);
對(duì)mPackageInof和mResources兩個(gè)變量初始化
回到makeApplication中,創(chuàng)建了一個(gè)Application對(duì)象,并將appContext傳進(jìn)去,其實(shí)就是將appContext傳遞給ContextWrapper中的Context類(lèi)型變量(Application也是繼承ContextWrapper)
2、Activity中的Context
在創(chuàng)建一個(gè)Activity時(shí),經(jīng)過(guò)輾轉(zhuǎn)調(diào)用,會(huì)執(zhí)行handleLaunchActivity(),然后調(diào)用performLaunchActivity(),該方法創(chuàng)建ContextImpl代碼如下:
r.packageInfo= getPackageInfo(aInfo.applicationInfo,
Context.CONTEXT_INCLUDE_CODE);
ContextImplappContext = new ContextImpl();
appContext.init(r.packageInfo,r.token, this);
appContext.setOuterContext(activity);
activity.attach(appContext,this, getInstrumentation(), r.token,
r.ident, app, r.intent,r.activityInfo, title, r.parent,
r.embeddedID,r.lastNonConfigurationInstance,
r.lastNonConfigurationChildInstances, config);
由于getPackageInfo函數(shù)之前已經(jīng)分析過(guò)了,稍微有點(diǎn)區(qū)別,但是大致流程是差不多的,所以此處的appContext執(zhí)行init之后,其中的mPackages變量和mResources變量時(shí)一樣的,activity通過(guò)attach函數(shù)將該appContext賦值到ContextWrapper中的Context類(lèi)型變量。
3、Service中的Context
同樣 在創(chuàng)建一個(gè)Service時(shí),經(jīng)過(guò)輾轉(zhuǎn)調(diào)用會(huì)調(diào)用到scheduleCreateService方法,之后會(huì)巧用handleCreateService
LoadedApkpackageInfo = getPackageInfoNoCheck(
data.info.applicationInfo);
ContextImplcontext = new ContextImpl();
context.init(packageInfo, null,this);
Application app =packageInfo.makeApplication(false, mInstrumentation);
context.setOuterContext(service);
service.attach(context, this,data.info.name, data.token, app,
ActivityManagerNative.getDefault());
其思路和上面兩個(gè)基本一樣,在此就不再詳述。
Context對(duì)資源的訪問(wèn)
很明確,不同的Context得到的都是同一份資源。這是很好理解的,請(qǐng)看下面的分析
得到資源的方式為context.getResources,而真正的實(shí)現(xiàn)位于ContextImpl中的getResources方法,在ContextImpl中有一個(gè)成員 private Resources mResources,它就是getResources方法返回的結(jié)果,mResources的賦值代碼為:
mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);
下面看一下ResourcesManager的getTopLevelResources方法,這個(gè)方法的思想是這樣的:在ResourcesManager中,所有的資源對(duì)象都被存儲(chǔ)在ArrayMap中,首先根據(jù)當(dāng)前的請(qǐng)求參數(shù)去查找資源,如果找到了就返回,否則就創(chuàng)建一個(gè)資源對(duì)象放到ArrayMap中。有一點(diǎn)需要說(shuō)明的是為什么會(huì)有多個(gè)資源對(duì)象,原因很簡(jiǎn)單,因?yàn)閞es下可能存在多個(gè)適配不同設(shè)備、不同分辨率、不同系統(tǒng)版本的目錄,按照android系統(tǒng)的設(shè)計(jì),不同設(shè)備在訪問(wèn)同一個(gè)應(yīng)用的時(shí)候訪問(wèn)的資源可以不同,比如drawable-hdpi和drawable-xhdpi就是典型的例子。
public Resources getTopLevelResources(String resDir, int displayId,
Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
final float scale = compatInfo.applicationScale;
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
token);
Resources r;
synchronized (this) {
// Resources is app scale dependent.
if (false) {
Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
}
WeakReference<Resources> wr = mActiveResources.get(key);
r = wr != null ? wr.get() : null;
//if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
if (r != null && r.getAssets().isUpToDate()) {
if (false) {
Slog.w(TAG, "Returning cached resources " + r + " " + resDir
+ ": appScale=" + r.getCompatibilityInfo().applicationScale);
}
return r;
}
}
//if (r != null) {
// Slog.w(TAG, "Throwing away out-of-date resources!!!! "
// + r + " " + resDir);
//}
AssetManager assets = new AssetManager();
if (assets.addAssetPath(resDir) == 0) {
return null;
}
//Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
DisplayMetrics dm = getDisplayMetricsLocked(displayId);
Configuration config;
boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
final boolean hasOverrideConfig = key.hasOverrideConfiguration();
if (!isDefaultDisplay || hasOverrideConfig) {
config = new Configuration(getConfiguration());
if (!isDefaultDisplay) {
applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
}
if (hasOverrideConfig) {
config.updateFrom(key.mOverrideConfiguration);
}
} else {
config = getConfiguration();
}
r = new Resources(assets, dm, config, compatInfo, token);
if (false) {
Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
+ r.getConfiguration() + " appScale="
+ r.getCompatibilityInfo().applicationScale);
}
synchronized (this) {
WeakReference<Resources> wr = mActiveResources.get(key);
Resources existing = wr != null ? wr.get() : null;
if (existing != null && existing.getAssets().isUpToDate()) {
// Someone else already created the resources while we were
// unlocked; go ahead and use theirs.
r.getAssets().close();
return existing;
}
// XXX need to remove entries when weak references go away
mActiveResources.put(key, new WeakReference<Resources>(r));
return r;
}
}
根據(jù)上述代碼中資源的請(qǐng)求機(jī)制,再加上ResourcesManager采用單例模式,這樣就保證了不同的ContextImpl訪問(wèn)的是同一套資源,注意,這里說(shuō)的同一套資源未必是同一個(gè)資源,因?yàn)橘Y源可能位于不同的目錄,但它一定是我們的應(yīng)用的資源,或許這樣來(lái)描述更準(zhǔn)確,在設(shè)備參數(shù)和顯示參數(shù)不變的情況下,不同的ContextImpl訪問(wèn)到的是同一份資源。設(shè)備參數(shù)不變是指手機(jī)的屏幕和android版本不變,顯示參數(shù)不變是指手機(jī)的分辨率和橫豎屏狀態(tài)。也就是說(shuō),盡管Application、Activity、Service都有自己的ContextImpl,并且每個(gè)ContextImpl都有自己的mResources成員,但是由于它們的mResources成員都來(lái)自于唯一的ResourcesManager實(shí)例,所以它們看似不同的mResources其實(shí)都指向的是同一塊內(nèi)存(C語(yǔ)言的概念),因此,它們的mResources都是同一個(gè)對(duì)象(在設(shè)備參數(shù)和顯示參數(shù)不變的情況下)。在橫豎屏切換的情況下且應(yīng)用中為橫豎屏狀態(tài)提供了不同的資源,處在橫屏狀態(tài)下的ContextImpl和處在豎屏狀態(tài)下的ContextImpl訪問(wèn)的資源不是同一個(gè)資源對(duì)象。
代碼:?jiǎn)卫J降腞esourcesManager類(lèi)
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
if (sResourcesManager == null) {
sResourcesManager = new ResourcesManager();
}
return sResourcesManager;
}
}
getApplication和getApplicationContext的區(qū)別
getApplication返回結(jié)果為Application,且不同的Activity和Service返回的Application均為同一個(gè)全局對(duì)象,在ActivityThread內(nèi)部有一個(gè)列表專門(mén)用于維護(hù)所有應(yīng)用的application
final ArrayList<Application> mAllApplications = new ArrayList<Application>();
getApplicationContext返回的也是Application對(duì)象,只不過(guò)返回類(lèi)型為Context,看看它的實(shí)現(xiàn)
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
上面代碼中mPackageInfo是包含當(dāng)前應(yīng)用的包信息、比如包名、應(yīng)用的安裝目錄等,原則上來(lái)說(shuō),作為第三方應(yīng)用,包信息mPackageInfo不可能為空,在這種情況下,getApplicationContext返回的對(duì)象和getApplication是同一個(gè)。但是對(duì)于系統(tǒng)應(yīng)用,包信息有可能為空,具體就不深入研究了。從這種角度來(lái)說(shuō),對(duì)于第三方應(yīng)用,一個(gè)應(yīng)用只存在一個(gè)Application對(duì)象,且通過(guò)getApplication和getApplicationContext得到的是同一個(gè)對(duì)象,兩者的區(qū)別僅僅是返回類(lèi)型不同。
在此總結(jié)一下:
(1)Context是一個(gè)抽象類(lèi),ContextWrapper是對(duì)Context的封裝,它包含一個(gè)Context類(lèi)型的變量,ContextWrapper的功能函數(shù)內(nèi)部其實(shí)都是調(diào)用里面的Context類(lèi)型變量完成的。Application,Service,Activity等都是直接或者間接繼承自ContextWrapper,但是并沒(méi)有真正的實(shí)現(xiàn)其中的功能,Application,Service,Activity中關(guān)于Context的功能都是通過(guò)其內(nèi)部的Context類(lèi)型變量完成的,而這個(gè)變量的真實(shí)對(duì)象必定是ContextImpl,所以沒(méi)創(chuàng)建一個(gè)Application,Activity,Servcice便會(huì)創(chuàng)建一個(gè)ContextImpl,并且這些ContextImpl中的mPackages和mResources變量都是一樣的,所以不管使用Acitivty還是Service調(diào)用getResources得到相同的結(jié)果
(2)在一個(gè)apk中,Context的數(shù)量等于Activity個(gè)數(shù)+Service個(gè)數(shù)+1.
- Android 中Context的使用方法詳解
- Android編程實(shí)現(xiàn)全局獲取Context及使用Intent傳遞對(duì)象的方法詳解
- Android全局獲取Context實(shí)例詳解
- Android編程實(shí)現(xiàn)為L(zhǎng)istView創(chuàng)建上下文菜單(ContextMenu)的方法
- Android context源碼詳解及深入分析
- Android面試筆記之常問(wèn)的Context
- 談?wù)凙ndroid里的Context的使用實(shí)例
- 避免 Android中Context引起的內(nèi)存泄露
- 安卓Android Context類(lèi)實(shí)例詳解
- 詳解Android中的Context抽象類(lèi)
- Android編程獲取全局Context的方法
- Android編程中context及全局變量實(shí)例詳解
- Android中ContextMenu用法實(shí)例
- android基礎(chǔ)教程之context使用詳解
- Android獲取其他包的Context實(shí)例代碼
- android中Context深入詳解
相關(guān)文章
Android Studio搜索功能(查找功能)及快捷鍵圖文詳解
這篇文章主要介紹了Android Studio搜索功能(查找功能)及快捷鍵圖文詳解,本文圖文并茂給大家介紹的非常詳細(xì),需要的朋友可以參考下2017-12-12
Android6.0開(kāi)發(fā)中屏幕旋轉(zhuǎn)原理與流程分析
這篇文章主要介紹了Android6.0開(kāi)發(fā)中屏幕旋轉(zhuǎn)原理與流程,結(jié)合實(shí)例形式詳細(xì)分析了Android6.0屏幕旋轉(zhuǎn)的原理與相關(guān)實(shí)現(xiàn)流程,并附帶了Android動(dòng)態(tài)開(kāi)啟與禁用屏幕旋轉(zhuǎn)的實(shí)現(xiàn)方法,需要的朋友可以參考下2017-11-11
Android編程實(shí)現(xiàn)圖標(biāo)拖動(dòng)效果的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)圖標(biāo)拖動(dòng)效果的方法,涉及Android事件響應(yīng)及圖標(biāo)變換的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
Android網(wǎng)頁(yè)H5 Input選擇相機(jī)和系統(tǒng)相冊(cè)
這篇文章主要為大家詳細(xì)介紹了Android網(wǎng)頁(yè)H5 Input選擇相機(jī)和系統(tǒng)相冊(cè),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
Android菜單(動(dòng)畫(huà)菜單、360波紋菜單)
本文主要介紹了Android菜單常用集合:FragmentTabHost系統(tǒng)菜單、上移式菜單、360波紋菜單、展開(kāi)式菜單。詳解注釋,可直接拿來(lái)用。下面跟著小編一起來(lái)看下吧2017-03-03
Android 中的危險(xiǎn)權(quán)限詳細(xì)整理
這篇文章主要介紹了Android 中的危險(xiǎn)權(quán)限詳細(xì)整理的相關(guān)資料,Android 中有上百種權(quán)限,現(xiàn)在將所有的權(quán)限歸為兩類(lèi),一類(lèi)是普通權(quán)限,一類(lèi)的危險(xiǎn)權(quán)限,危險(xiǎn)權(quán)限則表示那些可能會(huì)觸及到用戶安全隱私或者對(duì)設(shè)備安全造成影響的權(quán)限,需要的朋友可以參考下2017-07-07

