Android技術分享| Context淺析

語言: CN / TW / HK

類繼承圖

我們來看下關於 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()——> [email protected]
getBaseContext()       ——> [email protected]

我們發現當我們通過 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();  
        //...
    }  
}

在這裏插入圖片描述