Window和WindowManager和ViewRootImpl
1 Window
1.1什么是Window?
- Window是一个抽象类,提供了绘制窗口的一组通用API。
- Window负责Android中的显示,可以理解为一个View的载体,负责将这个View显示出来。-
- PhoneWindow是Window的唯一子类。
举例:Activity的mWindow属性就是一个Window对象,它实际是一个PhoneWindow对象,这个对象负责Activity的显示。DecorView是Activity中所有View的根View,因此mWindow对象可以说是DecorView的载体,负责将这个DecorView显示出来。
1.2 Window的类型
类型 | 层级(z-ordered) | 例子 |
---|---|---|
应用 Window | 1~99 | Activity |
子 Window | 1000~1999 | Dialog |
系统 Window | 2000~2999 | Toast |
- 子 Window无法单独存在,必须依赖与父级Window,例如Dialog必须依赖与Activity的存在。
- Window分层,在显示时层级高的窗口会覆盖在在层级低的窗口。
2 WindowManager
2.1 什么是WindowManager?
WindowManager是窗口管理器,它是一个接口,继承了ViewManager接口。
public interface ViewManager//定义对View的增删改
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
public interface WindowManager extends ViewManager {}//可见WindowManager也提供对View的增删改的接口方法
WindowManagerImpl是WindowManager的具体实现类。
获取WindowManagerImpl对象的方法:
context.getSystemService(Context.WINDOW_SERVICE)
context.getWindowManager()
2.2 WindowManager的作用
其实Window的具体创建和实现是位于系统级服务WindowManagerService内部的,我们本地应用是无法直接访问的,因此需要借助WindowManager来实现与系统服务通信,使得系统服务创建和显示窗口。通过WindowManager与WindowManagerService的交互的过程是一个IPC过程。因此可以说WindowManager是访问Window的入口。
- WindowManager作为我们唯一访问Window的入口,却只提供了对View的增删改操作。因此可以说操控Window的核心就是对载体View的操作。
2.3 使用WindowManager创建Window的过程
通过调用WindowManagerImpl对象的addView方法,会让系统的窗口服务按我们的要求帮我们创建一个窗口,并在这个窗口中添加我们提供的View。
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,mContext.getUserId());
}
- addView方法需要传入一个View对象和一个WindowManager.LayoutParams对象。WindowManager.LayoutParams比较常用的属性有flags与type,我们通过flags设置窗口属性,通过type设置窗口的类型。
可以看到,WindowManagerImpl内部是委托mGlobal的成员变量来实现的,mGlobal是一个WindowManagerGlobal对象。
public final class WindowManagerImpl implements WindowManager {
...
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
}
WindowManagerGlobal是单例模式,即一个进程中只有一个WindowManagerGlobal实例,所有的WindowManagerImpl对象都是委托这个实例进行代理的。
//经典懒汉式线程安全单例模式(那还记得双检锁和静态内部类方式实现吗...)
private static WindowManagerGlobal sDefaultWindowManager;
private WindowManagerGlobal() {
}
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
- WindowManagerGlobal维护了4个集合来统一管理整个进程中的所有窗口的信息,分别是:
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 | ArrayList<View> | 存储了所有Window所对应的View |
mRoots | ArrayList<ViewRootImpl> | 存储了所有Window所对应的ViewRootImpl |
mParams | ArrayList<WindowManager.LayoutParams> | 存储了所有Window所对应的布局参数 |
mDyingViews | ArraySet<View> | 存储的是即将被删除的View对象或正在被删除的View对象 |
WindowManager的addView方法委托给了mGlobal的addView方法。
WindowManagerGlobal.addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
//检查参数是否合法
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
//子Window需要调整部分布局参数
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
//创建ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
//设置View的布局属性
view.setLayoutParams(wparams);
//将相关信息保存到对应集合
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView, userId);//调用ViewRootImpl对象的setView方法(这里也是View绘制的根源)
} catch (RuntimeException e) {
...
}
}
}
3 ViewRootImpl
3.1 什么是ViewRootImpl?
ViewRootImpl是一个类,实现了ViewParent接口(该接口定义了成为一个View的parent的一些“职能”)。
public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {}
ViewRootImpl是链接WindowManager和DecorView的纽带(其前身叫ViewRoot)。ViewRootImpl有很多作用,它负责Window中对View的操作,是View的绘制流程和事件分发的发起者。WindowManager与WindowManagerService的IPC交互也是ViewRootImpl负责的,mGlobal的很多操作也都是通过ViewRootImpl来实现的。
PS:看到这我们可以类比WindowManager和ViewGroup的关系。
- ViewGroup实现了ViewManager和ViewParent两个接口,
- WindowManager实现了ViewManager接口,同时其内部通过ViewRootImpl来操控View的,ViewRootImpl实现了ViewParent接口。
因此一个进程中的所有WindowManager共同的合作的结果可以看成是一个负责管理该进程所有窗口的窗口Group,内部有很多窗口,并且能对这些窗口进行增删改。(个人看法)
3.2 ViewRootImpl的创建
public ViewRootImpl(Context context, Display display, IWindowSession session,boolean useSfChoreographer) {
mContext = context;
mWindowSession = session;//从WindowManagerGlobal中传递过来的IWindowSession的实例,它是ViewRootImpl和WMS进行通信的代理。
mDisplay = display;
mThread = Thread.currentThread();//保存当前线程
mFirst = true; //true表示第一次添加视图
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
...
}
- ViewRootImpl保存当前线程到mThread,然后每次处理来自控件树的请求时(如请求重新布局,请求重绘,改变焦点等),ViewRootImpl就会判断发起请求的thread与这个mThread是否相同,不相等就会抛出异常,由于ViewRootImpl是在主(UI)线程中创建的,且UI操作只能在主线程中运行。Activity中的ViewRootImpl的创建是在activity.handleResumeActivity方法中调用windowManager.addView(decorView)中。
- AttachInfo是View的内部类,AttachInfo对象存储了当前View树所在窗口的各种信息,并且会派发给View树中的每一个View。保存在每个View自己的mAttachInfo变量中。因此同一个View树下的所有View绑定的是同一个AttachInfo对象和同一个ViewRootImpl对象。
- view.getViewRootImpl获取ViewRootImpl对象
- Window对象可以通过获取DecorView再获取ViewRootImpl对象
3.3 继续Window创建的过程
ViewRootImpl.setView方法是View绘制流程的源头
ViewRootImpl.setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
setView(view, attrs, panelParentView, UserHandle.myUserId());
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {
synchronized (this) {
if (mView == null) {
...
requestLayout();
...
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls);
}
}
}
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();//判断是否创建ViewRootImpl时的线程(Activity中是主线程)
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//创建一个同步屏障(详见Android消息机制)
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//发送一条异步消息,mTraversalRunnable是处理这条消息的回调
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);//移除同步屏障
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();//View的绘制起点
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
ViewRootImpl.setView中先调用了requestLayout,完成View的绘制,再通过mWindowSession(IWindowSession是一个Binder对象,真正的实现类是Session)远程调用了addToDisPlay方法来完成Window的添加操作。
requestLayout中为什么要通过向主线程发送异步消息的方式来完成View的绘制呢???
在Activity的onCreate调用了setContentView后,只是将View添加到了DecorView中,DecorView真正的绘制是在activity.handleResumeActivity方法中,该方法最后会回调activity的onResume方法,因此你会发现在onCreate方法中创建子线程去更新UI不会报错。
performTraversals在绘制的最后会用dispatchOnGlobalLayout回调OnGlobalLayoutListener的onGlobalLayout()方法。因此我们可以使用view.getViewTreeObserver().addOnGlobalLayoutListener,实现onGlobalLayout() 方法来即将绘制完成的回调(至少measure和layout结束了)详见View的绘制流程
另外当手动调用invalidate,postInvalidate,requestInvalidate也会最终调用performTraversals,来重新绘制View。
一个结论:一个Window对应一个View也对应一个ViewRootImpl对象。