简介
想要学习WMS,首先要具备一些前置知识,比如Window、WindowManager等等,这里就来简单讲一下与Window有关的一些体系知识,希望能有所帮助。
Window、WindowManager、WMS的关系
Window是一个抽象类,其具体的实现类是PhoneWindow,它对View进行管理。WindowManager是什么呢?听名字就能感觉到,它是用来管理Window的,只不过它是一个接口,继承自ViewManager,其实现类为WindowManagerImpl。如果我们想要对Window进行添加、删除、更新等操作,就可以通过WindowManager,而WindowManager会将具体的工作交给WMS(WindowManagerService)来处理,WindowManager和WMS通过Binder进行跨进程通信。
WindowManager与WindowManagerImpl
上文说过,WindowManager继承自ViewManager,我们首先看一下ViewManager
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
ViewManager中定义了三个方法,分别对应view的添加、更新、删除操作,同时这些方法的入参都是View类型,说明Window也是以View的形式存在的。
Window是一个抽象类,其具体实现为PhoneWindow,PhoneWindow是在Activity的attach()方法中创建的
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, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
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();
...
}
在创建PhoneWindow之后,又调用了 setWindowManager()方法,其实现在Window类中,看一下其实现:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
WindowManager的创建是通过mContext.getSystemService(Context.WINDOW_SERVICE);方法实现的,之前的文章介绍过Context的实现类是ContextImpl,我们看一下ContextImpl中的实现
ContextImpl.java
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
继续看
SystemServiceRegistry.java
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
通过SYSTEM_SERVICE_FETCHERS.get(name),得到 ServiceFetcher, SYSTEM_SERVICE_FETCHERS是一个HashMap,其中有一个registerService()注册的方法是在这个Hashmap中添加元素的
SystemServiceRegistry.java
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
在SystemServiceRegistry初始化的时候,会进行许多的注册操作。我们看一下传入的 Context.WINDOW_SERVICE 在注册的时候做了什么
SystemServiceRegistry.java
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
找到了,其对应的是 WindowManagerImpl实例,也就是说 上文中(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),得到的是一个WindowManagerImpl实例,好了 我们回到 setWindowManager()方法中,
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); createLocalWindowManager(this),做了什么呢?
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
也是创建WindowManagerImpl,只是,这次创建,将Window作为参数传进来了,这样WindowManagerImpl就拥有了Window的引用,可以对Window进行操作了。
添加窗口的过程
上面WindowManagerImpl已经有了Window的引用了,可以对Window进行操作了,如果现在要添加一个窗口,会经过哪些流程呢?
WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
这里调用mGlobal的addView方法,其中最后一个参数mParentWindow就是Window对象。mGlobal是什么呢?
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
这里WindowManagerImpl将添加窗口的功能委托给 WindowManagerGlobal,WindowManagerGlobal是一个单例,在一个进程中只有一个WindowManagerGlobal实例,而WindowManagerImpl就可能会有多个实例。
接着来看一下WindowManagerGlobal的addView方法吧
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
在这里我们看到了一个很重要的类的声明和初始化,ViewRootImpl是一个我们经常要打交道的类,它肩负了很重要的职责
- View树的根,并管理View树
- 触发View的测量、布局、绘制
- 输入事件的中转站
- 管理Surface
- 与WMS进行通信 我们继续看ViewRootImpl的setView方法,这个方法可以说是一个非常核心的方法了。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
requestLayout();
...
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
}
...
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
}
}
}
这里有几个点需要关注,我们先来关注 mWindowSession.addToDisplay()方法,mWindowSession是一个Binder对象,用于进程之间通信,通过mWindowSession就可以与WMS进行通信了
Session.java
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
}
mService就是WindowManagerService的实例,通过调用WMS的addWindow方法,将this也就是session当做参数传入,每个进程对应一个session,WMS会使用List集合来保存Session,接下来的工作就交给了WMS。WMS将为这个窗口分配Surface,并确定其显示的次序,最终WMS将Surface交由SurfaceFlinger进行处理,SurfaceFlinger将这些Surface混合并绘制在手机屏幕上。
经过以上的讲解,我们再来回首总结一下Window关联出来的几个类吧,如图所示
更新窗口的过程
更新窗口的流程跟添加窗口的过程大体上是一样的,只是不再调用addView()方法,而是调用updateViewLayout()方法,那就从WindowManagerGlobal的updateViewLayout()开始
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
//将更新的参数设置到View中
view.setLayoutParams(wparams);
synchronized (mLock) {
//得到要更新的窗口在View列表中的索引
int index = findViewLocked(view, true);
//在ViewRootImpl列表中根据索引得到窗口的ViewRootImpl
ViewRootImpl root = mRoots.get(index);
//将参数设置到ViewRootImpl中
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
这里最终会调用viewRootImpl的setLayoutParams方法
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
...
scheduleTraversals();
}
}
ViewRootImpl中会调用 scheduleTraversals() 方法,
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//通过编舞者post一个回调,这个回调将在下一帧被渲染执行
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
通过mChoreographer编舞者,发送一个回调,这个回调将在下一帧被渲染时执行。mTraversalRunnable 是一个TraversalRunnable类型
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
回调执行时,会执行其run方法,doTraversal方法又会调用performTraversals方法,performTraversals方法使得ViewTree开始View的工作流程
private void performTraversals() {
...
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
}
通过这样的流程之后,view视图就更新完成了。
Window的属性
我们了解了Window的添加过程,其实Window的更新和删除过程跟其添加过程很相似,只是调用的方法不同而已,整体的流程还是一样的。Window有哪些属性呢?接下来就来简单说说。
分类
在WindowManager中的内部类LayoutParams定义了Window的类型
Window总体可以分为三大类
应用程序窗口(ApplicationWindow)
应用程序窗口有哪些呢?Activity就是典型的应用程序窗口,另外Dialog的窗口类型是 TYPE_APPLICATION。
/**
* Start of window types that represent normal application windows.
*/
public static final int FIRST_APPLICATION_WINDOW = 1;
public static final int TYPE_BASE_APPLICATION = 1;
public static final int TYPE_APPLICATION = 2;
public static final int TYPE_APPLICATION_STARTING = 3;
public static final int TYPE_DRAWN_APPLICATION = 4;
/**
* End of types of application windows.
*/
public static final int LAST_APPLICATION_WINDOW = 99;
注释写的很清楚,应用程序窗口包含这几种类型,这里的TYPE值表示的就是应用程序窗口类型的初始值,其范围为 1到99。
子窗口(Sub Window)
子窗口不能独立存在,需要附着在父窗口上,父窗口可以是一个应用类型的窗口,也可以是其他任意的窗口。PopupWindow就是典型的子窗口。
/**
* Start of types of sub-windows. The {@link #token} of these windows
* must be set to the window they are attached to. These types of
* windows are kept next to their attached window in Z-order, and their
* coordinate space is relative to their attached window.
*/
public static final int FIRST_SUB_WINDOW = 1000;
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
/**
* End of types of sub-windows.
*/
public static final int LAST_SUB_WINDOW = 1999;
以上就是子窗口对应的TYPE值,范围为1000到1999.
系统窗口(System Window)
系统窗口不需要对应Activity,也不需要有父窗口。一般情况下,系统窗口应该由系统来创建,如ANR的提示窗,系统状态栏,屏保等。
/**
* Start of system-specific window types. These are not normally
* created by applications.
*/
public static final int FIRST_SYSTEM_WINDOW = 2000;
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
...
/**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
系统窗口的Type范围为2000到2999.
窗口的显示顺序
Z-Oder
当一个进程向WMS申请一个窗口时,WMS会为窗口确定显示次序。窗口除了有X轴和Y轴以外,还有垂直于屏幕的Z轴,从屏幕内指向屏幕外,窗口的显示次序也就是窗口在Z轴的次序,这个次序称为Z-Oder。
上边所说的Type值就是Z-Oder排序的依据,Type值越大则Z-Oder的排序越靠前也就越接近用户。当然Type只是排序的依据之一,当多个窗口的Type值一样的时候,WMS会结合多种情况来得出最终的排序。
小结
与Window相关的东西捋下来还是挺多的,但这也只是其冰山一角,想要更深入的学习,还需要找一找其他的资料来辅助学习,当然这是学习WMS的基础,接下来就去揭开WMS的面纱吧。