1. 程式人生 > >Android原始碼分析-全面理解Context

Android原始碼分析-全面理解Context

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

轉載請註明出處: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; } ...}
可以看出,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。

           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述