深入解析Android App開發(fā)中Context的用法
Context在開發(fā)Android應(yīng)用的過程中扮演著非常重要的角色,比如啟動(dòng)一個(gè)Activity需要使用context.startActivity方法,將一個(gè)xml文件轉(zhuǎn)換為一個(gè)View對(duì)象也需要使用Context對(duì)象,可以這么說,離開了這個(gè)類,Android開發(fā)寸步難行,對(duì)于這樣一個(gè)類,我們又對(duì)他了解多少呢。我就說說我的感受吧,在剛開始學(xué)習(xí)Android開發(fā)時(shí),感覺使用Context的地方一直就是傳入一個(gè)Activity對(duì)象,久而久之感覺只要是Context的地方就傳入一個(gè)Activity就行了,那么我們現(xiàn)在就來詳細(xì)的分析一下Context和Activity的關(guān)系吧!
在開始本文之前我們先放置一個(gè)問題在這里:
我們平時(shí)在獲取項(xiàng)目資源時(shí)使用context.getResources()的時(shí)候?yàn)槭裁捶呕氐氖峭粋€(gè)值,明明是使用不同的Activity調(diào)用getResources返回結(jié)果卻是一樣的。
Context本身是一個(gè)純的abstract類,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中方法的類。
從上面的繼承關(guān)系來看,每一個(gè)Activity就是一個(gè)Context,每一個(gè)Service就是一個(gè)Context,這也就是為什么使用Context的地方可以被Activity或者Service替換了。
創(chuàng)建Context
根據(jù)前面所說,由于實(shí)現(xiàn)了Context的只有ContextImpl類,Activity和Service本沒有真正的實(shí)現(xiàn),他們只是內(nèi)部包含了一個(gè)真實(shí)的Context對(duì)象而已,也就是在在創(chuàng)建Activity或者Service的時(shí)候肯定要?jiǎng)?chuàng)建愛你一個(gè)ContextImpl對(duì)象,并賦值到Activity中的Context類型變量中。那我們就來看看Andorid源碼中有哪些地方創(chuàng)建了ContextImpl.
據(jù)統(tǒng)計(jì)Android中創(chuàng)建ContextImpl的地方一共有7處:
- 在PackageInfo.makeApplication()中
- 在performLaunchActivity()中
- 在handleCreateBackupAgent()中
- 在handleCreateService()中
- 2次在hanldBinderAppplication()中
- 在attach()方法中
由于創(chuàng)建ContextImpl的基本原理類似,所以這里只會(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類型的,到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中獲取,如果沒有,則new一個(gè)出來,并放入mPackages里面去,注意,這里的mPackages是ActivityThread中的屬性。
下面繼續(xù)分析一下LoadedApk這個(gè)類中的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類型變量(Application也是繼承ContextWrapper)
2、Activity中的Context
在創(chuàng)建一個(gè)Activity時(shí),經(jīng)過輾轉(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)分析過了,稍微有點(diǎn)區(qū)別,但是大致流程是差不多的,所以此處的appContext執(zhí)行init之后,其中的mPackages變量和mResources變量時(shí)一樣的,activity通過attach函數(shù)將該appContext賦值到ContextWrapper中的Context類型變量。
3、Service中的Context
同樣 在創(chuàng)建一個(gè)Service時(shí),經(jīng)過輾轉(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ì)資源的訪問
很明確,不同的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)需要說明的是為什么會(huì)有多個(gè)資源對(duì)象,原因很簡(jiǎn)單,因?yàn)閞es下可能存在多個(gè)適配不同設(shè)備、不同分辨率、不同系統(tǒng)版本的目錄,按照android系統(tǒng)的設(shè)計(jì),不同設(shè)備在訪問同一個(gè)應(yīng)用的時(shí)候訪問的資源可以不同,比如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訪問的是同一套資源,注意,這里說的同一套資源未必是同一個(gè)資源,因?yàn)橘Y源可能位于不同的目錄,但它一定是我們的應(yīng)用的資源,或許這樣來描述更準(zhǔn)確,在設(shè)備參數(shù)和顯示參數(shù)不變的情況下,不同的ContextImpl訪問到的是同一份資源。設(shè)備參數(shù)不變是指手機(jī)的屏幕和android版本不變,顯示參數(shù)不變是指手機(jī)的分辨率和橫豎屏狀態(tài)。也就是說,盡管Application、Activity、Service都有自己的ContextImpl,并且每個(gè)ContextImpl都有自己的mResources成員,但是由于它們的mResources成員都來自于唯一的ResourcesManager實(shí)例,所以它們看似不同的mResources其實(shí)都指向的是同一塊內(nèi)存(C語(yǔ)言的概念),因此,它們的mResources都是同一個(gè)對(duì)象(在設(shè)備參數(shù)和顯示參數(shù)不變的情況下)。在橫豎屏切換的情況下且應(yīng)用中為橫豎屏狀態(tài)提供了不同的資源,處在橫屏狀態(tài)下的ContextImpl和處在豎屏狀態(tài)下的ContextImpl訪問的資源不是同一個(gè)資源對(duì)象。
代碼:?jiǎn)卫J降腞esourcesManager類
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è)列表專門用于維護(hù)所有應(yīng)用的application
final ArrayList<Application> mAllApplications = new ArrayList<Application>();
getApplicationContext返回的也是Application對(duì)象,只不過返回類型為Context,看看它的實(shí)現(xiàn)
@Override public Context getApplicationContext() { return (mPackageInfo != null) ? mPackageInfo.getApplication() : mMainThread.getApplication(); }
上面代碼中mPackageInfo是包含當(dāng)前應(yīng)用的包信息、比如包名、應(yīng)用的安裝目錄等,原則上來說,作為第三方應(yīng)用,包信息mPackageInfo不可能為空,在這種情況下,getApplicationContext返回的對(duì)象和getApplication是同一個(gè)。但是對(duì)于系統(tǒng)應(yīng)用,包信息有可能為空,具體就不深入研究了。從這種角度來說,對(duì)于第三方應(yīng)用,一個(gè)應(yīng)用只存在一個(gè)Application對(duì)象,且通過getApplication和getApplicationContext得到的是同一個(gè)對(duì)象,兩者的區(qū)別僅僅是返回類型不同。
在此總結(jié)一下:
(1)Context是一個(gè)抽象類,ContextWrapper是對(duì)Context的封裝,它包含一個(gè)Context類型的變量,ContextWrapper的功能函數(shù)內(nèi)部其實(shí)都是調(diào)用里面的Context類型變量完成的。Application,Service,Activity等都是直接或者間接繼承自ContextWrapper,但是并沒有真正的實(shí)現(xiàn)其中的功能,Application,Service,Activity中關(guān)于Context的功能都是通過其內(nèi)部的Context類型變量完成的,而這個(gè)變量的真實(shí)對(duì)象必定是ContextImpl,所以沒創(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面試筆記之常問的Context
- 談?wù)凙ndroid里的Context的使用實(shí)例
- 避免 Android中Context引起的內(nèi)存泄露
- 安卓Android Context類實(shí)例詳解
- 詳解Android中的Context抽象類
- Android編程獲取全局Context的方法
- Android編程中context及全局變量實(shí)例詳解
- Android中ContextMenu用法實(shí)例
- android基礎(chǔ)教程之context使用詳解
- Android獲取其他包的Context實(shí)例代碼
- android中Context深入詳解
相關(guān)文章
很贊的引導(dǎo)界面效果Android控件ImageSwitcher實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Android控件ImageSwitcher如何實(shí)現(xiàn)很贊的引導(dǎo)界面的具體代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05Android設(shè)置PreferenceCategory背景顏色的方法
這篇文章主要介紹了Android設(shè)置PreferenceCategory背景顏色的方法,涉及Android設(shè)置背景色的技巧,需要的朋友可以參考下2015-05-05Android ActionBar完全解析使用官方推薦的最佳導(dǎo)航欄(上)
Action Bar是一種新増的導(dǎo)航欄功能,在Android 3.0之后加入到系統(tǒng)的API當(dāng)中,它標(biāo)識(shí)了用戶當(dāng)前操作界面的位置,并提供了額外的用戶動(dòng)作、界面導(dǎo)航等功能2017-04-04詳解android 用webview加載網(wǎng)頁(yè)(https和http)
這篇文章主要介紹了詳解android 用webview加載網(wǎng)頁(yè)(https和http),詳細(xì)的介紹了兩個(gè)錯(cuò)誤的解決方法,有興趣的可以了解一下2017-11-11Android實(shí)現(xiàn)短信驗(yàn)證碼自動(dòng)攔截讀取功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)短信驗(yàn)證碼自動(dòng)攔截讀取功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08android獲取屏幕高度和寬度的實(shí)現(xiàn)方法
這篇文章主要介紹了android獲取屏幕高度和寬度的實(shí)現(xiàn)方法,較為詳細(xì)的分析了Android獲取屏幕高度和寬度的原理與實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-01-01Android 圖片切換器(dp、sp、px) 的單位轉(zhuǎn)換器
這篇文章主要介紹了Android 圖片切換器(dp、sp、px) 的單位轉(zhuǎn)換器的相關(guān)資料,需要的朋友可以參考下2017-03-03