轉載請註明出處:http://blog.csdn.net/singwhatiwanna/article/details/21829971 (來自singwhatiwanna的博客)
前言
Context在android中的作用不言而喻,當我們訪問當前應用的資源,啓動一個新的activity的時候都需要提供Context,而這個Context到底是什麼呢,這個問題好像很好回答又好像難以說清楚。從字面意思,Context的意思是“上下文”,或者也可以叫做環境、場景等,儘管如此,還是有點抽象。從類的繼承來說,Context作爲一個抽象的基類,它的實現子類有三種:Application、Activity和Service(姑且這麼說,暫時不管ContextWrapper等類),那麼這三種有沒有區別呢?爲什麼通過任意的Context訪問資源都得到的是同一套資源呢?getApplication和getApplicationContext有什麼區別呢?應用中到底有多少個Context呢?本文將圍繞這些問題一一展開,所用源碼版本爲Android4.4。
什麼是Context
Context是一個抽象基類,我們通過它訪問當前包的資源(getResources、getAssets)和啓動其他組件(Activity、Service、Broadcast)以及得到各種服務(getSystemService),當然,通過Context能得到的不僅僅只有上述這些內容。對Context的理解可以來說:Context提供了一個應用的運行環境,在Context的大環境裏,應用纔可以訪問資源,才能完成和其他組件、服務的交互,Context定義了一套基本的功能接口,我們可以理解爲一套規範,而Activity和Service是實現這套規範的子類,這麼說也許並不準確,因爲這套規範實際是被ContextImpl類統一實現的,Activity和Service只是繼承並有選擇性地重寫了某些規範的實現。
Application、Activity和Service作爲Context的區別
首先,它們都間接繼承了Context,這是它們的相同點。
不同點,可以從幾個方面來說:首先看它們的繼承關係
Activity的繼承關係
Service和Application的繼承關係
通過對比可以清晰地發現,Service和Application的類繼承關係比較像,而Activity還多了一層繼承ContextThemeWrapper,這是因爲Activity有主題的概念,而Service是沒有界面的服務,Application更是一個抽象的東西,它也是通過Activity類呈現的。
下面來看一下三者在Context方面的區別
上文已經指出,Context的真正實現都在ContextImpl中,也就是說Context的大部分方法調用都會轉到ContextImpl中,而三者的創建均在ActivityThread中完成,我之前寫過一篇文章Android源碼分析-Activity的啓動過程,在文中我指出Activity啓動的核心過程是在ActivityThread中完成的,這裏要說明的是,Application和Service的創建也是在ActivityThread中完成的。下面我們看下三者在創建時是怎麼和ContextImpl相關聯的。
Activity對象中ContextImpl的創建
代碼爲ActivityThread中的performLaunchActivity方法
- if (activity != null) {
- Context appContext = createBaseContextForActivity(r, activity);
- /**
- * createBaseContextForActivity中創建ContextImpl的代碼
- * ContextImpl appContext = new ContextImpl();
- * appContext.init(r.packageInfo, r.token, this);
- * appContext.setOuterContext(activity);
- */
- CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
- Configuration config = new Configuration(mCompatConfiguration);
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
- + r.activityInfo.name + " with config " + config);
- activity.attach(appContext, this, getInstrumentation(), r.token,
- r.ident, app, r.intent, r.activityInfo, title, r.parent,
- r.embeddedID, r.lastNonConfigurationInstances, config);
- if (customIntent != null) {
- activity.mIntent = customIntent;
- }
- ...
- }
Application對象中ContextImpl的創建
代碼在ActivityThread中的handleBindApplication方法中,此方法內部調用了makeApplication方法
- public Application makeApplication(boolean forceDefaultAppClass,
- Instrumentation instrumentation) {
- if (mApplication != null) {
- return mApplication;
- }
- Application app = null;
- String appClass = mApplicationInfo.className;
- if (forceDefaultAppClass || (appClass == null)) {
- appClass = "android.app.Application";
- }
- try {
- java.lang.ClassLoader cl = getClassLoader();
- 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);
- }
- }
- ...
- }
Service對象中ContextImpl的創建
通過查看代碼發現和Activity、Application是一致的。分析到這裏,那麼三者的Context有什麼區別呢?沒有區別嗎?儘管如此,有一些細節是確定的:Dialog的使用需要Activity,在桌面上我們採用Application的Context無法彈出對話框,同時在桌面上想啓動新的activity,我們需要爲intent設置FLAG_ACTIVITY_NEW_TASK標誌,否則無法啓動activity,這一切都說明,起碼Application的Context和Activity的Context還是有區別的,當然這也可能不是Context的區別,因爲在桌面上,我們的應用沒有界面,這意味着我們能幹的事情可能受到了限制,事情的細節目前我還沒有搞的很清楚。
Context對資源的訪問
很明確,不同的Context得到的都是同一份資源。這是很好理解的,請看下面的分析
得到資源的方式爲context.getResources,而真正的實現位於ContextImpl中的getResources方法,在ContextImpl中有一個成員 private Resources mResources,它就是getResources方法返回的結果,mResources的賦值代碼爲:
mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);
下面看一下ResourcesManager的getTopLevelResources方法,這個方法的思想是這樣的:在ResourcesManager中,所有的資源對象都被存儲在ArrayMap中,首先根據當前的請求參數去查找資源,如果找到了就返回,否則就創建一個資源對象放到ArrayMap中。有一點需要說明的是爲什麼會有多個資源對象,原因很簡單,因爲res下可能存在多個適配不同設備、不同分辨率、不同系統版本的目錄,按照android系統的設計,不同設備在訪問同一個應用的時候訪問的資源可以不同,比如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;
- }
- }
代碼:單例模式的ResourcesManager類
- public static ResourcesManager getInstance() {
- synchronized (ResourcesManager.class) {
- if (sResourcesManager == null) {
- sResourcesManager = new ResourcesManager();
- }
- return sResourcesManager;
- }
- }
getApplication和getApplicationContext的區別
getApplication返回結果爲Application,且不同的Activity和Service返回的Application均爲同一個全局對象,在ActivityThread內部有一個列表專門用於維護所有應用的application:
final ArrayList<Application> mAllApplications = new ArrayList<Application>()
爲什麼說getApplication返回的都是同一個Application對象呢,是因爲Activity和Service的getApplication返回的Application對象是由ActivityThread創建它們的時候通過它們的attach方法來傳遞給它們的,也就是說所有Activity和Service所持有的Application均是ActivityThread內部的Application,由於一個應用只有一個包信息,所以ActivityThread內部只可能創建出一個Application,原因是當執行packageInfo.makeApplication的時候,如果已經創建過Application了,packageInfo.makeApplication方法就不會再創建新的Application。關於一個應用只有一個包信息,從代碼的邏輯來看的確是這樣的,在ActivityThread內部同樣有一個列表專門用於維護所有應用的包信息:
final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<String, WeakReference<LoadedApk>>()
getApplicationContext返回的也是Application對象,只不過返回類型爲Context,看看它的實現
- @Override
- public Context getApplicationContext() {
- return (mPackageInfo != null) ?
- mPackageInfo.getApplication() : mMainThread.getApplication();
- }