Context

什麼是Context

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

Context相關類的繼承關係


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

相關類介紹:

Context 

路徑:/frameworks/base/core/java/android/content/Context.java

說明:抽象類,提供了一組通用的API。

源代碼(部分)如下:

public abstract class Context {
	 ...
 	 public abstract Object getSystemService(String name);  //獲得系統級服務
	 public abstract void startActivity(Intent intent);     //通過一個Intent啓動Activity
	 public abstract ComponentName startService(Intent service);  //啓動Service
	 //根據文件名得到SharedPreferences對象
	 public abstract SharedPreferences getSharedPreferences(String name,int mode);
	 ...
}

ContextIml.java

路徑 :/frameworks/base/core/java/android/app/ContextImpl.java

說明:該Context類的實現類爲ContextIml,該類實現了Context類的功能。請注意,該函數的大部分功能都是直接調用其屬性mPackageInfo去完成,這點我們後面會講到。

源代碼(部分)如下:

/**
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 */
class ContextImpl extends Context{
	//所有Application程序公用一個mPackageInfo對象
    /*package*/ ActivityThread.PackageInfo mPackageInfo;
    
    @Override
    public Object getSystemService(String name){
    	...
    	else if (ACTIVITY_SERVICE.equals(name)) {
            return getActivityManager();
        } 
    	else if (INPUT_METHOD_SERVICE.equals(name)) {
            return InputMethodManager.getInstance(this);
        }
    } 
    @Override
    public void startActivity(Intent intent) {
    	...
    	//開始啓動一個Activity
        mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null, null, intent, -1);
    }
}

ContextWrapper

路徑 :\frameworks\base\core\java\android\content\ContextWrapper.java

說明: 正如其名稱一樣,該類只是對Context類的一種包裝,該類的構造函數包含了一個真正的Context引用,即ContextIml對象。

源代碼(部分)如下:

public class ContextWrapper extends Context {
    Context mBase;  //該屬性指向一個ContextIml實例,一般在創建Application、Service、Activity時賦值
    
    //創建Application、Service、Activity,會調用該方法給mBase屬性賦值
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
    @Override
    public void startActivity(Intent intent) {
        mBase.startActivity(intent);  //調用mBase實例方法
    }
}

ContextThemeWrapper

路徑:/frameworks/base/core/java/android/view/ContextThemeWrapper.java

說明:該類內部包含了主題(Theme)相關的接口,即android:theme屬性指定的。只有Activity需要主題,Service不需要主題,所以Service直接繼承於ContextWrapper類。

源代碼(部分)如下:

public class ContextThemeWrapper extends ContextWrapper {
	 //該屬性指向一個ContextIml實例,一般在創建Application、Service、Activity時賦值
	 
	 private Context mBase;
	//mBase賦值方式同樣有一下兩種
	 public ContextThemeWrapper(Context base, int themeres) {
	        super(base);
	        mBase = base;
	        mThemeResource = themeres;
	 }

	 @Override
	 protected void attachBaseContext(Context newBase) {
	        super.attachBaseContext(newBase);
	        mBase = newBase;
	 }
}

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:

finalArrayList<Application> mAllApplications  = newArrayList<Application>();

getApplicationContext返回的也是Application對象,只不過返回類型爲Context,看看它的實現

@Override  
public Context getApplicationContext() {  
    return (mPackageInfo != null) ?  
            mPackageInfo.getApplication() : mMainThread.getApplication();  
}

上面代碼中mPackageInfo是包含當前應用的包信息、比如包名、應用的安裝目錄等,原則上來說,作爲第三方應用,包信息mPackageInfo不可能爲空,在這種情況下,getApplicationContext返回的對象和getApplication是同一個。但是對於系統應用,包信息有可能爲空,具體就不深入研究了。從這種角度來說,對於第三方應用,一個應用只存在一個Application對象,且通過getApplication和getApplicationContext得到的是同一個對象,兩者的區別僅僅是返回類型不同。

