前言:
本文属于安卓页面绘制流程的第3篇,主要介绍应用是如何把APP侧的Window向system进行注册的。
主要分为2大块:
第一块,APP侧在resume周期时向系统侧申请绑定。
第二块,系统侧收到请求后处理绑定的流程。
一.APP侧Window注册
在上一篇文章中,我们已经讲过,在Activity的create周期内,其所对应的window及其中的布局创建完成。接下来,就是需要把这个window和系统侧做一个绑定。
1.1 Activity和ActivityClientRecord中成员变量赋值
首先,我们仍然看一下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来处理,而WindowManagerGlobal就是一个单例类,一个进程只会存在一个mGlobal对象。相关代码如下:
//WindowManagerImpl.java
mGlobal
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.addView(view, params,...);
}
1.2 WindowManagerGlobal装载Window
接下来,我们看一下WindowManagerGlobal.addView()中的逻辑。
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//1
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
ViewRootImpl root;
View panelParentView = null;
...
//2
if (windowlessSession == null) {
root = new ViewRootImpl(view.getContext(), display);
} else {
root = new ViewRootImpl(view.getContext(), display, windowlessSession);
}
view.setLayoutParams(wparams);
//3
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//4
root.setView(view, wparams, panelParentView, userId);
}
主要执行了以下的逻辑:
1.对最开始的WindowManager.LayoutParams进行一定的修正,补充一些必要的参数,具体内容我们2.3来讲。
2.创建ViewRootImpl,ViewRootImpl是一个很重要的角色,它负责维护window和系统侧的沟通,并且还是页面刷新显示流程的执行者。
3.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>();
4.上面说到,ViewRootImpl是流程的具体执行者,那么window的绑定自然也是交给其来处理。负责这个绑定任务的就是ViewRootImpl中的setView方法,我们2.4中来讲。
1.3 修复window的LayoutParams
这里的adjustLayoutParamsForSubWindow方法,是Window类提供的,我们看一下代码:
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW ...){
...
} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW){
} else {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
wp.setTitle(mAppName);
wp.packageName = mContext.getPackageName();
if (mHardwareAccelerated || (mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
wp.flags |= FLAG_HARDWARE_ACCELERATED;
}
}
}
其实主要就是给token和title赋值,title好理解。而token,其实是系统在Activity创建给分配Activity的那个token。
1.4 获取WindowSession
介绍setView之前,我们先来了解下WindowManagerGlobal中的WindowSession对象,它是一个负责和系统通信的binder引用。
我们直接看其中的getWindowSession()方法:
WindowManagerGlobal.java
public final class WindowManagerGlobal {
private static IWindowSession sWindowSession;
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
}
通过WindowManagerService的openSession方法获取的,我们再看一下系统侧的代码:
public class WindowManagerService{
@Override
public IWindowSession openSession(IWindowSessionCallback callback) {
return new Session(this, callback);
}
}
具体Session的代码我们就不看了,它就是一个binder的server端,负责接收APP过来的请求并进行处理,下一章要讲的addToDisplayAsUser方法就是它提供的。
至此,APP端负责和系统通讯的WindowSession已经成功获取了,并且把它传递给ViewRootImpl,下面的流程中,就会用到这个对象。
1.5 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());
}
相关代码进行了删减,只保留最核心的部分,这样看起来会比较清晰。
1.首先,在方法中通过requestLayout方法请求执行首次的View绘制流程。requestLayout中会生成一个任务放到主线程中去执行。首次绘制是十分关键的,window相关的内容都是首次绘制的时候传递到SF的,这个我们就不展开了,绘制流程我们系列第5篇会讲,首次通知SF我们会在第5篇介绍。
2.生成InputChannel对象,这个对象类似于一个回调,通过后面的binder方法传递给系统侧并注册。window上的点击事件,就会通过InputChannel回调通知到应用侧。生成WindowInputEventReceiver就用到了inputChannel,它会接收系统的通知然后通知到ViewRootImpl,最终通知到Activity和DecorView。
3.这里使用到2.3中获取的mWindowSession引用,把相关的对象传递给系统侧,进行相关的注册。传递的内容如下:
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 | APP提供的binder引用,server端是ViewRootImpl.W |
WindowManager.LayoutParams | attrs | window的显示属性 |
int | viewVisibility | 显示状态,首次的话是View.INVISIBLE |
int | layerStackId | displayId,首次的时候值为0 |
int | userId | 用户ID |
InputChannel | inputChannel | 事件通道,用于点击等事件的传递 |
第三章中,我们会详细的讲一下这些参数是如何使用的。
1.6 流程小节
整个流程如下图所示:
APP侧,首先构建WindowManagerGlobal维护所有的视图,并且为每个window都创建一个ViewRootImpl用于处理展示流程,并且首次的时候还会创建mWindowSession对象用于和系统通讯。最后,通过mWindowSession的addToDisplayAsUser方法,把相关的内容向系统侧进行注册。
二.系统侧Window绑定
为了方便一些对这块了解不多的读者,所以我们在介绍系统侧注册window的流程前,我们先对这一块的几个核心类和主要流程先做一个简单介绍。
2.1 核心类介绍
Window的注册,其实并不是把客户端的window对象注册到了系统,而是把ViewRootImpl中的内部类W(IWindow类型)的这个binder引用,提供给系统,让系统有了一个和应用交互的桥梁。
类名 | 功能介绍 |
com.android.server.wm.Session | 一个session对应一个应用进程,负责应用和系统之间的窗口注册/移除,SurfaceSession注册等等。 |
WindowManagerService | 顾名思义,用于所有应用的窗口管理。这里只是维护窗口的关系,并负责具体的渲染流程。 |
ViewRootImpl.W | ViewRootImpl提供的binder引用,负责和系统侧进行沟通。 |
SurfaceSession | 这个类注册是native的实现。负责维护应用和surfaceFlinger之间的连接。所以,APP刷新时是直接通知SF,并不需要经过system_server。 |
DisplayContent | 确定唯一的一块显示区域,由RootWindowContainer维护所有的视图区域。 |
2.2 把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, IWindow client, LayoutParams attrs, int viewVisibility,...) {
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, , client, token, parentWindow,appOp[0], attrs, viewVisibility, session.mUid, userId, session.mCanAddInternalSystemWindow);
...
if (openInputChannels) {
win.openInputChannel(outInputChannel);
}
...
//4
win.openInputChannel(outInputChannel);
...
//5
win.attach();
win.initAppOpsState();
...
win.mToken.addWindow(win);
}
我们先看一下方法的入参:
Session就是系统侧负责管理和应用视图交互的对象;
IWindow是应用侧ViewRootImpl传递过来的W对象;
attrs就是Window的显示参数;
viewVisibility是显示状态;
requestUserId是应用的UID;
outInputChannel则是传递过来的事件传递的通道。
然后我们再来看一下逻辑:
首先,根据displayId找到归属的DisplayContent,如果是首次,则创建,正常来说不会走创建流程。DisplayContent的作用确定唯一的显示区域,其实就是确定一块显示屏,用于跟踪一系列的WindowState;
然后,如果当前的window存在parent,则去查询其parent的WindowToken。WindowToken顾名思义,用于识别WindowState;
第三步,生成WindowState,这里的WindowState和APP侧的Window是对应的,WindowState就是在系统侧window的描述并负责和window进行通讯。
WindowState中使用到了很多的参数,我们做了一个来源整理:
第四步,绑定事件输入,这里的outInputChannel就是APP侧传递过来的。
最后,通过attch()方法完成绑定。
我们重点看一下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的数量。
2.3 流程小节
我们这里仍然做一个小的总结,window注册在系统侧的实现。其实就是接受一个客户端传递过来的binder引用对象IWindow,然后生成一个唯一的对应对象WindowState。并且在应用进程级别生成一个SurfaceSession去维护应用的ViewRootImpl和surfaceFlinger的关系。流程图如下:
三.总结
所以整个window的注册流程主要分为三块大块:
1.在Activity的resume流程中,会使用WindowManagerGlobal维护应用侧所有的视图。
2.WindowManagerGlobal添加Window时,会为其创建一个ViewRootImpl维护window,系统和SF的关系,并且负责向系统侧注册。注册时会传递一些必要的参数和通道对象,方便后续和系统的沟通。
3.系统收到后主要是生成window在系统侧的对象并记录。所以分别创建Window组的对象WindowToken和Window的系统侧对象WindowState并保存。
整体流程图如下:
到这里为止,只是完成了APP向system_server的注册,但是也页面的绘制显示并不是system_server负责的,而是surfaceFlinger。所以,system_server会帮助APP向SF发起相关的注册申请,而这一块,就属于我们下一篇要讲的SurfaceSession的创建。