Android中關於Context的三言兩語

crazyandcoder發表於2019-11-26

前言

今天我們來分析一下 Context 的原始碼,在 APP 開發中,我們會經常用到 Context ,那麼什麼是 Context 呢?它的常規語義是“上下文”那麼這個“上下文”到底是什麼呢?通過原始碼分析,我們能對這個Context有個基本的認識。

類繼承圖

我們來看下關於 Context 的類繼承圖,我們通過檢視原始碼得知,Context 是一個抽象類,所以它肯定有其實現類,查閱得知它的實現類為 ContextWrapper 和 ContextImpl ,所以它的繼承圖如下:

在這裡插入圖片描述

以上的 Context 類繼承關係清晰簡潔,可以得知,Application 、 Service 、Activity 都是繼承的 Context 類,所以從這裡我們可以得知:

Context 數量 = Activity 數量 + Service 數量 + 1
複製程式碼

另外,我們可以看到 Application 和 Service 都是直接繼承 ContextWrapper 的而 Activity 卻是繼承 ContextThemeWrapper 的,這是為何?其實 ContextThemeWrapper 是關於主題類的,Activity 是有介面的,而 Application 和 Service 卻沒有。接下來我們來詳細看下它們的原始碼實現。

ContextWrapper

我們進入到 ContextWrapper 原始碼中可以發現,它其實呼叫了 mBase 裡面的方法,而 mBase 其實是 ContextImpl ,所以最終還是得呼叫它的實現類 ContextImpl 類裡面的方法。

public class ContextWrapper extends Context {
    Context mBase;
    public ContextWrapper(Context base) {
        mBase = base;
    }
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
    //其餘的都是覆蓋Context裡面的方法
}
複製程式碼

我們可以按照上面的類的繼承圖進行依次分析,由上面可以知道 ContextWrapper 其實是呼叫 ContextImpl 裡面的方法,所以 Application 和 Service 還有 Activity 它們應該都跟 ContextImpl 有關的。到底是不是這樣的呢?我們追蹤原始碼進行分析。

Application

類似於 Java 的 main 啟動方法程式,Android 也有一個類似的方法,那就是在 ActivityThread 類中也有一個 main ,這是開始的地方,我們從這裡進行一點一點跟蹤:

ActivityThread#main

      //省略部分程式碼...
	  Looper.prepareMainLooper();
      ActivityThread thread = new ActivityThread();
      thread.attach(false);
      //省略部分程式碼...
      Looper.loop();      
      //省略部分程式碼...
複製程式碼

我們找到 ActivityThread 的 main 方法,省略無關程式碼,這個 main 方法就是不斷的從訊息佇列中獲取訊息,然後進行處理。我們本次不分析 Looper 相關的東西,只分析跟 Context 有關的內容,繼續進入 attach 方法,

Android 分析原始碼,不能一頭扎進去,我們應該主要分析它的流程。

ActivityThread#attach

//省略部分程式碼...
				mInstrumentation = new Instrumentation();
                ContextImpl context = ContextImpl.createAppContext(
                        this, getSystemContext().mPackageInfo);
				//Application的例項建立
                mInitialApplication = context.mPackageInfo.makeApplication(true, null);
                
                //呼叫Application裡面的生命週期方法onCreate
                mInitialApplication.onCreate();
//省略部分程式碼...
複製程式碼

這裡面出現了 ContextImpl ,所以下面應該會跟 Application 扯上關係,所以進入到 makeApplication 方法中繼續往下追蹤,

LoadedApk#makeApplication

//省略部分程式碼...
  Application app = null;
 ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
//省略部分程式碼...
複製程式碼

最終又進入到 Instrumentation#newApplication 方法裡面

Instrumentation#newApplication

static public Application newApplication(Class<?> clazz, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = (Application)clazz.newInstance();
        app.attach(context);
        return app;
    }
複製程式碼

Application#attach

	/**
    * @hide
    */
   /* package */ 
   final void attach(Context context) {
       attachBaseContext(context);
       mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
   }
複製程式碼

走到這裡就很明清晰了,最終將會呼叫 ContextWrapper 的 attachBaseContext 方法。從上面到這裡,如預料的一樣,分析到這裡,記住了多少?是不是隻知道 Application 裡面最終會呼叫 attachBaseContext 這個方法?這樣的話就對了,不能一頭扎進程式碼的海洋裡,到處遨遊,那樣會迷失方向的,Android 原始碼那麼大,那麼多,一一細節分析根本是不大可能的,所以只能把握流程,然後再針對性的分析實現過程。接著分析 Service 裡面相關的方法。

Service

對於 Service ,我們在 ActivityThread 中可以發現有個方法叫 handleCreateService ,這裡面有關於 Service 和 ContextImpl 之間的聯絡。

ActivityThread#handleCreateService

Service service = null;
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
           context.setOuterContext(service);
           Application app = packageInfo.makeApplication(false, mInstrumentation);
           service.attach(context, this, data.info.name, data.token, app,
                   ActivityManager.getService());
           service.onCreate();
複製程式碼

對於 Application 的那段程式碼我們可以發現,這兩者及其類似,我們進入到 attach 方法中檢視相關程式碼,發現

