Android應用程式視窗(Activity)的執行上下文環境(Context)的建立過程分析

weixin_34377065發表於2013-05-22

出自:http://blog.csdn.net/luoshengyang/article/details/8201936

  在前文中,我們簡要介紹了Android應用程式視窗的框架。Android應用程式視窗在執行的過程中,需要訪問一些特定的資源或者類。這些特定的資源或者類構成了Android應用程式的執行上下文環境,Android應用程式視窗可以通過一個Context介面來訪問它,這個Context介面也是我們在開發應用程式時經常碰到的。在本文中,我們就將詳細分析Android應用程式視窗的執行上下文環境的建立過程。

        在前面Android應用程式視窗(Activity)實現框架簡要介紹和學習計劃一文中提到,Android應用程式視窗的執行上下文環境是通過ContextImpl類來描述的,即每一個Activity元件都關聯有一個ContextImpl物件。ContextImpl類繼承了Context類,它與Activity元件的關係如圖1所示:


圖1 ContextImpl類與Activity類的關係圖

        這個類圖在設計模式裡面就可以稱為裝飾模式。Activity元件通過其父類ContextThemeWrapper和ContextWrapper的成員變數mBase來引用了一個ContextImpl物件,這樣,Activity元件以後就可以通過這個ContextImpl物件來執行一些具體的操作,例如,啟動Service元件註冊廣播接收者啟動Content Provider元件等操作。同時,ContextImpl類又通過自己的成員變數mOuterContext來引用了與它關聯的一個Activity元件,這樣,ContextImpl類也可以將一些操作轉發給Activity元件來處理。

        在前面Android應用程式啟動過程原始碼分析一文中,我們已經詳細分析過一個Activity元件的啟動過程了。在這個啟動過程中,最後一步便是通過ActivityThread類的成員函式performLaunchActivity在應用程式程式中建立一個Activity例項,並且為它設定執行上下文環境,即為它建立一個ContextImpl物件。接下來,我們就從ActivityThread類的成員函式performLaunchActivity開始,分析一個Activity例項的建立過程,以便可以從中瞭解它的執行上下文環境的建立過程,如圖2所示:

