注册

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)例子
应用 Window1~99Activity
子 Window1000~1999Dialog
系统 Window2000~2999Toast
  • 子 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>();
属性集合作用
mViewsArrayList<View>存储了所有Window所对应的View
mRootsArrayList<ViewRootImpl>存储了所有Window所对应的ViewRootImpl
mParamsArrayList<WindowManager.LayoutParams>存储了所有Window所对应的布局参数
mDyingViewsArraySet<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对象。


0 个评论

要回复文章请先登录注册