Android應用程式視窗(Activity)的執行上下文環境(Context)的建立過程分析
在前文中,我們簡要介紹了Android應用程式視窗的框架。Android應用程式視窗在執行的過程中,需要訪問一些特定的資源或者類。這些特定的資源或者類構成了Android應用程式的執行上下文環境,Android應用程式視窗可以通過一個Context介面來訪問它,這個Context介面也是我們在開發應用程式時經常碰到的。在本文中,我們就將詳細分析Android應用程式視窗的執行上下文環境的建立過程。
《Android系統原始碼情景分析》一書正在進擊的程式設計師網(http://0xcc0xcd.com)中連載,點選進入!
在前面Android應用程式視窗(Activity)實現框架簡要介紹和學習計劃
圖1 ContextImpl類與Activity類的關係圖
這個類圖在設計模式裡面就可以稱為裝飾模式。Activity元件通過其父類ContextThemeWrapper和ContextWrapper的成員變數mBase來引用了一個ContextImpl物件,這樣,Activity元件以後就可以通過這個ContextImpl物件來執行一些具體的操作,例如,
在前面Android應用程式啟動過程原始碼分析一文中,我們已經詳細分析過一個Activity元件的啟動過程了。在這個啟動過程中,最後一步便是通過ActivityThread類的成員函式performLaunchActivity在應用程式程序中建立一個Activity例項,並且為它設定執行上下文環境,即為它建立一個ContextImpl物件。接下來,我們就從ActivityThread類的成員函式performLaunchActivity開始,分析一個Activity例項的建立過程,以便可以從中瞭解它的執行上下文環境的建立過程,如圖2所示:
圖2 Android應用程式視窗的執行上下文環境的建立過程
這個過程一共分為10個步驟,接下來我們就詳細分析每一個步驟。
Step 1. ActivityThread.performLaunchActivity
public final class ActivityThread { ...... Instrumentation mInstrumentation; ...... private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ...... ComponentName component = r.intent.getComponent(); ...... Activity activity = null; try { java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); ...... } catch (Exception e) { ...... } try { Application app = r.packageInfo.makeApplication(false, mInstrumentation); ...... if (activity != null) { ContextImpl appContext = new ContextImpl(); ...... appContext.setOuterContext(activity); ...... Configuration config = new Configuration(mConfiguration); ...... activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstance, r.lastNonConfigurationChildInstances, config); ...... mInstrumentation.callActivityOnCreate(activity, r.state); ...... } ...... } catch (SuperNotCalledException e) { ...... } catch (Exception e) { ...... } return activity; }}
這個函式定義在檔案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
public class Instrumentation { ...... public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return (Activity)cl.loadClass(className).newInstance(); } ......}
這個函式定義在檔案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
class ContextImpl extends Context { ...... private Context mOuterContext; ...... ContextImpl() { // For debug only //++sInstanceCount; mOuterContext = this; } ......}
這個函式定義在檔案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
class ContextImpl extends Context { ...... private Context mOuterContext; ...... final void setOuterContext(Context context) { mOuterContext = context; } ......}
這個函式定義在檔案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
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks { ...... private Application mApplication; ...... /*package*/ Configuration mCurrentConfig; ...... private Window mWindow; private WindowManager mWindowManager; ...... 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, Object lastNonConfigurationInstance, HashMap<String,Object> lastNonConfigurationChildInstances, Configuration config) { attachBaseContext(context); mWindow = PolicyManager.makeNewWindow(this); mWindow.setCallback(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } ...... mApplication = application; ...... mWindow.setWindowManager(null, mToken, mComponent.flattenToString()); ...... mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; } ......}
這個函式定義在檔案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
public class ContextThemeWrapper extends ContextWrapper { private Context mBase; ...... @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); mBase = newBase; } ......}
這個函式定義在檔案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
public class ContextWrapper extends Context { Context mBase; ...... protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base; } ......}
這個函式定義在檔案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
public class Instrumentation { ...... public void callActivityOnCreate(Activity activity, Bundle icicle) { ...... activity.onCreate(icicle); ...... } ......}
這個函式定義在檔案frameworks/base/core/java/android/app/Instrumentation.java中。函式主要就是呼叫當前正在啟動的Activity元件的成員函式onCreate,用來通知它已經成功地建立和啟動完成了。
Step 10. Activity.onCreate
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks { ...... boolean mCalled; ...... /*package*/ boolean mVisibleFromClient = true; ...... protected void onCreate(Bundle savedInstanceState) { mVisibleFromClient = !mWindow.getWindowStyle().getBoolean( com.android.internal.R.styleable.Window_windowNoDisplay, false); mCalled = true; } ......}
這個函式定義在檔案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應用程式視窗以及它的檢視的建立過程,敬請關注!