/**
    * @hide
    */
   public final void attach(
           Context context,
           ActivityThread thread, String className, IBinder token,
           Application application, Object activityManager) {
	//呼叫attachBaseContext方法
       attachBaseContext(context);
       mThread = thread;           // NOTE:  unused - remove?
       mClassName = className;
       mToken = token;
       mApplication = application;
       mActivityManager = (IActivityManager)activityManager;
       mStartCompatibility = getApplicationInfo().targetSdkVersion
               < Build.VERSION_CODES.ECLAIR;
   }
複製程式碼

程式碼很簡單,就是這樣跟 ContextImpl 扯上關係的。因為 Service 和 Application 都是繼承的 ContextWrapper 類,接下來我們來分析一下關於 Activity 的程式碼。

Activity

在這裡說明一下為什麼 Service 和 Application 都是繼承的 ContextWrapper 類而 Activity 卻是繼承 ContextThemeWrapper 那是因為 Activity 是帶有介面顯示的,而 Service 和 Application 卻沒有,所以從名字我們可以看到 ContextThemeWrapper 包含主題的資訊,同時 ContextThemeWrapper 卻又是繼承自 ContextWrapper ,分析 ContextThemeWrapper 原始碼我們可以看到,裡面基本都是關於 theme 的方法,同時它也覆蓋了 attachBaseContext 方法。

我們進入 Activity 原始碼也發現它也有和 Service 類似的 attach 方法

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
		//省略部分程式碼...
        attachBaseContext(context);
複製程式碼

接下來我們來分析一下 Activity 在哪裡和這個扯上關係的。

ActivityThread#performLaunchActivity

performLaunchActivity 這個方法其實就是啟動 Activity 的方法 ,我們以後再來學習關於這個方法的內容,現在先分析 Context 的內容。我們進入到這個方法檢視:

//省略部分程式碼...
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
//省略部分程式碼...
  activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
//省略部分程式碼...
複製程式碼

首先通過 createBaseContextForActivity 方法建立ContextImpl 然後直接有 Activity attach 進去。到此為止,關於 Application 、Service 和 Activity 關於Context 的原始碼基本就差不多了。接下來我們來解決一些實際的內容。

例項理解

既然 Application、Service 和 Activity 都有 Context 那麼它們之間到底有啥區別呢?同時 getApplicationContext 和 getApplication() 又有什麼區別呢?接下來我們通過程式碼進行驗證。

我們現在的專案一般都有自定義 Application 的類進行一些初始化操作,本例中也新建一個 MyApplication 的類繼承自 Application,然後在Manifest.xml中進行註冊,程式碼如下:

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("androidos_analysis", "getApplicationContext()——> " + getApplicationContext());
        Log.d("androidos_analysis", "getBaseContext()       ——> " + getBaseContext());
    }
}
複製程式碼

列印結果如下:

getApplicationContext()——> com.ihidea.androidosanalysis.MyApp@9831cf9
getBaseContext()       ——> android.app.ContextImpl@13d643e
複製程式碼

我們發現當我們通過 getApplicationContext 獲取的是我們申明的 Application 例項,而通過 getBaseContext 獲取到的卻是 ContextImpl 這是為什麼呢?我們檢視它們的實現發現

ContextWrapper#getBaseContext

/**
   * @return the base context as set by the constructor or setBaseContext
   */
  public Context getBaseContext() {
      return mBase;
  }
複製程式碼

其實在上文我們已經分析過了它們的原始碼,我們知道其實這個mBase就是 ContextImpl 了。而 getApplicationContext

ContextWrapper#getApplicationContext

@Override
  public Context getApplicationContext() {
      return mBase.getApplicationContext();
  }
複製程式碼

通過上面分析我們知道 其實 Application 它本身也是一個 Context 所以,這個們返回的就是它自己了。所以這裡獲取getApplicationContext()得到的結果就是MyApplication本身的例項。

有時候我們程式碼裡面也會有關於 getApplication 的用法,那麼 這個跟 getApplicationContext 又有什麼區別呢?我們再來log一下就知道了。

我們建立一個 MainActivity 然後在裡面列印兩行程式碼:

MainActivity#onCreate

Log.d("androidos_analysis", "getApplicationContext()——> " + getApplicationContext());
Log.d("androidos_analysis", "getApplication()       ——> " + getApplication());
複製程式碼

在這裡插入圖片描述

我們可以發現 這兩個返回的結果都是 一樣的,其實不難理解,

Activity#getApplication

/** Return the application that owns this activity. */
   public final Application getApplication() {
       return mApplication;
   }
複製程式碼

其實 getApplication 返回的就是 Application 所以這兩者是一樣的了。但是都是返回的 Application ,Android 為什麼要存在這兩個方法呢?這就涉及到作用域的問題了,我們可以發現使用 getApplication 的方法的作用範圍是 Activity 和 Service ,但是我們在其他地方卻不能使用這個方法,這種情況下我們就可以使用 getApplicationContext 來獲取 Application 了。什麼情況下呢?譬如:BroadcastReceiver 我們想在Receiver 中獲取 Application 的例項我們就可以通過這種方式來獲取:

public class MyReceiver extends BroadcastReceiver {  
    @Override  
    public void onReceive(Context context, Intent intent) { 
        MyApplication myApp = (MyApplication) context.getApplicationContext();  
        //...
    }  
}
複製程式碼

以上內容就是關於 Context 的部分內容。

關於作者

專注於 Android 開發多年,喜歡寫 blog 記錄總結學習經驗,blog 同步更新於本人的公眾號,歡迎大家關注,一起交流學習~

在這裡插入圖片描述

相關文章