1 參考鏈接
Android應用程序窗口(Activity)的視圖對象(View)的創建過程分析
Android應用setContentView與LayoutInflater加載解析機制源碼分析
Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析
2 概念
每一個Activity都有一個關聯的Window對象,用來描述一個應用程序窗口。每一個應用程序窗口內部又包含有一個View對象,用來描述應用程序窗口的視圖。應用程序窗口視圖是真正用來實現UI內容和佈局的,也就是說,每一個Activity組件的UI內容和佈局都是通過與其所關聯的一個Window對象的內部的一個View對象來實現的。
由圖可知:Activity、 Window、 View 三者如何協同顯示界面的。一個Activity包含一個window對象,這個對象是由PhoneWindow來實現的,PhoneWindow將DecorView做爲整個應用窗口的根View,而這個DecorView又將屏幕劃分爲兩個區域一個是TitleView一個是ContentView,我們平常做應用所寫的佈局正是展示在ContentView中的。
通俗的表達是:Activity剪窗花的人(控制的);Window窗戶(承載的一個模型);View窗花(要顯示的視圖View);LayoutInflater剪刀—將佈局(圖紙)剪成窗花。
3 UML圖分析
(1)應用程序窗口內部所包含的視圖對象的實際類型爲DecorView。DecorView類繼承了View類,是作爲容器(ViewGroup)來使用的,它的實現如圖1–DecorView類的實現所示:
(2)每一個應用程序窗口的視圖對象都有一個關聯的ViewRootImpl對象,這些關聯關係是由窗口管理器來維護的,如圖2–應用程序窗口視圖與ViewRootImpl的關係圖所示:
ViewRootImpl類是從Handler類繼承下來的,從Handler類繼承下來的子類可以調用父類的成員函數sendMessage來向指定的線程的消息隊列發送消息,以及在自己重寫的成員函數handleMessage中處理該消息。 ViewRootImpl類在兩種情況需要經常應用程序進程的主線程的消息隊列發送消息。
簡單來說,ViewRootImpl相當於是MVC模型中的Controller,它有以下職責:
- 負責爲應用程序窗口視圖創建Surface。
- 配合WindowManagerService來管理系統的應用程序窗口。
- 負責管理、佈局和渲染應用程序窗口視圖的UI。
(3)Activity組件在啓動的過程中,會調用ActivityThread類的成員函數handleLaunchActivity,用來創建以及首次激活Activity組件,因此,接下來我們就從這個函數開始,具體分析應用程序窗口的視圖對象及其所關聯的ViewRootImpl對象的創建過程,如圖3–應用程序窗口視圖的創建過程所示。
Activity第一次setContentView時,performLaunchActivity()開始做的操作是初始化各個view對象, 並制定上下級關係,僅此而已。真正開始的地方是在handleResumeActivity中通過windowManager的實現類的addView方法,方法內部通過ViewRootImpl的實例調用setView()方法。該方法內部的requestlayout()發出消息,開始執行doTraversal(),從而開始view三大流程。
補充,添加:7.LayoutInflater.inflate(layoutResID, mContentParent);修改:8.handleResumeActivity()。
(4)Window創建過程
(5)應用程序窗口(Activity)與WindowManagerService服務的連接
從兩方面來看Activity組件與WindowManagerService服務之間的連接。一方面是從Activity到WindowManagerService服務的連接,另一方面是從WindowManagerService服務到Activity組件的連接。從Activity組件到WindowManagerService服務的連接是以Activity所在的應用程序進程爲單位來進行的。當一個應用程序進程在啓動第一個Activity組件的時候,它便會打開一個到WindowManagerService服務的連接,這個連接以應用程序進程從WindowManagerService服務處獲得一個實現了IWindowSession接口的Session代理對象來標誌。從WindowManagerService服務到Activity組件的連接是以Activity爲單位來進行的。在應用程序進程這一側,每一個Activity組件都關聯一個實現了IWindow接口的W對象,這個W對象在Activity組件的視圖對象創建完成之後,就會通過前面所獲得一個Session代理對象來傳遞給WindowManagerService服務,而WindowManagerService服務接收到這個W對象之後,就會在內部創建一個WindowState對象來描述與該W對象所關聯的Activity組件的窗口狀態,並且以後就通過這個W對象來控制對應的Activity組件的窗口狀態。
上述Activity組件與WindowManagerService服務之間的連接模型如圖所示:
Session類和WindowState類的實現:(下節再詳細介紹)
4 源碼分析
4.1 ActivityThread.handleLaunchActivity
public final class ActivityThread {
......
private final void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
// Initialize before creating the activity
WindowManagerGlobal.initialize();
//ActivityThread.performLaunchActivity
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
......
handleResumeActivity(r.token, false, r.isForward);
......
}
......
}
......
}
函數首先調用ActivityThread類的成員函數performLaunchActivity來創建要啓動的Activity組件。在創建Activity組件的過程中,還會爲該Activity組件創建窗口對象和視圖對象。Activity組件創建完成之後,就可以將它激活起來了,這是通過調用ActivityThread類的成員函數handleResumeActivity來執行的。
4.2 ActivityThread.performLaunchActivity
public final class ActivityThread {
......
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
if (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);//activity.attach
if (r.isPersistable()) {
//Instrumentation.callActivityOnCreate
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
......
}
......
}
......
}
4.3 Activity.onCreate
public class Instrumentation {
......
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
//Activity.performCreate()
activity.performCreate(icicle);
postPerformCreate(activity);
}
......
}
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks {
......
final void performCreate(Bundle icicle) {
restoreHasCurrentPermissionRequest(icicle);
//Activity.onCreate()
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
protected void onCreate(@Nullable Bundle savedInstanceState) {
......
}
}
public class Hello extends Activity implements OnClickListener {
......
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//4.4 Activity.setContentView
setContentView(R.layout.main);
......
}
......
}
4.4 Activity.setContentView
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks {
......
private Window mWindow;
......
public Window getWindow() {
return mWindow;
}
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent........
Window window) {
......
mWindow = new PhoneWindow(this, window);//PhoneWindow
mWindow.getLayoutInflater().setPrivateFactory(this);
mWindowManager = mWindow.getWindowManager();
......
}
......
public void setContentView(@LayoutRes int layoutResID) {
//4.5 PhoneWindow.setContentView(layoutResID)
getWindow().setContentView(layoutResID);
}
......
}
Activity類的成員函數setContentView首先調用另外一個成員函數getWindow來獲得成員變量mWindow所描述的一個窗口對象,接着再調用這個窗口對象的成員函數setContentView來執行創建應用程序窗口視圖對象的工作。
Activity類的成員變量mWindow指向的是一個PhoneWindow對象,因此接下來我們就繼續分析PhoneWindow類的成員函數setContentView的實現。
4.5 PhoneWindow.setContentView(核心)
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private ViewGroup mContentParent;
......
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//4.6 installDecor(),獲取到mContentParent視圖
installDecor();
} else {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());
transitionTo(newScene);
} else {
//4.7 mLayoutInflater.inflate(layoutResID, mContentParent)
//將我們的資源文件通過LayoutInflater對象轉換爲View樹,並且添加至mContentParent視圖中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
......
}
mContentParent用來描述一個類型爲DecorView的視圖對象用作UI容器。當它的值等於null的時候,就說明正在處理的應用程序窗口的視圖對象還沒有創建。在這種情況下,就會調用函數installDecor來創建應用程序窗口視圖對象。否則的話,就說明是要重新設置應用程序窗口的視圖。在重新設置之前,首先調用變量mContentParent所描述的一個ViewGroup對象來移除原來的UI內空。
首先判斷mContentParent是否爲null,也就是第一次調運);如果是第一次調用,則調用installDecor()方法,否則判斷是否設置FEATURE_CONTENT_TRANSITIONS Window屬性(默認false),如果沒有就移除該mContentParent內所有的所有子View;接着mLayoutInflater.inflate(layoutResID, mContentParent);將我們的資源文件通過LayoutInflater對象轉換爲View樹,並且添加至mContentParent視圖中。將參數layoutResID所描述的一個UI佈局設置到前面所創建的應用程序窗口視圖中去,最後還會調用一個Callback接口的函數onContentChanged來通知對應的Activity,它的視圖內容發生改變了。
接下來,我們就繼續分析函數installDecor的實現,以便可以繼續瞭解應用程序窗口視圖對象的創建過程。
4.6 PhoneWindow.installDecor
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private DecorView mDecor;
......
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;
......
private TextView mTitleView;
......
private CharSequence mTitle = null;
......
private void installDecor() {
if (mDecor == null) {
//generateDecor()實現代碼如下
mDecor = generateDecor();
......
}
if (mContentParent == null) {
//generateLayout(mDecor)實現代碼如下
mContentParent = generateLayout(mDecor);
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(com.android.internal.R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
}
}
}
......
}
由於我們是在Activity啓動中創建應用程序窗口視圖的,因此假設此時mDecor的值等於null。這時候installDecor就會調用另外一個函數generateDecor來創建一個DecorView對象,並且保存在PhoneWindow類的成員變量mDecor中。generateDecor()代碼如下:
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
installDecor接着再調用另外一個函數generateLayout來根據當前應用程序窗口的Feature來加載對應的窗口布局文件。這些佈局文件保存在frameworks/base/core/res/res/layout目錄下,它們必須包含有一個id值爲“content”的佈局控件。這個佈局控件必須要從ViewGroup類繼承下來,用來作爲窗口的UI容器。PhoneWindow類的成員函數generateLayout執行完成之後,就會這個id值爲“content”的ViewGroup控件來給PhoneWindow類的成員函數installDecor,後者再將其保存在成員變量mContentParent中。generateLayout(decor)代碼和DecorView添加至窗口的過程示例圖如下:
protected ViewGroup generateLayout(DecorView decor) {
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else {
//系統提供的佈局:R.layout.screen_simple
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//系統佈局中content部分,加載自己的佈局
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
mDecor.finishChanging();
return contentParent;
}
installDecor還會檢查前面加載的窗口布局文件是否包含有一個id值爲“title”的TextView控件。如果包含有的話,就會將它保存在PhoneWindow類的成員變量mTitleView中,用來描述當前應用程序窗口的標題欄。但是,如果當前應用程序窗口是沒有標題欄的,即它的Feature位FEATURE_NO_TITLE的值等於1,那麼installDecor就需要將前面得到的標題欄隱藏起來。(注意,mTitleView所描述的標題欄有可能是包含在一個id值爲“title_container”的容器裏面的,在這種情況下,就需要隱藏該標題欄容器。另一方面,如果當前應用程序窗口是設置有標題欄的,那麼installDecor就會設置它的標題欄文字。應用程序窗口的標題欄文字保存在變量mTitle中,我們可以調用函數setTitle來設置。)
這一步執行完成之後,應用程序窗口視圖就創建完成了,回到前面的4.1中,即ActivityThread類的成員函數handleLaunchActivity中,接下來就會調用ActivityThread類的另外一個函數handleResumeActivity來激活正在啓動的Activity組件。由於在是第一次激活該Activity組件,因此,在激活之前,還會爲該Activity創建一個ViewRootImpl對象,並且與前面所創建的應用程序窗口視圖關聯起來,以便後面可以通過該ViewRootImpl對象來控制應用程序窗口視圖的UI展現。
4.7 LayoutInflater.inflate(layoutResID, mContentParent);
4.7.1 得到LayoutInflater
LayoutInflater lif = LayoutInflater.from(Context context);
LayoutInflater lif = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//源碼中LayoutInflater實例化獲取的方法:
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
4.7.2 LayoutInflater源碼的View inflate(…)方法族剖析
得到LayoutInflater對象之後我們就是傳遞xml然後解析得到View,如下方法:
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
//XmlResourceParser
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
獲取到XmlResourceParser接口的實例(Android默認實現類爲Pull解析XmlPullParser)。接着看第10行inflate(parser, root, attachToRoot);,你會發現無論哪個inflate重載方法最後都調運了inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)方法:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
//定義返回值,初始化爲傳入的形參root
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//如果一開始就是END_DOCUMENT,那說明xml文件有問題
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//有了上面判斷說明這裏type一定是START_TAG,也就是xml文件裏的root node
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
if (TAG_MERGE.equals(name)) {
//處理merge tag的情況(merge,你懂的,APP的xml性能優化)
//root必須非空且attachToRoot爲true,否則拋異常結束(APP使用merge時要注意的地方,
//因爲merge的xml並不代表某個具體的view,只是將它包起來的其他xml的內容加到某個上層
//ViewGroup中。)
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//遞歸inflate方法調運
rInflate(parser, root, attrs, false, false);
} else {
// Temp is the root view that was found in the xml
//xml文件中的root view,根據tag節點創建view對象
final View temp = createViewFromTag(root, name, attrs, false);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
//根據root生成合適的LayoutParams實例
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
//如果attachToRoot=false就調用view的setLayoutParams方法
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp
//遞歸inflate剩下的children
rInflate(parser, temp, attrs, true, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
//root非空且attachToRoot=true則將xml文件的root view加到形參提供的root裏
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
//返回xml裏解析的root view
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (IOException e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
//返回參數root或xml文件裏的root view
return result;
}
}
接下來,我們就繼續分析ActivityThread類的成員函數handleResumeActivity的實現。
4.8 ActivityThread.handleResumeActivity
public final class ActivityThread {
......
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
......
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
......
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
}
}
if (r.window == null && !a.mFinished && willBeVisible) { //
r.window = r.activity.getWindow();
//4.9 getDecorView()
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//4.10 Activity.getWindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
......
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//4.11 (WindowManagerImpl)WindowManager.addView(decor, l)
wm.addView(decor, l);
}
}
......
}
......
}
......
}
函數handleResumeActivity首先調用另一個函數performResumeActivity來通知Activity,它要被激活了,即會導致Activity的函數onResume被調用。performResumeActivity的返回值是一個ActivityClientRecord對象r,這個ActivityClientRecord對象的變量activity描述的就是正在激活的Activity組件a。
handleResumeActivity接下來判斷正在激活的Activity是否是可見的。如果可見,那麼變量willBeVisible的值就會等於true。Activity類的變量mStartedActivity用來描述一個Activity是否正在啓動一個新的Activity,並且等待這個新的Activity組件的執行結果。如果是的話,那麼這個Activity組件的成員變量mStartedActivity的值就會等於true,表示在新的Activity組件的執行結果返回來之前,當前Activity組件要保持不可見的狀態。因此,當Activity組件a的成員變量mStartedActivity的值等於true的時候,它接下來就是不可見的,否則的話,就是可見的。
雖然說在Activity組件a的變量mStartedActivity的值等於true的情況下,它接下來的狀態要保持不可見的,但是有可能它所啓動的Activity組件的UI不是全屏的。在這種情況下,Activity組件a的UI仍然是有部分可見的,這時候也要將變量willBeVisible的值設置爲true。因此,如果前面得到變量willBeVisible的值等於false,那麼函數handleResumeActivity接下來就會通過Binder進程間通信機制來調用ActivityManagerService服務的成員函數willActivityBeVisible來檢查位於Activity組件a上面的其它Activity組件是否是全屏的。如果不是,那麼ActivityManagerService服務的函數willActivityBeVisible的返回值就會等於true,表示接下來需要顯示Activity組件a。
前面得到的ActivityClientRecord對象r的變量window用來描述當前正在激活的Activity組件a所關聯的應用程序窗口對象。當它的值等於null的時候,就表示當前正在激活的Activity組件a所關聯的應用程序窗口對象還沒有關聯一個ViewRootImpl對象。進一步地,如果這個正在激活的Activity組件a還活着,並且接下來是可見的,即ActivityClientRecord對象r的成員變量mFinished的值等於false,並且前面得到的變量willBeVisible的值等於true,那麼這時候就說明需要爲與Activity組件a所關聯的一個應用程序窗口視圖對象關聯的一個ViewRootImpl對象。
將一個Activity的應用程序窗口視圖對象與一個ViewRootImpl對象關聯是通過該Activity組件所使用的窗口管理器來執行的。一個Activity組件所使用的本地窗口管理器保存它的成員變量mWindowManager中,這可以通過Activity類的函數getWindowManager來獲得。在接下來的 中,我們再分析Activity類的成員函數getWindowManager的實現。
由於我們現在要給Activity組件a的應用程序窗口視圖對象關聯一個ViewRootImpl對象,因此,我們就需要首先獲得這個應用程序窗口視圖對象。從前面的Step 6可以知道,一個Activity組件的應用程序窗口視圖對象保存在與其所關聯的一個應用程序窗口對象的內部,因此,我們又要首先獲得這個應用程序窗口對象。與一個Activity組件所關聯的應用程序窗口對象可以通過調用該Activity組件的成員函數getWindow來獲得。一旦獲得了這個應用程序窗口對象(類型爲PhoneWindow)之後,我們就可以調用它的成員函數getDecorView來獲得它內部的視圖對象。在接下來的Step 8和Step 9中,我們再分別分析Activity類的成員函數Activity類的成員函數getWindow和PhoneWindow類的成員函數getDecorView的實現。
在關聯應用程序窗口視圖對象和ViewRootImpl對象的時候,還需要第三個參數,即應用程序窗口的佈局參數,這是一個類型爲WindowManager.LayoutParams的對象,可以通過調用應用程序窗口的函數getAttributes來獲得。還要判斷最後一個條件是否成立,即當前正在激活的Activity組件a在本地進程中是否是可見的,即它的變量mVisibleFromClient的值是否等於true。如果是可見的,那麼最後就可以調用前面所獲得的一個本地窗口管理器wm(類型爲LocalWindowManager)的成員函數addView來執行關聯應用程序窗口視圖對象和ViewRootImpl對象的操作。
接下來,我們就分別分析Activity類的成員函數getWindow、PhoneWindow類的成員函數getDecorView、ctivity類的成員函數getWindowManager以及LocalWindowManager類的成員函數addView的實現。
4.9 PhoneWindow.getDecorView
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private DecorView mDecor;
......
@Override
public final View getDecorView() {
if (mDecor == null) {
installDecor(); //installDecor();
}
return mDecor;
}
......
}
函數getDecorView首先判斷變量mDecor的值是否等於null。如果是的話,那麼就說明當前正在處理的應用程序窗口還沒有創建視圖對象。這時候就會調用另外一個函數installDecor來創建這個視圖對象。從前面的調用過程可以知道,當前正在處理的應用程序窗口已經創建過視圖對象,因此,這裏的成員變量mDecor的值不等於null,函數getDecorView直接將它返回給調用者。
這一步執完成之後,返回到前面的4.9中,即函數handleResumeActivity中,接下來就會繼續調用當前正在激活的Activity的函數getWindowManager來獲得一個本地窗口管理器。
4.10 Activity.getWindowManager
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory, Window.Callback.... {
......
private Window mWindow;
private WindowManager mWindowManager;
public WindowManager getWindowManager() {
return mWindowManager;
}
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, ....
Window window) {
......
//mWindow.getWindowManager()
mWindowManager = mWindow.getWindowManager();
......
}
......
}
public abstract class Window {
private final WindowManager mWindowManager;
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
......
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);//WindowManagerImpl
}
public WindowManager getWindowManager() {
return mWindowManager;
}
......
}
Activity類的成員變量mWindowManager指向的一是WindowManagerImpl的本地窗口管理器,Activity類的成員函數getWindowManager直接將它返回給調用者。
4.11 WindowManagerImpl.addView
public class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
//WindowManagerGlobal.addView()
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
}
這一步執完成之後,返回到前面的4.9中,即ActivityThread類的成員函數handleResumeActivity中,接下來就會繼續調用前面所獲得的WindowManagerImpl對象的函數addView來爲當前正在激活的Activity組件的應用程序窗口視圖對象關聯一個ViewRootImpl對象。
4.12 WindowManagerGlobal.addView
public class WindowManagerGlobal {
......
private void addView(View view, ViewGroup.LayoutParams params, boolean nest) {
......
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
}
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
root = new ViewRootImplImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
try {
//4.13 ViewRootImpl.setView
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
WindowManagerGlobal類是如何關聯一個應用程序窗口視圖對象(View對象)和一個ViewRootImpl對象的? 一個View對象在與一個ViewRootImpl對象關聯的同時,還會關聯一個WindowManager.LayoutParams對象,這個WindowManager.LayoutParams對象是用來描述應用程序窗口視圖的佈局屬性的。
WindowManagerGlobal類有三個成員變量mViews、mRoots和mParams,它們分別是類型爲View、ViewRootImpl和WindowManager.LayoutParams的數組。這三個數組的大小是始終保持相等的。這樣, 有關聯關係的View對象、ViewRootImpl對象和WindowManager.LayoutParams對象就會分別保存在數組mViews、mRoots和mParams的相同位置上,也就是說,mViews[i]、mRoots[i]和mParams[i]所描述的View對象、ViewRootImpl對象和WindowManager.LayoutParams對象是具有關聯關係的。因此,WindowManagerGlobal類的函數addView在關聯一個View對象、一個ViewRootImpl對象和一個WindowManager.LayoutParams對象的時候,只要分別將它們放在數組mViews、mRoots和mParams的相同位置上就可以了。
參數view和參數params描述的就是要關聯的View對象和WindowManager.LayoutParams對象。函數addView首先調用另外一個函數findViewLocked來檢查參數view所描述的一個View對象是否已經存在於數組中mViews中了。如果已經存在的話,那麼就說明該View對象已經關聯過ViewRootImpl對象以及WindowManager.LayoutParams對象了。在這種情況下,如不允許重複對參數view所描述的一個View對象進行重新關聯的。
如果參數view所描述的一個View對象還沒有被關聯過一個ViewRootImpl對象,那麼成員函數addView就會創建一個ViewRootImpl對象,並且將它與參數view和params分別描述的一個View對象和一個WindowManager.LayoutParams對象保存在數組mViews、mRoots和mParams的相同位置上。注意,如果數組mViews、mRoots和mParams尚未創建,那麼成員函數addView就會首先分別爲它們創建一個大小爲1的數組,以便可以用來分別保存所要關聯的View對象、ViewRootImpl對象和WindowManager.LayoutParams對象。另一方面,如果數組mViews、mRoots和mParams已經創建,那麼成員函數addView就需要分別將它們的大小增加1,以便可以在它們的末尾位置上分別保存所要關聯的View對象、ViewRootImpl對象和WindowManager.LayoutParams對象。
還有另外一個需要注意的地方是當參數view描述的是一個子應用程序窗口的視圖對象時,即WindowManager.LayoutParams對象wparams的成員變量type的值大於等於WindowManager.LayoutParams.FIRST_SUB_WINDOW並且小於等於WindowManager.LayoutParams.LAST_SUB_WINDOW時,那麼成員函數addView還需要找到這個子視圖對象的父視圖對象panelParentView,這是通過遍歷數組mRoots來查找的。首先,WindowManager.LayoutParams對象wparams的成員變量token指向了一個類型爲W的Binder本地對象的一個IBinder接口,用來描述參數view所描述的一個子應用程序窗口視圖對象所屬的父應用程序窗口視圖對象。其次,每一個ViewRootImpl對象都通過其成員變量mWindow來保存一個類型爲W的Binder本地對象,因此,如果在數組mRoots中,存在一個ViewRootImpl對象,它的成員變量mWindow所描述的一個W對象的一個IBinder接口等於WindowManager.LayoutParams對象wparams的成員變量token所描述的一個IBinder接口時,那麼就說明與該ViewRootImpl對象所關聯的View對象即爲參數view的父應用程序窗口視圖對象。
成員函數addView爲參數view所描述的一個View對象和參數params所描述的一個WindowManager.LayoutParams對象關聯好一個ViewRootImpl對象root之後,最後還會將這個View對view象和這個WindowManager.LayoutParams對象,以及變量panelParentView所描述的一個父應用程序窗視圖對象,保存在這個ViewRootImpl對象root的內部去,這是通過調用這個ViewRootImpl對象root的成員函數setView來實現的,因此,接下來我們就繼續分析ViewRootImpl類的成員函數setView的實現。
4.15 ViewRootImpl.setView
public final class ViewRootImpl extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
......
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
......
View mView;
......
final View.AttachInfo mAttachInfo;
......
boolean mAdded;
......
public void setView(View view, WindowManager.LayoutParams attrs,
View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mWindowAttributes.copyFrom(attrs);
......
mAttachInfo.mRootView = view;
.......
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
}
mAdded = true;
......
//requestLayout()
requestLayout();
......
try {
res = sWindowSession.add(mWindow, mWindowAttributes,
getHostVisibility(), mAttachInfo.mContentInsets,
mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
......
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
......
}
......
}
}
......
}
參數view所描述的一個View對象會分別被保存在ViewRootImpl類的變量mView以及成員變量mAttachInfo所描述的一個AttachInfo的變量mRootView中,而參數attrs所描述的一個WindowManager.LayoutParams對象的內容會被拷貝到ViewRoot類的成員變量mWindowAttributes中去。
當參數panelParentView的值不等於null的時候,就表示參數view描述的是一個子應用程序窗口視圖對象。在這種情況下,參數panelParentView描述的就是一個父應用程序窗口視圖對象。這時候我們就需要獲得用來描述這個父應用程序窗口視圖對象的一個類型爲W的Binder本地對象的IBinder接口,以便可以保存在ViewRoot類的成員變量mAttachInfo所描述的一個AttachInfo的成員變量 mPanelParentWindowToken中去。這樣以後就可以知道ViewRoot類的成員變量mView所描述的一個子應用程序窗口視圖所屬的父應用程序窗口視圖是什麼了。注意,通過調用參數panelParentView的所描述的一個View對象的成員函數getApplicationWindowToken即可以獲得一個對應的W對象的IBinder接口。
上述操作執行完成之後,ViewRoot類的成員函數setView就可以將成員變量mAdded的值設置爲true了,表示當前正在處理的一個ViewRoot對象已經關聯好一個View對象了。接下來,ViewRoot類的成員函數setView還需要執行兩個操作:
- 調用ViewRoot類的另外一個函數requestLayout來請求對應用程序窗口視圖的UI作第一次佈局。
- 調用ViewRootImpl類的靜態成員變量sWindowSession所描述的一個類型爲Session的Binder代理對象的成員函數add來請求WindowManagerService增加一個WindowState對象,以便可以用來描述當前正在處理的一個ViewRoot所關聯的一個應用程序窗口。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//scheduleTraversals()
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
//TraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
//doTraversal()
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
//performTraversals(),整個View樹的繪圖流程在此開始
performTraversals();
}
}