Android 窗口机制 SDK31源码分析 总目录
- 初识
- DecorView与SubDecor的创建加载
- Window与Window Manager的创建加载
- ViewRootImpl的创建以及视图真正加载
- ViewRootImpl的事件分发
- 一定要在主线程才可以更新UI吗?为什么?
- Activity的Token和Dialog的关系
- Toast机制 - 封装可以在任何线程调用的toast
- 总结
通过上一章我们知道在handleResumeActivity方法中,最终将将DecorView通过addView方法被添加到了WindowManager之中了。那么这里面干了什么呢?
这里涉及到一个重要的类ViewRootImpl,本章详细解析ViewRootImpl的创建以及视图真正的加载过程
ViewRootImpl
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {
...
//持有主线程
final Thread mThread;
//DecorView
View mView;
final View.AttachInfo mAttachInfo;
...
//执行setView方法去想Window添加View
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {...}
void doTraversal() {...}
void scheduleTraversals() {...}
...
}
差不多见名识意吧:
- 它会在WindowManager调用addView添加DecorView时进行初始化,能和系统的WindowManagerService交互,管理DecorView 的绘图和窗口状态;会持有DecorView;
- 会进行绘制主线程检查,异步线程抛异常;
- 会进行我们所熟知视图绘制三大流程,performMeasure、performLayout、performDraw;
- 在View中会被
mParent
变量所持有,在View.AttachInfo中被mViewRootImpl
变量所持有,我们平时调用的getViewRootImpl()
方法,就是获取的mViewRootImpl
变量的引用。 - 调用View的
invalidate()
方法也会到ViewRootImpl里面,另外,除了绘制流程,它还承担了事件分发(即输入事件中转站,下一章节讲到)。 - 负责和WMS进行进程间通信。
源码调用分析
通过上一章节分析我们知道,WindowManager调用addView
方法进行管理DecorView。我们又知道最终肯定是通过WindowManagerGlobal来进行实际的执行,所以我们直接看WindowManagerGlobal的addView方法:
WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
...
ViewRootImpl root;
View panelParentView = null;
...
root = new ViewRootImpl(view.getContext(), display);
//保存ViewRootImpl、DecorView以及params
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
//调用ViewRootImpl的setView方法
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
在addView
中我们看到了ViewRootImpl
,首先构建ViewRootImpl
,之后通过setView
方法,将DecorView传递到了ViewRootImpl
。
另WindowManagerGlobal中维护的和Window操作相关的3个列表,在窗口的添加、更新和删除过程中都会涉及这3个列表,它们分别是View 列表(ArrayList<View> mViews)
、布局参数列表(ArrayList<WindowManager.LayoutParams>mParams)
和ViewRootImpl列表(ArrayList<ViewRootImpl>mRoots
。
看ViewRootImpl
的构建以及setView
的源码:
//ViewRootImpl构造方法
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
...
//持有主线程,即创建ViewRootImpl的线程
mThread = Thread.currentThread();
//初始化AttachInfo,内部传递持有了ViewRootImpl,后续的代码里,它将被传递给View,所以可以通过View获取到mAttachInfo,进而获取到ViewRootImpl。
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
}
ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
//持有DecorView
mView = view;
...
//AttachInfo持有DecorView
mAttachInfo.mRootView = view;
//调用requestLayout
requestLayout();
...
//通过WindowSession来完成window最终的添加,该过程mWindowSession类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,所以这其实是一次IPC的过程,远程的调用了Session的addToDisPlay方法。
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
mTempControls);
...
//将自己传递给View的mParent变量,如此调用View相关的invalidate方法,就直接调用到了ViewRootImpl
view.assignParent(this);
...
}
}
除了相关变量的赋值操作,在setView
中有两个很值得注意的地方。
- 调用了
requestLayout
方法,其内部会调用到scheduleTraversals
,进而调用了measure、layout、draw
。 - 通过IPC,将window添加到WMS(WindowManagerService)进行管理。
针对将window添加到WMS这一块,我们可以看一下Session的代码:
Session
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
requestedVisibility, outInputChannel, outInsetsState, outActiveControls);
}
这个mService
就是WMS,也就是说ViewRootImpl通过Session可以和WMS进行跨进程调用,而我们看到Session也被WMS持有,每个应用程序进程都会对应一个Session,WMS会用ArrayList来保存这些Session。剩下的工作就交给WMS来处理,在WMS中会为这个添加的窗口分配Surface,并确定窗口显示次序,可见负责显示界面的是画布Surface,而不是窗口本身。WMS 会将它所管理的Surface 交由SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上。
DecorView的绘制流程则是通过调用requestLayout
来继续完成的
ViewRootImpl
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//判断当前是否是主线程即mThread,如果不是,则抛异常。就是在子线程更新UI没使用handler的话就会抛出的异常
checkThread();
//设置mLayoutRequested为true。
mLayoutRequested = true;
//进而测量、布局、绘制
scheduleTraversals();
}
}
在requestLayout
的方法中,首先进行调用者的线程检查,如果不是主线程,即不和mThread
指向的不是同一个线程对象,就抛出异常。随后设置mLayoutRequested
为true,这个变量是做什么呢?它主要用来区分是否需要进行测量和布局。最后调用mLayoutRequested
方法。
这里说明一下,requestLayout
方法和invalidae
方法均是通过最后均调用了scheduleTraversals
方法,那么是如何区分是不是需要测量和布局呢?就是上面提到的mLayoutRequested
变量了,如果是requestLayout
则赋值为true,就会调用测量和布局;如果是invalidae
,则默认就为false。
接下来看一下scheduleTraversals
及其相关方法的调用流程。
ViewRootImpl
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mTraversalRunnable
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
ViewRootImpl
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
//调用了performTraversals
performTraversals();
}
}
ViewRootImpl
private void performTraversals() {
final View host = mView;
...
//将mAttachInfo传入View,同时会调用View的onAttachedToWindow方法
host.dispatchAttachedToWindow(mAttachInfo, 0);
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
上面列出了scheduleTraversals
整个方法的调用链,下面来梳理一下:
- 给Choreographer注册了一个
mTraversalRunnable
。Choreographer用于接收显示系统的VSync信号,在下一个帧渲染时控制执行一些操作。Choreographer的postCallback方法用于发起添加回调,这个添加的回调将在下一帧被渲染时执行。所以我们知道mTraversalRunnable
里面的内容将在下一帧渲染时被执行。 - 我们看一下
mTraversalRunnable
的类型,内部调用了doTraversal
。而在doTraversal
内部调用了performTraversals
。 - 在
performTraversals
中,调用DecorView 的dispatchAttachedToWindow
方法,给View绑定了mAttachInfo
,进而可以调用到View的onAttachedToWindow
方法。之后就是熟悉的测量、布局、绘制调用流程。
ViewRootImpl的创建以及视图绘制流程就清楚了,和之前的结合进行简单的总结一下:
在handleLaunchActivity
中进行Activity的实例化,之后初始化WindowManager,Context等,Window持有WindowManager,给Activity绑定Window对象。然后调用到onCreate的setContentView,创建DecorView被Window持有。同时我们自己的布局包含在了DecorView中;之后调用handleStartActivity
方法,里面会调用onStart
以及onRestoreInstanceState
;
之后真正的显示过程在调用handleResumeActivity
回调调用onResume
之后,此时会将DecorView通过WindowManager的addView方法和WindowManager进行绑定,利用ViewRootImpl进行和WMS交互,加入window布局到到WMS,然后进行View的测量、布局、绘制,最后调用Activity的makeVisible
进行显示。
下一章节预告:ViewRootImpl的事件分发
创作不易,如有帮助一键三连咯🙆♀️。欢迎技术探讨噢!