深入了解WindowManager
所有需要显示到屏幕上的内容(包括Activity、Dialog等)都是通过WindowManager
来操作的,WindowManager
是一个非常重要的子系统,这就是我们常说的WMS(Window Manager Service)
。本文的目的是理清WindowManager
的基本知识脉络,抛开具体的细枝末节,只关心WindowManager
和 WindowManagerService
(后续简称WMS
)、Surface
、SurfaceFlinger
等建立关联以及交互的一个基本过程。
与WindowManager 联系上的第一步就是通过Context的getSystemService
方法。各种系统服务会注册到ContextImpl的一个map容器中,然后通过该服务的字符串健来获取,WindowManager也是在ContextImpl中注册的众多服务之一,我们来看看下面的程序:
registerService(WINDOW_SERVICE, new ServiceFetcher() {
Display mDefaultDisplay;
public Object getService(ContextImpl ctx) {
Display display = ctx.mDisplay;
if (display == null) {
if (mDefaultDisplay == null) {
DisplayManager dm = (DisplayManager)ctx.getOuterContext()
.getSystemService(Context.DISPLAY_SERVICE);
mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
}
display = mDefaultDisplay;
}
return new WindowManagerImpl(display);
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在最后一行代码中,我们看到来WindowManager在Java层的具体实现,也就是indowManagerImpl
。那么Dialog对象又是怎样获取到WindowManager对象的呢? 我们从上述代码知道,WindowManager是注册到ContextImpl 中的,而getSystemService
也是Context定义的接口,因此,需要先从Dialog 的构造函数进行分析,因为Context对象就是从Dialog构造函数传递进来的:
Dialog(Context context, int theme, boolean createContextThemeWrapper) {
//设置、包装Context 相关的代码
//1. 获取WindowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w;
//设置Window回调
w.setCallback(this);
//设置Window的WindowManager 对象
w.setWindowManager(mWindowManager, null, null);
//代码省略
}
2
3
4
5
6
7
8
9
10
11
12
可以看到最终是通过Window对象的setWindowManager
函数将Window对象与WindowManager 建立来联系,该函数是在Window类中,我们看看这个函数实现:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) {
//参数复制等代码
if (wm == null) {
wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
//注意这里,调用了createLocalWindowManager 函数
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
2
3
4
5
6
7
8
最后一句代码很重要,调用的是WindowManagerImpl 中的createLocalWindowManager
方法,我们继续跟踪下去:
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mDiaplay, parentWindow);
}
2
3
这个方法很简单,只是单纯地构建了一个WindowManagerImpl
对象,与ContextImpl注册的WindowManagerImpl
不同的是,这里多了一个parentWindow 对象,也就是说, 此时构建的WindowManagerImpl 对象是与具体的Window 关联的,而ContextImpl 注册的并没有此参数(只有一个mDisplay参数)。此时,在Java层上Window对象就已经和WindowManager建立了第一步的联系。
当然这只是慢慢长路的第一步,我们还是看看WindowManagerImpl的核心代码:
public final class WindowManagerImpl implement WindowManager {
private final WindowManagerGlobal = WindowManagerGlobal.getInstance();
//Window对象
private WindowManagerImpl(Display display, Window parentWindow) {
mDisplay = display;
mParentWindow = parentWindow;
}
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParanetWindow);
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
//代码省略
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
很明显,WindowManagerImpl也不是具体“干活”的对象,它也只是一个穿着Impl羊皮的“狼”罢了,添加View、移除View、更新View的布局等具体的工作都交给了WindowManagerGlobal
这个类,在这一切就绪之后,会调用WindowManager的addView方法请求系统将View显示到屏幕上,经过上述代码分析可知,实际上调用的是WindowManagerGlobal中的addView方法,继续跟踪:
//将View 添加到WindowManager中,也就是在手机窗口中显示该View
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
//检查参数有效性等代码省略
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
ViewRootImpl root;
View panelParentView = null;
syschronized (mLock) {
//代码省略
//1. 构建 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
//2. 给View设置布局参数
view.setLayoutParams(wparams);
//3. 将View添加到View列表中
mViews.add(view);
//4. 将ViewRootImpl 对象 root 添加到 mRoots对象中
mRoots.add(root);
mParams.add(wparams);
}
//do this last because it fires off messages to start doing thins
try {
//5. 调用ViewRootImpl 的setView方法将View显示到手机窗口中
root.serView(view, wparams, panelParentView);
} catch(RuntimeException e){
...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
上面程序主要分了以下4个步骤,具体概括如下:
- 构建ViewRootImpl;
- 将布局参数设置给View;
- 存储这些ViewRootImpl、View、LayoutParam到列表中;
- 通过ViewRootImpl 的setView 将View 显示到窗口上。
ViewRootImpl 乍一看它的名字可能会误以为它是一个View,但实际上不是这样的,它集成自Handler 类(api23以前) ,是作为native 层 与 Java层 View 系统通信的桥梁,比如我们熟知的performTraversals
函数就是收到系统绘制View的消息之后,通过调用视图树的各个节点的measure
、layout
、draw
方法来绘制整棵视图树。
对WindowManager有些了解的读者可能注意到,经过上述的剖析,我们才到Framework层,而WMS可是运行在native层的,Framework层如何与native层建立联系的关键点,不过我们这里只讨论与WMS的关联。从WindowManagerGlobal 的addView
函数中可以看到,第一个重要步骤就是构建了ViewRootImpl对象root,我们看看它的构造函数:
public ViewRootImpl(Context context, Display display) {
mContext = context;
//1. 获取Window Session,也就是与WindowManagerService 建立链接
mWindowSession = WindowManagerGlobal.getWindowSession();
//代码省略
//保存当前线程,更新UI的线程只能是创建ViewRootImpl 时的线程
//我们在应用开发中,如果在自线程中更新UI会抛出一场,但并不是因为只有UI线程才能更新UI
//而是因为ViewRootImpl 是在UI线程中创建的
mThread = Thread.currentThread();
}
2
3
4
5
6
7
8
9
10
我们看到ViewRootImpl的代码1处,又一个WindowManagerGlobal.getWindowSession()方法,通过函数命名以及WindowManagerGlobal 的作用来看,这很可能就是与native层建立通信的地方。
有代码才有真相,我们继续跟踪。
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
//1. 获取WindowManagerService
IWindowManager windowManager = getWindowManagerService();
//2. 与WindowManagerService建立一个Session
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
//获取WindowManagerService
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
try {
if (sWindowManagerService != null) {
ValueAnimator.setDurationScale(
sWindowManagerService.getCurrentAnimatorScale());
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowManagerService;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
在getWindowSession
函数中,Framework 层首先通过getWindowManagerService
函数获取到IWindowManager对象,该函数中通过ServiceManager.getService
函数获取WMS,并且将WMS转换为IWindowManager 类型。我们先看看ServiceManager.getService
函数代码:
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return getIServiceManager().getService(name);
}
}catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
2
3
4
5
6
7
8
9
10
11
12
13
从程序中可以看到 ServiceManager.getService
返回的是IBinder对象,也就是说AndroidFramework 与 WMS 之间也是通过Binder 机制进行通信,到了这一步我们已经与WMS建立来初步联系。获取WMS之后,有调用了IWindowManager.Stub类的asInterface 函数,将获取到的WMS的IBinder对象转换成WindowManager对象。最后,通过openSession函数来与WMS 建立一个通信会话,相当于Framework层与native层建立来一个长期合作的“办事处”,双方有什么需求都通过这个Session来交换信息。
但是,此时Dialog 或者 Activity 的View 并不能显示在手机屏幕上,WMS只是负责管理手机屏幕上View的z-order, 也就是说WMS 管理当前状态下哪个View应该在最上层显示。仔细想一下,你发现其实WMS管理的并不是Window,而是View,只不过它管理的是属于某个Window下的View。
与WMS建立Session之后就到了调用ViewRootImpl 的setView 方法了,该方法会想WMS发起显示Dialog 或者Activity中的DecorView 请求,具体代码如下:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
//1. 请求布局
requestLayout();
try {
//2. 向WMS发起请求
res = sWindowSession.add(mWindow, mWindowAttributes, getHostVisibility(), mAttachInfo.mContentInsets);
}
}
}
2
3
4
5
6
7
8
9
10
setView 很复杂,但是我们主要关注两步:
- requestLayout;
- 向WMS发起显示当前Window的请求。
我们先看看requestLayout 函数:
public void requestLayout() {
checkThread();
mLayoutRequested = true;
scheduleTraversals(); // 发送 DO_TRAVERSAL 消息
}
public void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
sendEmptyMessage(DO_TRAVERSAL);
}
}
2
3
4
5
6
7
8
9
10
11
就是往 handler 中发送了一个 DO_TRAVERSAL
消息,这个消息会触发整个视图树的绘制操作,也就是最终会执行performTraversals
函数,这是一个极为复杂又非常重要的函数。简单来说它主要做了如下操作:
private void performTraversals() {
//1. 获取Surface 对象,用于图形绘制
//2. 丈量整个视图树的各个View的大小,performMeasure函数
//3. 布局整个视图树,performLayout 函数
//4. 绘制整棵视图树,performDraw 函数
}
2
3
4
5
6
在第四步中,Framework 会获取到图形绘制表面Surface 对象,然后获取它的可绘制区域,也就是我们的Canvas对象,然后Framework 在这个Canvas 对象上绘制,具体代码如下:
private void performDraw() {
if (!mAttachInfo.mScreenOn && !mReportNextDraw) {
return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
//代码省略
mIsDrawing = true;
try {
//调用绘制函数
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
}
//代码省略
}
private void draw(boolean fullRedrawNeeded) {
//1. 获取绘制表面
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
//代码省略
//2. 绘图表面需要更新
if (!dirty.isEmpty() || mIsAnimating) {
//3. 使用GPU绘制,也就是硬件加速
if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnable()) {
//代码省略
//使用硬件渲染器绘制
attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, animating ? null : mCurrentDirty);
} else {
//代码省略
//4. 使用CPU 绘制图形
if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
在 draw 函数中会获取到需要绘制的区域,以及判断是否使用GPU 进行绘制。通常情况下使用的是CPU 绘制,也就是调用的是drawSoftware函数来绘制。我们看看该函数的实现:
//使用CPU绘制图形
private boolean drawSoftware(Surface surface, attachInfo attachInfo, int yoff, boolean scalingRequired, Rect dirty) {
//Draw with software renderer.
Canvas canvas;
try {
int left = dirty.left;
int top = dirty.top;
int right = dirty.right;
int bottom = dirty.bottom;
//1. 获取指定区域的Canvas对象,用于Framework层绘制
canvas = mSurface.lockCanvas(dirty);
//代码省略
} // 省略catch
try {
//代码省略
try {
//2. 从DecorView开始绘制,也就是整个Window 的根视图,这会引起整棵树的重绘
mView.draw(canvas);
//代码省略
} finally {
}
} finally {
try {
//3. 释放Canvas 锁, 然后通知SurfaceFlinger 更新这块区域
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
return false;
}
}
return true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
综上所述,上述的视图树绘制代码中主要分为下面几个步骤:
- 判断是使用CPU绘制还是GPU绘制。
- 获取绘制表面Surface对象。
- 通过Surface对象获取并锁住Canvas绘图对象。
- 从DecorView开始发起整棵视图树的绘制流程。
- Surface 对象解锁Canvas,并且同时SurfaceFlinger 更新视图。
内容绘制完毕之后请求WMS显示该窗口上的内容,至此,Activity、Dialog 等组件的View就显示到用户的屏幕上了。整个WMS系统是极为复杂的,涉及的概念、技术非常广泛,各个层次的交互错综复杂,我们在此只是在一个比较多的层次上概括性地梳理了它的整个脉络,它的简化结构如下图:
关于WindowManagerService 以及 Surface 系统的细节大家可以参考其他文章,我们在此就不做过多的分析了。
本文转载自《Android源码设计模式解析与实战》一书,手敲一遍,加深印象。原作者在这