前言
Window
表示一个窗口的概念,Android
中所有的视图都是通过Window
来呈现的,不管是Activity
、Dialog
、还是Toast
,它们的视图实际上都是附加在Window
上的,因此,Window
实际是View
的管理者。Window
是一个非常重要的子系统,这也是我们常说的WMS
(WindowManagerService
)。下面我们就分析一下Window
、WMS
、View
建立关联以及交互的一个基本过程。
Window体系相关UML类图
-
Session :是一个
Binder
对象,代表一个活跃的客户端会话,在每个进程中都有一个
Session 与WindowManager交互的对象。 -
WindowManagerService :也是一个
Binder
对象,负责对窗口的管理。 - Window :应用程序用来与窗口管理器交谈的界面。
- PhoneWindow :Window 的具体实现。
- WindowManagerImpl : 负责与系统窗口管理器通信、绑定到上下文、显示的操作。
- ViewRootImpl :负责View的(测量、摆放、绘制)三大流程。
- WindowManagerGlobal :WindowManager 的具体实现。
WindowManager
与WindowManager
联系上的第一步就是通过Context
的getSystemService
()方法,在分析文章Android源码中单例模式 中我们知道,各种系统服务会注册到ContextImpl
的一个map
容器中,然后通过该服务的字符串键获取,WindowManager
也是ContextImpl
中注册的众多服务之一,我们看下下面这段程序:
//窗口服务
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
最后一行代码中,我们看到了WindowManager
在Java层的具体实现,也就是WindowManagerImpl
。那Activity
或者Dialog
又是如何获取到WindowManager
对象呢?我们从上述代码知道,WindowManager
是注册到ContextImpl
中的,而getSystemService
也是Context
定义的接口,因此,我们就从Dialog的构造函数和Activity入手,因为Context
是传到Dialog
构造函数的。
Dialog构造函数
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
//获取WindowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
//设置Window回调
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
//设置Window的WindowManager对象
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
}
Activity的attch方法
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
...
//创建Window并设置window的监听
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
...
//Window设置WindowManager对象
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
无论是Dialog
还是Activity
都是通过Window
对象的setWindowManager
方法将WindowManager
与Window
关联。该函数是在Window
中,看看实现:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
...
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
最后调用的是WindowManagerImpl
的createLocalWindowManager
方法,这里与ContextImpl
注册的WindowManagerImpl
不同的是,这里多了一个parentWindow
参数,也就是说,此时构建的WindowManagerImpl
对象是与具体的Window
关联的,而ContextImpl
注册的并没有此参数。这是Window
已经和WindowManager
建立了初步联系。为什么这么说呢?我们看下WindowManagerImpl
的具体实现:
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
...
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
@Override
public Display getDefaultDisplay() {
return mContext.getDisplay();
}
}
显然WindowManagerImpl
还没有实现对视图的操作,Dialog是在show()方法里添加的,添加、更新、删除都交给了WindowManagerGlobal
这个类,通过以上分析对VIew
的操作实际上是调用的是WindowManagerGlobal
的方法,继续跟踪:
WindowManagerGlobal对View的操作
看上面UML类图我看到WindowManagerGlobal有几个重要的属性,
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
在上面声明中,没mViews
存储的是所有Window
所对应的View
,mRoots
存储的是所有Window
多对用的ContextImpl
,mParams
存储的是所有Window
的布局参数,而mDyingViews
则存储了那些整被删除的View
对象,或者说那些已经调用removeView
方法但是删除操作还未完成的Window
对象,在addView
中将Window
一系列对象添加到容器中。
WindowManagerGlobal的addView过程
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...//省略参数检查代码
ViewRootImpl root;
View panelParentView = null;
...
//创建ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
//设置参数
view.setLayoutParams(wparams);
//添加到容器列表中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// 调用ViewRootImpl的setView方法将VIew显示到手机上
root.setView(view, wparams, panelParentView);
}
上面程序主要完成以下工作;
- 构建ViewRootImpl;
- 将布局参数设置给View;
- 存储这些ViewRootImpl、View、LayoutParam到列表中;
- 通过ViewRootImpl的setView将View显示到窗口。
很多人对ViewRootImpl
并不陌生,从UML类图可以看出这个类里面有一个我们熟知的performTraversals
方法,ViewRootImpl
收到系统绘制View的消息后performTraversals
就会调用视图树的各个节点的meature
、layout
、draw
方法来绘制整颗视图树。
从上述代码分析来看,第一个重要步骤就是创建了ViewRootImpl
对象,我们看看它的构造方法:
public ViewRootImpl(Context context, Display display) {
mContext = context;
//获取Window Session,也就是也WindowManagerService建立联系
mWindowSession = WindowManagerGlobal.getWindowSession();
//保存当前线程,,更新Ui的 线程只能是创建ViewRootImpl时的线程,
//我们在开发中,如果在子线程更新UI会抛出异常,但并不是因为只有UI线程才能更新UI
//而是因为ViewRootImpl是在UI线程中创建的
mThread = Thread.currentThread();
...
}
获取WindowManagerService
我们看下ViewRootImpl
的构造函数中是如何获取到WindowManagerService
的:
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
InputMethodManager imm = InputMethodManager.getInstance();
//获取WindowManagerService
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
imm.getClient(), imm.getInputContext());
}
return sWindowSession;
}
}
//获取WindowManagerService
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
//aidl
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
}
return sWindowManagerService;
}
}
在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 Binder.allowBlocking(getIServiceManager().getService(name));
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
从程序中可以看到ServiceManager.getService
返回的是一个IBinder
对象,也就是说Android Framework
层与WMS
之间也是通过Binder
机制进行通讯。获取WMS
之后,又调用IWindowManager
.Stub
类的asInterface
方法,看到这里我们就会想起AIDL
,详情请看这篇文章理解AIDL ,将获取到的WMS
的IBinder
对象转换成WindowManager
对象,最后,通过openSession
函数来与WMS
建立一个通信会话,相当于Framework
层与native
层建立了一个长期合作的”办事处“,双方有什么需求都通过这个Session
来交换信息。
ViewRootImpl的setView方法
与WMS
建立Session
后就到了ViewRootImpl
的setView
方法了,该方法会向WMS
发起显示Dialog
或者Activity
中的DecorView
请求,具体代码:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
//请求布局
requestLayout();
//向WMS发起请求
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
}
}
setView
过程比较复杂,但我们只需要关注两步:
- requestLayout();
- 向WMS发起显示当前Window请求
ViewRootImpl的requestLayout过程
我们再来看下requestLayout
方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//发起绘制
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//注意第二个参数,第一个第三个省略
mChoreographer.postCallback(... , mTraversalRunnable , ... );
...
}
}
//创建子线程去绘制
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
//绘制入口
performTraversals();
}
最终会执行performTraversals
();方法,这是一个极其复杂有非常重要的函数。主要做了如下操作:
- 获取Surface对象,同于图形绘制
- 测量视图树中各个View的大小,performMeasure()
- 摆放整个视图树,performLayout()
- 绘制整棵视图树,performDraw()
代码如下:
private void performTraversals() {
//会调用View的onMeasure
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//会调用View的onLayout
performLayout(lp, mWidth, mHeight);
//会调用View的Draw
performDraw();
}
在performDraw
方法中,Framework
层获取到图形绘制表面的Surface
对象,然后获取它的可绘制区域,也就是我们的Canvas
对象,然后Framework
在这个Canvas
对象上绘制,具体代码如下;
private void performDraw() {
draw(fullRedrawNeeded);
}
private void draw(boolean fullRedrawNeeded) {
//获取绘制表面
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
//使用GPU绘制,也就是硬件加速
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
...
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
//使用CPU绘制图形
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
}
在draw
方法中会获取到需要绘制的区域,以及判断是否使用GPU
进行绘制。通常情况下使用的是CPU
绘制,也就是调用的是drawSoftware
()。我们看看该函数的实现:
//使用CPU绘制
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
//获取指定区域的获取指定区域的Canvas对象对象,用于绘制
canvas = mSurface.lockCanvas(dirty);
}
...
try {
...
//从DecorView开始绘制,也就是整个Window的根视图,整棵树都会绘制
mView.draw(canvas);
} finally {
try {
//释放Canvas锁,然后通知Surface更新这块区域,与开头照应
surface.unlockCanvasAndPost(canvas);
}
return true;
}
综上所述,上述的视图树绘制代码主要分为下面几个步骤:
- 判断是CPU还是GPU绘制
- 获取绘制表面的Surface对象
- 通过Surface对象获取并锁住Canvas绘图对象
- 从DecorView开始发起整颗树的绘制流程
- Surface对象解锁Canvas,并通知SurfaceFlinger更新视图
了解具体View的三大流程请看文章:
以上就是整个视图的绘制过程,但是此时Dialog
或者Activity
的View
并不能显示在手机屏幕上,WMS
只是负责管理手机上的View
,也就是说WMS
管理当前状态下那个View
应该显示在最上层。其实WMS
管理的并不是Window
,而是View
,只不过他管理的是属于某个WIndow
下的View
。
ViewRootImpl请求WMS添加Window过程
我们上面只是分析了ViewRootImpl
的requestLayout
过程,下面再回到ViewRootImpl
的setView
方法,绘制完成接着会通过WindowSession
最终来完成WIndow
的添加过程,在下面的代码中mWindowSession
的类型是IWindowSession
,它也是一个Binder
对象,真正的实现类是Session
,也就是Window
的添加过程是一次IPC
调用。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
在Session内部会通过WindowManagerService
来实现Window
的添加,代码如下:
frameworks\base\services\core\java\com\android\server\wm\Session.java
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
这样Window
的添加过程就交给了WindowManagerService
去处理了,在WMS内部会保留一个单独的Session
。具体Window
在WMS
内部如何添加的,本篇不对分析,至此对于View
的绘制以及视图如何添加到Window
整个流程已经很明了了。关于WMS
和Surface
系统的细节可以参考市面上关于源码的书籍。我们看下Window
的删除过程
WindowManagerGlobal的removeView过程
Window
的删除过程和添加过程一样,都是先通过WIndowManagerImpl
后,在进一步通过WindowManagerGlobal
来实现删除,下面是WindowManagerGlobal
的removeView
的实现:
public void removeView(View view, boolean immediate) {
...
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
}
...
}
removeView
的过程很清晰,首先通过findViewLoched
来查找待删除View
的索引,这个查找过程就是建立的数组遍历,然后再调用removeViewLocked
来做进一步的删除,如下所示:
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
removeViewLocked
是通过ViewRootImpl
来完成删除操作的。在WIndowManager
中提供了两个接口removeView
和removeViewImmediate
,分别表示异步删除和同步删除,一般不使用同步删除,以免发生意外的错误,这里主要说下异步删除的情况,具体的异步删除操作是由ViewRootImpl
的die
方法完成,在异步删除的情况下,die
方法只是发送了一个请求删除的消息后就立刻返回了,这个时候View
并没有完成删除操作,所以最后会将其添加到WindowManagerGlobal
的待删除列表mDyingViews
中,看下ViewRootImpl
的die
方法实现:
boolean die(boolean immediate) {
//同步删除 直接调用doDie,并返回
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
...
//发送handler消息
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
在die方法内部只是做了简单的判断,如果是异步删除,那么就发送一个MSG_DIE 的消息,ViewRootImpl
中的Handler
会调用doDie
方法,如果是同步删除,就不发送消息,直接调用doDie
方法,这就是这两种方法的区别。在doDie
方法中会调用dispatchDepatchedFromWindow
方法,真正删除View
的逻辑在dispatchDepatchedFromWindow
内部实现,代码如下:
void doDie() {
dispatchDetachedFromWindow();
//将WindowManagerGlobal的列表中移除保存的ViewRootImpl、View、Param
WindowManagerGlobal.getInstance().doRemoveView(this);
}
void dispatchDetachedFromWindow() {
//调用VIew的dispatchDetachedFromWindow();
mView.dispatchDetachedFromWindow();
...
//Session中的remove
mWindowSession.remove(mWindow);
...
}
public void remove(IWindow window) {
//WMS移除Window
mService.removeWindow(this, window);
}
//WindowManagerGlobal中移除保存的ViewRootImpl、View、Param
void doRemoveView(ViewRootImpl root) {
synchronized (mLock) {
final int index = mRoots.indexOf(root);
if (index >= 0) {
mRoots.remove(index);
mParams.remove(index);
final View view = mViews.remove(index);
mDyingViews.remove(view);
}
}
}
以上代码主要做了一下事情:
- 垃圾回收相关的工作,比如清除数据和消息,移除回调
- 通过Session的remove方法删除Window,同样也是一个IPC过程,最终会调用WMS的removeView方法
- 调用View的dispatchDetachedFromWindow();方法,对于View的dispatchDetachedFromWindow()我们不陌生,当View从Window中移除时,这个方法就会被调用,可以在这个方法内部做一些资源回收的工作,比如终止动画、停止线程。
- 调用WindowManagerGlobal的doRemoveView方法刷新数据,包括mViews、mRoots、mParams、mDyingViews,需要将当前Window所关联的这三类对象从列表中删除。
WindowManagerGlobal的updateViewLayout过程
Window
的删除过程我们已经分析完了,下面看下WIndow
的更新过程,还是要从WindowManagerGlobal
的updateViewLayout
说起,代码如下:
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
updateViewLayout
方法做的事情就比较简单了,首先他需要更新View的LayoutParams
并替换老的LayoutParams
,接着再更新ViewRootImpl
中的LayoutParams
,这一步是通过ViewRootImpl
的setLayoutParams
方法来实现的。在ViewRootImpl
的setLayoutParams
中会通过scheduleTraversales
方法来对View
重新测量布局以及绘制这三个过程,在performTraversales
会通过WindowSession
来更新Window
视图,这个过程最终是由WMS
的relayoutWindow
来具体实现的,同样也是一个IPC过程。
参考
- 《Android开发艺术探索》
- 《Android源码设计模式》