上一篇讲了Activity的启动流程(https://www.jianshu.com/p/5e91681a8f65),从本篇开始将笔墨着重在Activity的的绘制流程上。绘制的内容将分为两大块:应用端的绘制和WMS端窗口的管理,介绍的路线如下,本篇主讲(一)窗口的添加。
(一)窗口的添加
(二)Choreographer
(三)VSync
(四)Surface
(五)RenderThread
(六)StartingWIndow
(七)窗口切换
一、窗口的管理方式
在介绍Activity的启动流程时贴了一张Activity数据结构,其实在WMS中也有与AMS中非常相似的数据结构。从下图中可以看到ActivityStack与TaskStack、TaskRecord与Task、ActivityRecord与ActivityWindowToken是一个一一对应的关系。但Window的划分比Activity更细,窗口分为应用窗口、系统窗口、子窗口,而WMS中的窗口从应用端来看是View的概念。
数据结构.jpg
二、应用窗口的添加
为了不将问题复杂化,这里先从应用端添加应用窗口出发,理清楚窗口添加的逻辑,同时也与上一篇的Activity的启动流程形成一个过渡。
addView.jpg
从上图看,添加窗口的链路并不长,但是其中涉及到的类其实很多。
addView-UML图.jpg
window-UML图.jpg
- 在调用ActivityThread的performLaunchActivity函数时,会创建一个Activity对象,并调用Activity的attach函数初始化了Activity的一些重要属性,也就是创建PhoneWindow对象和WindowManagerImpl对象。PhoneWindow代表了应用端的Window,一个Activity对应一个Window。WindowManagerImpl用于添加、更新、删除View。
ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
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,
r.assistToken);
...
}
Activity.java
final void attach() {
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
...
}
SystemServiceRegistry.java
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
- 写Activity的时候在onCreate函数中调用setContentView函数加载布局文件,在PhoneWindow中会新建DecorView对象mDecor,若所有的View构成一个View树,DecorView代表了根View。DecorView中包含一个LinearLayout的mContentRoot,这个LinearLayout包含两个部分,标题栏和内容栏,内容栏就是PhoneWindow的ViewGroup对象mContentParent。在setContentView函数设置的布局就放在mContentParent中。
Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
}
PhoneWindow.java
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
...
}
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
...
int layoutResource;
layoutResource = R.layout.screen_simple;
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
DecorView.java
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
final View root = inflater.inflate(layoutResource, null);
mContentRoot = (ViewGroup) root;
...
}
screen_simple.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
- 在调用ActivityThread的handleResumeActivity函数时,会获取上面创建的DecorView对象。将该DecorView添加到WMS中。WindowManagerGlobal是单例的,一个进程只有一个WindowManagerGlobal对象,WindowManagerGlobal中管理着所有的View和View对应的ViewRootImpl和属性,ViewRootImpl的作用是操作View。PhoneWindow的adjustLayoutParamsForSubWindow函数会根据View的类型设置WMS中对应窗口的分组属性,这里再次强调应用端的View对应WMS中的窗口。
<1> 在ActivityThread的handleResumeActivity函数中,指定DecorView的类型为TYPE_BASE_APPLICATION,则为应用窗口,所以WMS中窗口的分组属性为Token的代理对象。Token定义在ActivityRecord中,继承自IApplicationToken.Stub。
<2> 若为子窗口(1000~1999),WMS中窗口的分组属性为W的代理对象。W定义在ViewRootImpl中,继承自IWindow.Stub。
<3> 若为系统窗口(2000~2999),WMS中窗口的分组属性为null。
ActivityThread.java
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
final Activity a = r.activity;
ViewManager wm = a.getWindowManager();
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
a.mDecor = decor;
wm.addView(decor, l);
...
}
WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
...
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
...
}
PhoneWindow.java
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
CharSequence curTitle = wp.getTitle();
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
if (decor != null) {
wp.token = decor.getWindowToken();
}
...
} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
...
} else {
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
}
}
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
...
}
- WMS的addWindow函数主要的作用是检查是否可添加窗口,根据窗口类型(应用窗口、子窗口、系统窗口)创建或找到WindowToken对象,若为应用窗口则有对应的AppWindowToken对象。WindowToken和AppWindowToken代表着窗口令牌。创建对应的WindowState对象,WindowState代表着窗口。应用窗口的Parent为AppWindowToken、子窗口的Parent为WindowState,系统窗口的Parent为WindowToken。WindowState会计算mBaseLayer和mSubLayer,用于保存到Parent保存的Children列表中,保存的顺序影响着显示的顺序。WMS还会根据上面设置的窗口分组属性作为键,WindowState做为值加入到WMS的mWindowMap中,mWindowMap是一个WindowHashMap,保存着所有的窗口。
子窗口的保存顺序:
private static final Comparator<WindowState> sWindowSubLayerComparator =
new Comparator<WindowState>() {
public int compare(WindowState w1, WindowState w2) {
final int layer1 = w1.mSubLayer;
final int layer2 = w2.mSubLayer;
if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
return -1;
}
return 1;
};
};
应用窗口和系统窗口的保存顺序:
private final Comparator<WindowState> mWindowComparator =
(WindowState newWindow, WindowState existingWindow) -> {
return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
};
protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
WindowState existingWindow) {
return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
}
总而言之,WMS管理着所有的窗口,窗口的位置、可见性、显示顺序等都是由WMS决定的。