一篇文章讓你徹底弄懂Context到底是什麼,不懂還怎麼做Android開發?

前言

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

類繼承圖

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

一篇文章讓你徹底弄懂Context到底是什麼,不懂還怎麼做Android開發?

以上的 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());

一篇文章讓你徹底弄懂Context到底是什麼,不懂還怎麼做Android開發?

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

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 的部分內容。

最後對於程序員來說,要學習的知識內容、技術有太多太多,要想不被環境淘汰就只有不斷提升自己,從來都是我們去適應環境,而不是環境來適應我們!

這裏附上上述的技術體系圖相關的幾十套騰訊、頭條、阿里、美團等公司19年的面試題,把技術點整理成了視頻和PDF(實際上比預期多花了不少精力),包含知識脈絡 + 諸多細節,由於篇幅有限,這裏以圖片的形式給大家展示一部分。

相信它會給大家帶來很多收穫:

一篇文章讓你徹底弄懂Context到底是什麼,不懂還怎麼做Android開發?

上述【高清技術腦圖】以及【配套的架構技術PDF】可以 加我wx:X1524478394 免費獲取

當程序員容易,當一個優秀的程序員是需要不斷學習的,從初級程序員到高級程序員,從初級架構師到資深架構師,或者走向管理,從技術經理到技術總監,每個階段都需要掌握不同的能力。早早確定自己的職業方向,才能在工作和能力提升中甩開同齡人。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章