前言:
View绘制流程中,主要流程是这样的:
1.用户进入页面,首先创建和绑定Window;
2.首次创建以及后续vsync信号来临时,会请求执行刷新流程;
3.刷新流程完成后,会通知SurfaceFlinger读取数据以及刷新页面。
本篇就是大流程中的第一个环节,重点讲解进入页面后,Window是如何创建以及绑定到系统侧的。
本文的流程主要分为以下三大块:
1.APP侧window和布局的创建流程;
2.APP侧window是如何绑定ViewRootImpl以及注册到系统侧的;
3.系统侧接收到window后,是如何处理的。
一.APP侧Window和View创建
1.1 创建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(){//1mWindow = new PhoneWindow();//3mWindow.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都已经有值了。
1.2 DecorView和ContentParent创建
接下来,我们看下callActivityOnCreate的流程。Activity.onCreate流程没有什么有关window的逻辑,但是一般我们都会在onCreate中调用setContentView,这个方法中却大有玄机,我们一起看一下:
//android.app.Window.java
public void setContentView(@LayoutRes int layoutResID) {//1getWindow().setContentView(layoutResID);
}//com.android.internal.policy.PhoneWindow
public void setContentView(int layoutResID) {if (mContentParent == null) {//2installDecor();}...//3mLayoutInflater.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上。
具体的解析逻辑不是本文的核心,这里就不去细讲了。
1.3 小结
至此,Activity的创建和其onCreate的流程已经结束,此时Activity中的成员变量mWindowManager和mWindow对象已经完成了赋值,总结一下,如下图所示:
二.APP侧Window注册
2.1 Activity和ActivityClientRecord中成员变量赋值
在第一章中,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();...//1a.mDecor = decor;//2l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;//3wm.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,...);
}
2.2 WindowManagerGlobal装载Window
接下来,我们看一下WindowManagerGlobal.addView()中的逻辑。
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {ViewRootImpl root;View panelParentView = null;...//1if (windowlessSession == null) {root = new ViewRootImpl(view.getContext(), display);} else {root = new ViewRootImpl(view.getContext(), display, windowlessSession);}view.setLayoutParams(wparams);//2mViews.add(view);mRoots.add(root);mParams.add(wparams);//3root.setView(view, wparams, panelParentView, userId);
}
主要执行了以下的逻辑:
1.创建ViewRootImpl,ViewRootImpl的角色是页面刷新显示流程的执行者。
2.WindowManagerGlobal的角色是维护客户端所有的页面的,所以自然而然的,其中就维护了很多集合。比如存储所有根布局的mView对象等等,这里就是往集合中注册的。
@UnsupportedAppUsage
private final ArrayList<View> mViews = new ArrayList<View>();
@UnsupportedAppUsage
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
@UnsupportedAppUsage
private final ArrayList<WindowManager.LayoutParams> mParams =new ArrayList<WindowManager.LayoutParams>();
3.上面说到,ViewRootImpl是流程的具体执行者,那么window的绑定自然也是交给其来处理。所以这里通过ViewRootImpl.setView方法来负责。
2.3 ViewRootImpl负责视图的绑定
接下来,我们就看下ViewRootImpl的setView()方法。
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {...if (mView == null) {mView = view;...//1requestLayout();//2InputChannel inputChannel = null;if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {inputChannel = new InputChannel();}//3res = 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 |
三.系统侧Window绑定
介绍系统侧的流程前,我们先对系统侧的几个核心类简单介绍下,因为大多数的读者对于系统侧的了解较少。
3.1 核心类介绍
类名 | 功能介绍 |
com.android.server.wm.Session | 一个session对应一个应用进程,负责应用和系统之间的窗口注册/移除,SurfaceSession注册等等。 |
SurfaceSession | 这个类注册是native的实现。负责维护应用和surfaceFlinger之间的连接。所以,APP刷新时是直接通知SF,并不需要经过system_server。 |
WindowManagerService | 顾名思义,用于所有应用的窗口管理。这里只是维护窗口的关系,并负责具体的渲染流程。 |
3.2 应用进程绑定唯一的IWindowSession
上一章有讲到,一个应用会有一个维护所有的视图的容器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方法。
3.3 把window注册到系统侧
接下来我们就看一下第二中讲到的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;...//1final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);...//2WindowToken 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)}...}...//3final WindowState win = new WindowState(this, session, );...if (openInputChannels) {win.openInputChannel(outInputChannel);}...//4win.openInputChannel(outInputChannel);...//5win.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的数量。
3.4 小结
我们仍然做一个小的总结,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仍然会创建和绑定的,只不过这时候展示的会是黑屏。