目录
- 前言
- 一、addview示例
- 二、addview流程
- 2.1 流程图
- 2.2 流程分析
- 2.2.1 Actitity的启动流程创建PhoneWindow和DecorView
- 2.2.2.WindowManagerImpl 添加view
- 2.2.3 ViewRootImpl.setView
- 三、总结
前言
WMS 功能繁杂,通过添加View流程进一步分析WMS
通过本文了解掌握以下知识:
Window是什么?
- 表示一个窗口的概念,是所有View的直接管理者,任何视图都通过Window呈现(点击事件由Window->DecorView->View;
Activity的setContentView底层通过Window完成)- Window是一个抽象类,具体实现是PhoneWindow。这个可以看Activity#attach方法源码
- 创建Window需要通过WindowManager创建,WindowManager是外界访问Window的入口,Window具体实现位于WindowManagerService中
WindowManager和WindowManagerService的交互是通过IPC完成
Window和View关系?
- Window和View通过ViewRootImpl建立联系,View是视图的呈现方式,但是不能单独存在,必须依附在Window这个抽象的概念上。
- WMS把所有的用户消息发给View/ViewGroup,但是在View/ViewGroup处理消息的过程中,有一些操作是公共的, Window把这些公共行为抽象出来, 这就是Window。
Activity、View、Window三者之间的关系?
- 在Activity启动过程其中的attach()方法中初始化了PhoneWindow,而PhoneWindow是Window的唯一实现类。
- 然后Activity通过setContentView将View设置到了PhoneWindow上,而View通过WindowManager的addView()、removeView()、updateViewLayout()对View进行管理。
一、addview示例
先看一个简单的案例。在主屏幕上添加一个TextView并展示,并且这个TextView独占一个窗口。
1.activity布局:
布局里什么都不绘制
2.addview
onCreate方法里获取Windowmanager
动态添加TextView ,设置其背景颜色、字体颜色
设置Windowmanager的宽高、TYPE
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_iccpanel);
TextView mview = new TextView(this);
mview.setText("add view");
mview.setBackgroundColor(Color.RED);
mview.setTextColor(Color.BLUE);
WindowManager mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams wmParams = new WindowManager.LayoutParams();
wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
wmParams.width = 200;
wmParams.height = 100;
mWindowManager.addView(mview, wmParams);
}
看一下效果:
WindowManager 通过addview的方式,将TextView显示到了屏幕上。
二、addview流程
WindowManager只是一个接口,真正实现类是WindowManagerImpl,并最终以代理模式交给WindowManagerGlobal实现。
可以通过源码自行查看,也可以阅读我的第一篇WMS文章WMS之窗口属性的WindowManager简介部分。
在addview之前,activity已经拥有了window实例和父布局View:
此块内容查看SetContentView()流程分析
接下来看addview的流程:
2.1 流程图
2.2 流程分析
2.2.1 Actitity的启动流程创建PhoneWindow和DecorView
WindowManager.addView添加窗口之前,TextView的onDraw不会被调用,也就说View必须被添加到窗口中,才会被绘制。只有申请了依附窗口,View才会有可以绘制的目标内存,而窗口的创建就是在Actitity的启动流程
在 Activity 的启动流程中,当调用 attach() 方法时,会创建 PhoneWindow 对象,并同时创建一个 WindowManagerImpl 实例来维护 PhoneWindow 内的内容。PhoneWindow 表示应用程序窗口的实体内容,而 WindowManagerImpl 则负责实现窗口的添加、删除、更新等操作。
在 Activity 的 onCreate() 方法中,通常会调用 setContentView() 方法来设置布局资源,这个方法内部会创建一个 DecorView 实例作为 PhoneWindow 的实体内容,同时将该视图添加到 WindowManagerImpl 管理的窗口列表中。DecorView 是一个特殊的 View,它包含所有 UI 控件,并为其提供一个基础框架,比如标题栏、状态栏、背景等。
2.2.2.WindowManagerImpl 添加view
WindowManagerImpl 作为WindowManager的实例,内部最终是以代理模式交给WindowManagerGlobal实现。
WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
WindowManagerGlobal.java
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");
}
/**
*检查view是否为null,如果是则抛出IllegalArgumentException异常。
检查display是否为null,如果是则抛出IllegalArgumentException异常。
检查params是否为WindowManager.LayoutParams类型,如果不是则抛出IllegalArgumentException异常。
*/
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//将params强制转换为WindowManager.LayoutParams类型,并赋值给wparams变量。
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
// 如果parentWindow不为null,则调用parentWindow的adjustLayoutParamsForSubWindow()方法来调整wparams的参数。
} else {
//如果没有parentWindow,则根据应用程序的硬件加速设置来设置View的硬件加速标志。
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
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) {
/**
*在同步块内部进行以下操作:
创建一个Runnable对象mSystemPropertyUpdater,用于监视系统属性的变化。
查找是否已经存在相同的view,如果存在则根据情况执行相应的操作。
对于panel window类型的窗口,查找其所依附的父窗口。
*/
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
IWindowSession windowlessSession = null;
// If there is a parent set, but we can't find it, it may be coming
// from a SurfaceControlViewHost hierarchy.
if (wparams.token != null && panelParentView == null) {
for (int i = 0; i < mWindowlessRoots.size(); i++) {
ViewRootImpl maybeParent = mWindowlessRoots.get(i);
if (maybeParent.getWindowToken() == wparams.token) {
windowlessSession = maybeParent.getWindowSession();
break;
}
}
}
//根据条件创建ViewRootImpl对象root,如果有父窗口,则使用windowlessSession创建ViewRootImpl对象。
if (windowlessSession == null) {
root = new ViewRootImpl(view.getContext(), display);
} else {
root = new ViewRootImpl(view.getContext(), display,
windowlessSession, new WindowlessWindowLayout());
}
//设置View的LayoutParams为wparams。
view.setLayoutParams(wparams);
//将view、root、wparams添加到相应的列表中(mViews、mRoots、mParams)。
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
//将view设置到root中,并处理可能出现的RuntimeException异常。
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
// BadTokenException or InvalidDisplayException, clean up.
if (viewIndex >= 0) {
removeViewLocked(viewIndex, true);
}
throw e;
}
}
}
WindowManagerGlobal中维护的和Window操作相关的3个列表,在窗口的添加、更新和删除过程中都会涉及这3个列表,它们分别是View 列表(ArrayList<View> mViews)
、布局参数列表(ArrayList<WindowManager.LayoutParams>mParams)
和ViewRootImpl列表(ArrayList<ViewRootImpl>mRoots
。
WindowManagerImpl 的addView()主要做了以下事情:
对传入的参数进行合法性检查,包括检查View、Display和LayoutParams是否为空,以及对LayoutParams进行类型转换和调整。
创建ViewRootImpl对象,用于管理View的显示和交互。
将View的LayoutParams设置为传入的LayoutParams。
将View、ViewRootImpl对象和LayoutParams添加到相应的列表中。
尝试将View设置到ViewRootImpl对象中,并处理可能出现的RuntimeException异常。
中间的IWindowSession 暂且忽略。
最后就到了ViewRootImpl 的setView方法
2.2.3 ViewRootImpl.setView
在上面的addView方法中创建了一个 ViewRootImpl 实例,将 ViewRootImpl 与 View 树进行关联,这样 ViewRootImpl 就可以指挥View树的具体工作,包括测量、布局和绘制等操作。ViewRootImpl 是一个核心组件,它作为控制器,负责处理系统消息队列、与 WindowManagerService 通信、触发 View 树的渲染等任务。
大致看一下ViewRootImpl类的重要内容
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {
...
//持有主线程
final Thread mThread;
// View
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添加rView时进行初始化,能和系统的WindowManagerService交互,管理View 的绘图和窗口状态;会持有View;
会进行绘制主线程检查,异步线程抛异常;
会进行我们所熟知视图绘制三大流程,performMeasure、performLayout、performDraw
;
在View中会被mParent变量所持有,在View.AttachInfo中被mViewRootImpl变量所持有,我们平时调用的getViewRootImpl()方法,就是获取的mViewRootImpl变量的引用。
调用View的invalidate()方法也会到ViewRootImpl里面,另外,除了绘制流程,它还承担了事件分发。
负责和WMS进行进程间通信。
继续看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);
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
//持有View
mView = view;
...
//AttachInfo持有View
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混合并绘制到屏幕上。
View的绘制流程则是通过调用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中,调用View 的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进行显示。
三、总结
addView:
1.创建ViewRootImpl;
2.将ViewRoot、DecorView、布局参数保存到WM的内部列表中;
3.ViewRoot.setView()建立ViewRoot和View的联系。
setView:
1.进行View绘制三大流程:performMeasure、performLayout、performDraw
2.会通过WindowSession完成Window的添加过程(一次IPC调用)
requestLayout
:内部调用scheduleTraversals(), 底层通过mChoreographer去监听下一帧的刷新信号。
mWindowSession.addToDisplay
: 执行WindowManagerService的addWindow
addWindow
: 检查参数等设置;检查Token;将Token、Window保存到WMS中;将WindowState保存到Session中。(WindowState WindowSession等知识)
附带一张WMS整体框架