View绘制流程中,主要流程是这样的:
1.用户进入页面,首先创建和绑定Window;
2.首次创建以及后续vsync信号来临时,会请求执行刷新流程;
3.刷新流程完成后,会通知SurfaceFlinger读取数据以及刷新页面。
本篇就是大流程中的第一个环节,重点讲解进入页面后,Window是如何创建以及绑定到系统侧的。
本文的流程主要分为以下三大块:
1.APP侧window和布局的创建流程;
2.APP侧window是如何绑定ViewRootImpl以及注册到系统侧的;
3.系统侧接收到window后,是如何处理的。
Activity启动时,会经历performLaunchActivity和handleResumeActivity的流程,而window的创建以及decorView的创建,就是在launch的过程中。
我们首先看一下ActivityThread.performLaunchActivity中的代码:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
//Activity的创建
Activity activity = null;
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
//Activity的关联
activity.attach();
//执行Activity的onCreate流程
mInstrumentation.callActivityOnCreate()
...
}
我们看一下activity.attach中实现的相关内容:
//android.app.Activity
final void attach(){
//1
mWindow = new PhoneWindow();
//3
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),...);
mWindow.setColorMode(info.colorMode);
}
//com.android.internal.policy.PhoneWindow.java
public PhoneWindow(@UiContext Context context) {
public PhoneWindow(@UiContext Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
}
主要执行了以下的逻辑:
1.创建了Activity所绑定的Window,成员名为mWindow,类型为PhoneWindow。
2.在PhoneWindow中,mLayoutInflater赋值。我们的布局就是通过mLayoutInflater对象去解析的。
3.给Window对象绑定WindowManager,这个WindowManager实际上是WindowManagerImpl。
所以此时,Activity中的mWindow,以及PhoneWindow中的mWindowManager和mLayoutInflater都已经有值了。
接下来,我们看下callActivityOnCreate的流程。Activity.onCreate流程没有什么有关window的逻辑,但是一般我们都会在onCreate中调用setContentView,这个方法中却大有玄机,我们一起看一下:
//android.app.Window.java
public void setContentView(@LayoutRes int layoutResID) {
//1
getWindow().setContentView(layoutResID);
}
//com.android.internal.policy.PhoneWindow
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//2
installDecor();
}
...
//3
mLayoutInflater.inflate(layoutResID, mContentParent);
}
private void installDecor() {
if(mDecor == null){
mDecor = generateDecor(-1);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
主要执行了以下的逻辑:
1.调用Activity中所持有window去加载layout。
2.首次的时候,通过installDecor方法去创建根布局DecorView以及容器布局mContentParent。mContentParent是Activity上所有的View的父容器。
3.通过mLayoutInflater对象去解析生成布局对象,并且关联到mContentParent上。
具体的解析逻辑不是本文的核心,这里就不去细讲了。
至此,Activity的创建和其onCreate的流程已经结束,此时Activity中的成员变量mWindowManager和mWindow对象已经完成了赋值,总结一下,如下图所示:
在第一章中,Activity所对应的window及其中的布局创建完成了,所以下一步,就是需要把这个window向系统做一个绑定,这个流程,主要是在Activity的onResume周期中执行的。
首先,我们仍然看一下resume周期所对应的代码,如下:
//ActivityThread.java
public void handleResumeActivity(ActivityClientRecord r, ...) {
final Activity a = r.activity;
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
...
//1
a.mDecor = decor;
//2
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
//3
wm.addView(decor, l);
}
}
主要执行了以下的逻辑:
1.把window中的decor赋值给Activity中的mDecor;
2.设置Window.LayoutParams的type类型为WindowManager.LayoutParams.TYPE_BASE_APPLICATION;在安卓中,type决定window图层优先级,值越大优先级越高,部分图层优先级如下:
public static final int TYPE_BASE_APPLICATION = 1;//默认Activity对应的图层
public static final int FIRST_SYSTEM_WINDOW = 2000;//系统弹窗的图层
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;//Toast的图层
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;//悬浮窗的图层等级
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;//同上,用于替代上面那个
3.通过windowManager添加decor。这里wm的对象,实际上是WindowManagerImpl,而其中的addView方法中,又交给了WindowManagerGlobal来处理,相关代码如下:
//WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.addView(view, params,...);
}
接下来,我们看一下WindowManagerGlobal.addView()中的逻辑。
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {
ViewRootImpl root;
View panelParentView = null;
...
//1
if (windowlessSession == null) {
root = new ViewRootImpl(view.getContext(), display);
} else {
root = new ViewRootImpl(view.getContext(), display, windowlessSession);
}
view.setLayoutParams(wparams);
//2
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//3
root.setView(view, wparams, panelParentView, userId);
}
主要执行了以下的逻辑:
1.创建ViewRootImpl,ViewRootImpl的角色是页面刷新显示流程的执行者。
2.WindowManagerGlobal的角色是维护客户端所有的页面的,所以自然而然的,其中就维护了很多集合。比如存储所有根布局的mView对象等等,这里就是往集合中注册的。
@UnsupportedAppUsage
private final ArrayList mViews = new ArrayList();
@UnsupportedAppUsage
private final ArrayList mRoots = new ArrayList();
@UnsupportedAppUsage
private final ArrayList mParams =
new ArrayList();
3.上面说到,ViewRootImpl是流程的具体执行者,那么window的绑定自然也是交给其来处理。所以这里通过ViewRootImpl.setView方法来负责。
接下来,我们就看下ViewRootImpl的setView()方法。
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
...
if (mView == null) {
mView = view;
...
//1
requestLayout();
//2
InputChannel inputChannel = null;
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();
}
//3
res = mWindowSession.addToDisplayAsUser(...);
}
//
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,Looper.myLooper());
}
这一块的逻辑也是较为清晰的:
首先,通过requestLayout方法尝试进行首次View绘制的完整流程,虽然这时window还没有绑定上,但是并不影响View流程的开始,毕竟View流程中,只有最后的绘制流程才需要和SurfaceFlinger进行交互。
然后,生成InputChannel对象,这个对象类似于一个回调,通过后面的binder接口传递给系统侧。后面window上的点击事件,就会通过InputChannel回调通知到应用侧。后面把inputChannel绑定到WindowInputEventReceiver中,所以APP侧点击事件的来源,就是其中的onInputEvent方法。
最后,把相关的对象传递给系统侧,完成注册。传递的内容如下:
int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in int userId,
in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel,
out InsetsState insetsState, out InsetsSourceControl[] activeControls);
int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, in int userId, in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel, out InsetsState insetsState, out InsetsSourceControl[] activeControls);
整个流程如下图所示:
给系统侧传递的数据列表如下:
对象类型 |
成员变量名 |
解释 |
IWindow |
window |
对应ViewRootImpl中的IWindow.Stub,传递的一个binder对象 |
WindowManager.LayoutParams |
attrs |
window对应的layoutParams属性 |
int |
viewVisibility |
根布局的显示状态 |
int |
layerStackId |
displayId,显示区域的唯一ID |
int |
userId |
应用的userId |
InsetsVisibilities |
requestedVisibilities |
|
InputChannel |
outInputChannel |
事件分发流程中,传递的通道 |
InsetsState |
insetsState |
|
InsetsSourceControl |
activeControls |
介绍系统侧的流程前,我们先对系统侧的几个核心类简单介绍下,因为大多数的读者对于系统侧的了解较少。
类名 |
功能介绍 |
com.android.server.wm.Session |
一个session对应一个应用进程,负责应用和系统之间的窗口注册/移除,SurfaceSession注册等等。 |
SurfaceSession |
这个类注册是native的实现。负责维护应用和surfaceFlinger之间的连接。所以,APP刷新时是直接通知SF,并不需要经过system_server。 |
WindowManagerService |
顾名思义,用于所有应用的窗口管理。这里只是维护窗口的关系,并负责具体的渲染流程。 |
上一章有讲到,一个应用会有一个维护所有的视图的容器WindowManagerGlobal,那么它其中,一定有一个负责和系统侧通信的对象,这个对象就是IWindowSession。相关代码如下:
//WindowManagerGlobal.java
public static IWindowSession getWindowSession() {
if (sWindowSession == null) {
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(...);
}
}
//WindowManagerService.java
@Override
public IWindowSession openSession(IWindowSessionCallback callback) {
return new Session(this, callback);
}
也就是说,WindowManagerGlobal中只会持有一个sWindowSession对象,而WindowManagerGlobal对应一个应用的进程,所以IWindowSession是绑定唯一一个应用进程的。IWindowSession是一个binder的引用,其在系统侧的具体实现是Session。上面的addToDisplayAsUser方法,就是通过IWindowSession中提供的binder方法。
接下来我们就看一下第二中讲到的addToDisplayAsUser()方法,它负责把应用侧的Window向系统侧注册。我们看一下其在系统侧的实现:
//Session.java
class Session extends IWindowSession.Stub{
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
return mService.addWindow(this, ...);
}
}
逻辑很简单,直接交给WindowServiceManger的addWindow方法去处理,接下来我们就看下这个方法:
//WindowManagerService.java
public int addWindow(Session session, ...) {
WindowState parentWindow = null;
...
//1
final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
...
//2
WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);
...
if (token == null) {
if( hasParent ){
token = parentWindow.mToken;
} else if (){
token = new WindowToken.Builder(this, binder, type)
} else {
token = new WindowToken.Builder(this, binder, type)
}
...
}
...
//3
final WindowState win = new WindowState(this, session, );
...
if (openInputChannels) {
win.openInputChannel(outInputChannel);
}
...
//4
win.openInputChannel(outInputChannel);
...
//5
win.attach();
win.initAppOpsState();
...
win.mToken.addWindow(win);
}
首先,根据displayId找到归属的DisplayContent,DisplayContent的作用是用于跟踪一系列的WindowState;
然后,如果当前的window存在parent,则去查询其parent的WindowToken。WindowToken顾名思义,用于识别WindowState;
接下来,生成WindowState,这里的WindowState和APP侧的Window是对应的,WindowState就是在系统侧window的描述并负责和window进行通讯;
然后,绑定事件输入,这里的outInputChannel就是APP侧传递过来的。
最后,通过attch()方法完成绑定,我们重点看一下这个方法:
//WindowState.java
void attach() {
if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
mSession.windowAddedLocked();
}
//Session.java
void windowAddedLocked() {
if (mPackageName == null) {
mPackageName = wpc.mInfo.packageName;
}
if (mSurfaceSession == null) {
mSurfaceSession = new SurfaceSession();
...
mService.mSessions.add(this);
}
mNumWindow++;
}
简单来说,一个应用首次完成window.attch()的时候,初始化mPackageName和mSurfaceSession()。
而mSurfaceSession对应的就是显示在前台的区域,它初始化后,对应的就是native创建surface以及后续和surfaceFlinger交互的流程了,这个我们后面的文章来讲解。
最后使用mNumWindow记录Window的数量。
我们仍然做一个小的总结,window注册在系统侧的实现。其实就是接受一个客户端传递过来的binder引用对象IWindow,然后生成一个唯一的对应对象WindowState。并且在应用进程级别生成一个SurfaceSession去维护应用的ViewRootImpl和surfaceFlinger的关系。流程图如下:
最后,我们做一下总结,整个window的注册流程主要分为三块大块:
1.create流程主要是各种对象的初始化。流程中完成客户端window的创建以及mDecor,mContentParent等相关成员变量的初始化;
2.resume流程主要是window关系的维护。所以创建视图处理类ViewRootImpl,并且使用其把window向系统侧申请绑定;
3.系统收到后主要是生成window在系统侧的对象并记录。所以分别创建Window组的对象WindowToken和Window的系统侧对象WindowState并保存。
整体流程图如下:
1.如果onCreate中不调用setContentView,那么会执行后面的流程吗?
答:会的,即使不调用setContentView,只是不会有ContentView,但是DecorView仍然会创建和绑定的,只不过这时候展示的会是黑屏。