Android源碼分析-全面理解Context

轉載請註明出處:http://blog.csdn.net/singwhatiwanna/article/details/21829971 (來自singwhatiwanna的博客)java

前言

Context在android中的做用不言而喻,當咱們訪問當前應用的資源,啓動一個新的activity的時候都須要提供Context,而這個Context究竟是什麼呢,這個問題好像很好回答又好像難以說清楚。從字面意思,Context的意思是「上下文」,或者也能夠叫作環境、場景等,儘管如此,仍是有點抽象。從類的繼承來講,Context做爲一個抽象的基類,它的實現子類有三種:Application、Activity和Service(姑且這麼說,暫時無論ContextWrapper等類),那麼這三種有沒有區別呢?爲何經過任意的Context訪問資源都獲得的是同一套資源呢?getApplication和getApplicationContext有什麼區別呢?應用中到底有多少個Context呢?本文將圍繞這些問題一一展開,所用源碼版本爲Android4.4。android

什麼是Context

Context是一個抽象基類,咱們經過它訪問當前包的資源(getResources、getAssets)和啓動其餘組件(Activity、Service、Broadcast)以及獲得各類服務(getSystemService),固然,經過Context能獲得的不只僅只有上述這些內容。對Context的理解能夠來講:Context提供了一個應用的運行環境,在Context的大環境裏,應用才能夠訪問資源,才能完成和其餘組件、服務的交互,Context定義了一套基本的功能接口,咱們能夠理解爲一套規範,而Activity和Service是實現這套規範的子類,這麼說也許並不許確,由於這套規範實際是被ContextImpl類統一實現的,Activity和Service只是繼承並有選擇性地重寫了某些規範的實現。數據結構

Application、Activity和Service做爲Context的區別

首先,它們都間接繼承了Context,這是它們的相同點。app

不一樣點,能夠從幾個方面來講:首先看它們的繼承關係ide

Activity的繼承關係源碼分析


Service和Application的繼承關係this


經過對比能夠清晰地發現,Service和Application的類繼承關係比較像,而Activity還多了一層繼承ContextThemeWrapper,這是由於Activity有主題的概念,而Service是沒有界面的服務,Application更是一個抽象的東西,它也是經過Activity類呈現的。.net

下面來看一下三者在Context方面的區別設計

上文已經指出,Context的真正實現都在ContextImpl中,也就是說Context的大部分方法調用都會轉到ContextImpl中,而三者的建立均在ActivityThread中完成,我以前寫過一篇文章Android源碼分析-Activity的啓動過程,在文中我指出Activity啓動的核心過程是在ActivityThread中完成的,這裏要說明的是,Application和Service的建立也是在ActivityThread中完成的。下面咱們看下三者在建立時是怎麼和ContextImpl相關聯的。code

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;
	}
	...
}
能夠看出,Activity在建立的時候會new一個ContextImpl對象並在attach方法中關聯它,須要注意的是,建立Activity使用的數據結構是ActivityClientRecord。

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);
		}
	}
	...
}
看代碼發現和Activity中ContextImpl的建立是相同的。

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採用單例模式,這樣就保證了不一樣的ContextImpl訪問的是同一套資源,注意,這裏說的同一套資源未必是同一個資源,由於資源可能位於不一樣的目錄,但它必定是咱們的應用的資源,或許這樣來描述更準確,在設備參數和顯示參數不變的狀況下,不一樣的ContextImpl訪問到的是同一份資源。設備參數不變是指手機的屏幕和android版本不變,顯示參數不變是指手機的分辨率和橫豎屏狀態。也就是說,儘管Application、Activity、Service都有本身的ContextImpl,而且每一個ContextImpl都有本身的mResources成員,可是因爲它們的mResources成員都來自於惟一的ResourcesManager實例,因此它們看似不一樣的mResources其實都指向的是同一塊內存(C語言的概念),所以,它們的mResources都是同一個對象(在設備參數和顯示參數不變的狀況下)。在橫豎屏切換的狀況下且應用中爲橫豎屏狀態提供了不一樣的資源,處在橫屏狀態下的ContextImpl和處在豎屏狀態下的ContextImpl訪問的資源不是同一個資源對象。

代碼:單例模式的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();
    }
上面代碼中mPackageInfo是包含當前應用的包信息、好比包名、應用的安裝目錄等,原則上來講,做爲第三方應用,包信息mPackageInfo不可能爲空,在這種狀況下,getApplicationContext返回的對象和getApplication是同一個。可是對於系統應用,包信息有可能爲空,具體就不深刻研究了。從這種角度來講,對於第三方應用,一個應用只存在一個Application對象,且經過getApplication和getApplicationContext獲得的是同一個對象,二者的區別僅僅是返回類型不一樣。

應用中Context的數量

到此已經很明瞭了,一個應用中Context的數量等於Activity的個數 + Service的個數 + 1,這個1爲Application。
相關文章
相關標籤/搜索