圖2 Android應用程式視窗的執行上下文環境的建立過程

        這個過程一共分為10個步驟,接下來我們就詳細分析每一個步驟。

        Step 1. ActivityThread.performLaunchActivity

 

  1. public final class ActivityThread {  
  2.     ......  
  3.     
  4.     Instrumentation mInstrumentation;  
  5.     ......  
  6.   
  7.     private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {  
  8.         ......  
  9.   
  10.         ComponentName component = r.intent.getComponent();  
  11.         ......  
  12.   
  13.         Activity activity = null;  
  14.         try {  
  15.             java.lang.ClassLoader cl = r.packageInfo.getClassLoader();  
  16.             activity = mInstrumentation.newActivity(  
  17.                     cl, component.getClassName(), r.intent);  
  18.             ......  
  19.         } catch (Exception e) {  
  20.             ......  
  21.         }  
  22.   
  23.         try {  
  24.             Application app = r.packageInfo.makeApplication(false, mInstrumentation);  
  25.             ......  
  26.   
  27.             if (activity != null) {  
  28.                 ContextImpl appContext = new ContextImpl();  
  29.                 ......  
  30.                 appContext.setOuterContext(activity);  
  31.                 ......  
  32.                 Configuration config = new Configuration(mConfiguration);  
  33.                 ......  
  34.   
  35.                 activity.attach(appContext, this, getInstrumentation(), r.token,  
  36.                         r.ident, app, r.intent, r.activityInfo, title, r.parent,  
  37.                         r.embeddedID, r.lastNonConfigurationInstance,  
  38.                         r.lastNonConfigurationChildInstances, config);  
  39.                 ......  
  40.   
  41.                 mInstrumentation.callActivityOnCreate(activity, r.state);  
  42.   
  43.                 ......    
  44.             }  
  45.   
  46.             ......  
  47.         } catch (SuperNotCalledException e) {  
  48.             ......  
  49.         } catch (Exception e) {  
  50.             ......  
  51.         }  
  52.   
  53.         return activity;  
  54.     }  
  55. }  

        這個函式定義在檔案frameworks/base/core/java/android/app/ActivityThread.java中。

        要啟動的Activity元件的類名儲存在變數component。有了這個類名之後,函式就可以呼叫ActivityThread類的成員變數mInstrumentation所描述一個Instrumentation物件的成員函式newActivity來建立一個Activity元件例項了,並且儲存變數activity中。Instrumentation類是用來記錄應用程式與系統的互動過程的,在接下來的Step 2中,我們再分析它的成員函式newActivity的實現。

       建立好了要啟動的Activity元件例項之後,函式接下來就可以對它進行初始化了。初始化一個Activity元件例項需要一個Application物件app、一個ContextImpl物件appContext以及一個Configuration物件config,它們分別用來描述該Activity元件例項的應用程式資訊、執行上下文環境以及配置資訊。這裡我們主要關心執行上下文環境的建立過程,即ContextImpl物件appContext的建立過程,這個過程我們在接下來的Step 4中再分析。

       ContextImpl物件appContext建立完成之後,函式就會呼叫它的成員函式setOuterContext來將與它所關聯的Activity元件例項activity儲存在它的內部。這樣,ContextImpl物件appContext以後就可以訪問與它所關聯的Activity元件的屬性或者方法。在接下來的Step 5中,我們再分析ContextImpl類的成員函式setOuterContext的實現。

       接著,函式就呼叫Activity元件例項activity的成員函式attach來將前面所建立的ContextImpl物件appContext以及Application物件app和Configuration物件config儲存在它的內部。這樣,Activity元件例項activity就可以訪問它的執行上下文環境資訊了。在接下來的Step 6中,我們再分析Activity類的成員函式attach的實現。

       最後,函式又通過呼叫ActivityThread類的成員變數mInstrumentation所描述一個Instrumentation物件的成員函式callActivityOnCreate來通知Activity元件例項activity,它已經被建立和啟動起來了。在接下來的Step 9中,我們再分析它的成員函式callActivityOnCreate的實現。

       接下來,我們就分別分析Instrumentation類的成員函式newActivity、ContextImpl類的建構函式以及成員函式setOuterContext、Activity類的成員函式attach和Instrumentation類的成員函式callActivityOnCreate的實現。

       Step 2. Instrumentation.newActivity

 

  1. public class Instrumentation {  
  2.     ......  
  3.   
  4.     public Activity newActivity(ClassLoader cl, String className,  
  5.             Intent intent)  
  6.             throws InstantiationException, IllegalAccessException,  
  7.             ClassNotFoundException {  
  8.         return (Activity)cl.loadClass(className).newInstance();  
  9.     }  
  10.   
  11.     ......  
  12. }  

        這個函式定義在檔案frameworks/base/core/java/android/app/Instrumentation.java中。

 

        引數cl描述的是一個類載入器,而引數className描述的要載入的類。以className為引數來呼叫cl描述的是一個類載入器的成員函式loadClass,就可以得到一個Class物件。由於className描述的是一個Activity子類,因此,當函式呼叫前面得到的Class物件的成員函式newInstance的時候,就會建立一個Activity子類例項。這個Activity例項就是用來描述在前面Step 1中所要啟動的Activity元件的。

       Activity子類例項在建立的過程,會呼叫父類Activity的預設建構函式,以便可以完成Activity元件的建立過程。

       Step 3. new Activity

       Activity類定義在檔案frameworks/base/core/java/android/app/Activity.java中,它沒有定義自己的建構函式,因此,系統就會為它提供一個預設的建構函式。一般來說,一個類的建構函式是用來初始化該類的例項的,但是,系統為Activity類提供的預設建構函式什麼也不做,也就是說,Activity類例項在建立的時候,還沒有執行實質的初始化工作。這個初始化工作要等到Activity類的成員函式attach被呼叫的時候才會執行。在後面的Step 6中,我們就會看到Activity類的成員函式attach是如何初始化一個Activity類例項的。

      這一步執行完成之後,回到前面的Step 1中,即ActivityThread類的成員函式performLaunchActivity中,接下來就會呼叫ContextImpl類的建構函式來建立一個ContextImpl物件,以便可以用來描述正在啟動的Activity元件的執行上下文資訊。

      Step 4. new ContextImpl

 

  1. class ContextImpl extends Context {  
  2.     ......  
  3.   
  4.     private Context mOuterContext;  
  5.     ......  
  6.   
  7.     ContextImpl() {  
  8.         // For debug only  
  9.         //++sInstanceCount;  
  10.         mOuterContext = this;  
  11.     }  
  12.   
  13.     ......  
  14. }  

       這個函式定義在檔案frameworks/base/core/java/android/app/ContextImpl.java中。

 

       ContextImpl類的成員變數mOuterContext的型別為Context。當一個ContextImpl物件是用來描述一個Activity元件的執行上下文環境時,那麼它的成員變數mOuterContext指向的就是該Activity元件。由於一個ContextImpl物件在建立的時候,並沒有引數用來指明它是用來描述一個Activity元件的執行上下文環境,因此,這裡就暫時將它的成員變數mOuterContext指向它自己。在接下來的Step 5中,我們就會看到,一個ContextImpl物件所關聯的一個Activity元件是通過呼叫ContextImpl類的成員函式setOuterContext來設定的。

      這一步執行完成之後,回到前面的Step 1中,即ActivityThread類的成員函式performLaunchActivity中,接下來就會呼叫ContextImpl類的成員函式setOuterContext來設定前面所建立一個ContextImpl物件所關聯的一個Activity元件,即正在啟動的Activity元件。

      Step 5. ContextImpl.setOuterContext

 

  1. class ContextImpl extends Context {  
  2.     ......  
  3.   
  4.     private Context mOuterContext;  
  5.     ......  
  6.   
  7.     final void setOuterContext(Context context) {  
  8.         mOuterContext = context;  
  9.     }  
  10.   
  11.     ......  
  12. }  

       這個函式定義在檔案frameworks/base/core/java/android/app/ContextImpl.java中。

 

       引數context描述的是一個正在啟動的Activity元件,ContextImpl類的成員函式setOuterContext只是簡單地將它儲存在成員變數mContext中,以表明當前正在處理的一個ContextImpl物件是用來描述一個Activity元件的執行上下文環境的。

       這一步執行完成之後,回到前面的Step 1中,即ActivityThread類的成員函式performLaunchActivity中,接下來就會呼叫Activity類的成員函式attach來初始化正在啟動的Activity元件,其中,就包括設定正在啟動的Activity元件的執行上下文環境。

       Step 6. Activity.attach

 

  1. public class Activity extends ContextThemeWrapper  
  2.         implements LayoutInflater.Factory,  
  3.         Window.Callback, KeyEvent.Callback,  
  4.         OnCreateContextMenuListener, ComponentCallbacks {  
  5.     ......  
  6.   
  7.     private Application mApplication;  
  8.     ......  
  9.   
  10.     /*package*/ Configuration mCurrentConfig;  
  11.     ......  
  12.   
  13.     private Window mWindow;  
  14.   
  15.     private WindowManager mWindowManager;  
  16.     ......  
  17.   
  18.     final void attach(Context context, ActivityThread aThread,  
  19.             Instrumentation instr, IBinder token, int ident,  
  20.             Application application, Intent intent, ActivityInfo info,  
  21.             CharSequence title, Activity parent, String id,  
  22.             Object lastNonConfigurationInstance,  
  23.             HashMap<String,Object> lastNonConfigurationChildInstances,  
  24.             Configuration config) {  
  25.         attachBaseContext(context);  
  26.   
  27.         mWindow = PolicyManager.makeNewWindow(this);  
  28.         mWindow.setCallback(this);  
  29.         if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {  
  30.             mWindow.setSoftInputMode(info.softInputMode);  
  31.         }  
  32.         ......  
  33.   
  34.         mApplication = application;  
  35.         ......  
  36.   
  37.         mWindow.setWindowManager(null, mToken, mComponent.flattenToString());  
  38.         ......  
  39.   
  40.         mWindowManager = mWindow.getWindowManager();  
  41.         mCurrentConfig = config;  
  42.     }  
  43.   
  44.     ......  
  45. }  

 

        這個函式定義在檔案frameworks/base/core/java/android/app/Activity.java中。

        函式首先呼叫從父類ContextThemeWrapper繼承下來的成員函式attachBaseConext來設定執行上下文環境,即將引數context所描述的一個ContextImpl物件儲存在內部。在接下來的Step 7中,我們再分析ContextThemeWrapper類的成員函式attachBaseConext的實現。

        函式接下來呼叫PolicyManager類的靜態成員函式makeNewWindow來建立了一個PhoneWindow,並且儲存在Activity類的成員變數mWindow中。這個PhoneWindow是用來描述當前正在啟動的應用程式視窗的。這個應用程式視窗在執行的過程中,會接收到一些事件,例如,鍵盤、觸控式螢幕事件等,這些事件需要轉發給與它所關聯的Activity元件處理,這個轉發操作是通過一個Window.Callback介面來實現的。由於Activity類實現了Window.Callback介面,因此,函式就可以將當前正在啟動的Activity元件所實現的一個Window.Callback介面設定到前面建立的一個PhoneWindow裡面去,這是通過呼叫Window類的成員函式setCallback來實現的。

       引數info指向的是一個ActivityInfo物件,用來描述當前正在啟動的Activity元件的資訊。其中,這個ActivityInfo物件的成員變數softInputMode用來描述當前正在啟動的一個Activity元件是否接受軟鍵盤輸入。如果接受的話,那麼它的值就不等於WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED,並且描述的是當前正在啟動的Activity元件所接受的軟鍵盤輸入模式。這個軟鍵盤輸入模式設定到前面所建立的一個PhoneWindow物件內部去,這是通過呼叫Window類的成員函式setSoftInputMode來實現的。

       在Android系統中,每一個應用程式視窗都需要由一個視窗管理者來管理,因此,函式再接下來就會呼叫前面所建立的一個PhoneWindow物件從父類Window繼承下來的成員函式setWindowManager來為它設定一個合適的視窗管理者。這個視窗管理者設定完成之後,就可以通過呼叫Window類的成員函式getWindowManager來獲得。獲得這個視窗管理者之後,函式就將它儲存在Activity類的成員變數mWindowManager中。這樣,當前正在啟動的Activity元件以後就可以通過它的成員變數mWindowManager來管理與它所關聯的視窗。

       除了建立和初始化一個PhoneWindow之外,函式還會分別把引數application和config所描述的一個Application物件和一個Configuration物件儲存在Activity類的成員變數mApplication和mCurrentConfig中。這樣,當前正在啟動的Activity元件就可以訪問它的應用程式資訊以及配置資訊。

      在接下來的一篇文章中,我們再詳細分析PolicyManager類的靜態成員函式makeNewWindow,以及Window類的成員函式setCallback、setSoftInputMode和setWindowManager的實現,以便可以瞭解應用程式視窗的建立過程。

      接下來,我們繼續分析ContextThemeWrapper類的成員函式attachBaseConext的實現,以便可以繼續瞭解一個應用程式視窗的執行上下文環境的設定過程。

      Step 7. ContextThemeWrapper.attachBaseConext

 

  1. public class ContextThemeWrapper extends ContextWrapper {  
  2.     private Context mBase;  
  3.     ......  
  4.   
  5.     @Override protected void attachBaseContext(Context newBase) {  
  6.         super.attachBaseContext(newBase);  
  7.         mBase = newBase;  
  8.     }  
  9.   
  10.     ......  
  11. }  

        這個函式定義在檔案frameworks/base/core/java/android/view/ContextThemeWrapper.java中。

 

        ContextThemeWrapper類用來維護一個應用程式視窗的主題,而用來描述這個應用程式視窗的執行上下文環境的一個ContextImpl物件就儲存在ContextThemeWrapper類的成員函式mBase中。

        ContextThemeWrapper類的成員函式attachBaseConext的實現很簡單,它首先呼叫父類ContextWrapper的成員函式attachBaseConext來將引數newBase所描述的一個ContextImpl物件儲存到父類ContextWrapper中去,接著再將這個ContextImpl物件儲存在ContextThemeWrapper類的成員變數mBase中。

        接下來,我們就繼續分析ContextWrapper類的成員函式attachBaseConext的實現。

        Step 8. ContextWrapper.attachBaseConext

 

  1. public class ContextWrapper extends Context {  
  2.     Context mBase;  
  3.     ......  
  4.   
  5.     protected void attachBaseContext(Context base) {  
  6.         if (mBase != null) {  
  7.             throw new IllegalStateException("Base context already set");  
  8.         }  
  9.         mBase = base;  
  10.     }  
  11.   
  12.     ......  
  13. }  

        這個函式定義在檔案frameworks/base/core/java/android/content/ContextWrapper.java 中。

 

        ContextWrapper類只是一個代理類,它只是簡單地封裝了對其成員變數mBase所描述的一個Context物件的操作。ContextWrapper類的成員函式attachBaseConext的實現很簡單,它只是將引數base所描述的一個ContextImpl物件儲存在成員變數mBase中。這樣,ContextWrapper類就可以將它的功能交給ContextImpl類來具體實現。

        這一步執行完成之後,當前正在啟動的Activity元件的執行上下文環境就設定完成了,回到前面的Step 1中,即ActivityThread類的成員函式performLaunchActivity中,接下來就會呼叫Instrumentation類的成員函式callActivityOnCreate來通知當前正在啟動的Activity元件,它已經建立和啟動完成了。

        Step 9. Instrumentation.callActivityOnCreate

 

  1. public class Instrumentation {  
  2.     ......  
  3.   
  4.     public void callActivityOnCreate(Activity activity, Bundle icicle) {  
  5.         ......  
  6.   
  7.         activity.onCreate(icicle);  
  8.   
  9.         ......  
  10.     }  
  11.    
  12.     ......  
  13. }  

       這個函式定義在檔案frameworks/base/core/java/android/app/Instrumentation.java中。

 

       函式主要就是呼叫當前正在啟動的Activity元件的成員函式onCreate,用來通知它已經成功地建立和啟動完成了。

      Step 10. Activity.onCreate

 

  1. public class Activity extends ContextThemeWrapper  
  2.         implements LayoutInflater.Factory,  
  3.         Window.Callback, KeyEvent.Callback,  
  4.         OnCreateContextMenuListener, ComponentCallbacks {  
  5.     ......  
  6.   
  7.     boolean mCalled;  
  8.     ......  
  9.   
  10.     /*package*/ boolean mVisibleFromClient = true;  
  11.     ......      
  12.   
  13.     protected void onCreate(Bundle savedInstanceState) {  
  14.         mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(  
  15.                 com.android.internal.R.styleable.Window_windowNoDisplay, false);  
  16.         mCalled = true;  
  17.     }  
  18.    
  19.     ......  
  20. }  

 

       這個函式定義在檔案frameworks/base/core/java/android/app/Activity.java中。

       一般來說,我們都是通過定義一個Activity子類來實現一個Activity元件的。重寫父類Activity的某些成員函式的時候,必須要回撥父類Activity的這些成員函式。例如,當Activity子類在重寫父類Activity的成員函式onCreate時,就必須回撥父類Activity的成員函式onCreate。這些成員函式被回撥了之後,Activity類就會將其成員變數mCalled的值設定為true。這樣,Activity類就可以通過其成員變數mCalled來檢查其子類在重寫它的某些成員函式時,是否正確地回撥了父類的這些成員函式。

       Activity類的另外一個成員變數mVisibleFromClient用來描述一個應用程式視窗是否是可見的。如果是可見的,那麼它的值就會等於true。當Activity類的成員函式onCreate被其子類回撥時,它就會檢查對應的應用程式視窗的主題屬性android:windowNoDisplay的值是否等於true。如果等於true的話,那麼就說明當前所啟動的應用程式視窗是不可見的,這時候Activity類的成員變數mVisibleFromClient的值就會被設定為false,否則的話,就會被設定為true。

      Activity子類在重寫成員函式onCreate的時候,一般都會呼叫父類Activity的成員函式setContentView來為為當前正啟動的應用程式視窗建立檢視(View)。在接下來的文章中,我們再詳細描述應用程式視窗的檢視的建立過程。

      至此,一個Activity元件的建立過程,以及它的執行上下文環境的建立過程,就分析完成了。這個過程比較簡單,我們是從中獲得以下三點資訊:

      1. 一個Android應用視窗的執行上下文環境是使用一個ContextImpl物件來描述的,這個ContextImpl物件會分別儲存在Activity類的父類ContextThemeWrapper和ContextWrapper的成員變數mBase中,即ContextThemeWrapper類和ContextWrapper類的成員變數mBase指向的是一個ContextImpl物件。

      2. Activity元件在建立過程中,即在它的成員函式attach被呼叫的時候,會建立一個PhoneWindow物件,並且儲存在成員變數mWindow中,用來描述一個具體的Android應用程式視窗。

      3.  Activity元件在建立的最後,即在它的子類所重寫的成員函式onCreate中,會呼叫父類Activity的成員函式setContentView來建立一個Android應用程式視窗的檢視。

      在接下來的兩篇文章中,我們就將會詳細描述Android應用程式視窗以及它的檢視的建立過程,敬請關注!

老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!

相關文章