具體創建Context的時機

Context的真正實現都在ContextImpl中,也就是說Context的大部分方法調用都會轉到ContextImpl中,而三者的創建均在ActivityThread中完成

應用程序創建Context實例的情況有如下幾種情況:

1.創建Application對象時, 而且整個App共一個Application對象

2.創建Service對象時

3.創建Activity對象時

因此應用程序App共有的Context數目公式爲:

總Context實例個數 = Service個數 + Activity個數 + 1(Application對應的Context實例)

創建Application對象的時機

每個應用程序在第一次啓動時,都會首先創建Application對象。如果對應用程序啓動一個Activity(startActivity)流程比較清楚的話,創建Application的時機在創建handleBindApplication()方法中,該函數位於ActivityThread.java類中,如下:

//創建Application時同時創建的ContextIml實例
private final void handleBindApplication(AppBindData data){
    ...
	///創建Application對象
    Application app = data.info.makeApplication(data.restrictedBackupMode, null);
    ...
}

public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
	...
	try {
        java.lang.ClassLoader cl = getClassLoader();
        ContextImpl appContext = new ContextImpl();    //創建一個ContextImpl對象實例
        appContext.init(this, null, mActivityThread);  //初始化該ContextIml實例的相關屬性
        ///新建一個Application對象 
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
       appContext.setOuterContext(app);  //將該Application實例傳遞給該ContextImpl實例         
    } 
	...
}

創建Activity對象的時機

通過startActivity()或startActivityForResult()請求啓動一個Activity時,如果系統檢測需要新建一個Activity對象時,就會回調handleLaunchActivity()方法,該方法繼而調用performLaunchActivity()方法,去創建一個Activity實例,並且回調onCreate(),onStart()方法等,函數都位於 ActivityThread.java類,如下:

//創建一個Activity實例時同時創建ContextIml實例
private final void handleLaunchActivity(ActivityRecord r, Intent customIntent) {
	...
	Activity a = performLaunchActivity(r, customIntent);  //啓動一個Activity
}
private final Activity performLaunchActivity(ActivityRecord r, Intent customIntent) {
	...
	Activity activity = null;
    try {
    	//創建一個Activity對象實例
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    }
    if (activity != null) {
        ContextImpl appContext = new ContextImpl();      //創建一個Activity實例
        appContext.init(r.packageInfo, r.token, this);   //初始化該ContextIml實例的相關屬性
        appContext.setOuterContext(activity);            //將該Activity信息傳遞給該ContextImpl實例
        ...
    }
    ...    
}

創建Service對象的時機

通過startService或者bindService時,如果系統檢測到需要新創建一個Service實例,就會回調handleCreateService()方法,完成相關數據操作。handleCreateService()函數位於ActivityThread.java類,如下:

//創建一個Service實例時同時創建ContextIml實例
private final void handleCreateService(CreateServiceData data){
	...
	//創建一個Service實例
	Service service = null;
    try {
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        service = (Service) cl.loadClass(data.info.name).newInstance();
    } catch (Exception e) {
    }
	...
	ContextImpl context = new ContextImpl(); //創建一個ContextImpl對象實例
    context.init(packageInfo, null, this);   //初始化該ContextIml實例的相關屬性
    //獲得我們之前創建的Application對象信息
    Application app = packageInfo.makeApplication(false, mInstrumentation);
    //將該Service信息傳遞給該ContextImpl實例
    context.setOuterContext(service);
    ...
}

另外,需要強調一點的是,通過對ContextImp的分析可知,其方法的大多數操作都是直接調用其屬性mPackageInfo(該屬性類型爲PackageInfo)的相關方法而來。這說明ContextImp是一種輕量級類,而PackageInfo纔是真正重量級的類。而一個App裏的所有ContextIml實例,都對應同一個packageInfo對象。

 

整理自:

http://www.cnblogs.com/android100/p/Android-Context.html

http://blog.csdn.net/qinjuning/article/details/7310620




















發佈了8 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章