最全的View绘制流程(上)— Window、DecorView
如需转载请评论或简信,并注明出处,未经允许不得转载
目录
前言
对于接触Android开发不久的同学来说,要写一个页面,我们大多数时候都是先创建一个layout.xml
布局文件,在布局文件中进行页面搭建,然后通过Activity
的sentContentView()
将布局文件设置到Activity中,这样Android系统就自动帮我们绘制了这个页面。我们知道,在Android中,一个页面是由一个个View
组合而成的,那我们有没有想过,Android中View
的绘制流程是怎么样的呢?本文将分为上下两部分,上部分主要讲Activity
、Window
、WindowManager
、ViewRoot
等相关概念及互相之间的联系,了解这些有助于我们对View
的绘制流程有一个更系统的认识,下部分会详细介绍view的measure
、layout
、draw
的过程
Window和DecorView的创建
我们先从我们比较熟悉的activity.setContentView()
说起,查看这个方法的源码
private Window mWindow;
public void setContentView(@LayoutRes int layoutResID) {
//调用window对象的setContentView()
getWindow().setContentView(layoutResID);
//创建ActionBar
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
我们发现,Activity
的setContentView()
实际上调用的是Window
的setContentView()
mWindow
什么时候被创建的呢?
Activity.java
//只截取部分主要代码
final void attach(...) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
//创建PhoneWindow对象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
...
//通过Context.getSystemService(Context.WINDOW_SERVICE)的方式获取WindowManager的实例
//给window设置windowManger
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();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
}
可以看出,mWindow
是在Activity
的attach()
中被创建的,它实际上是一个PhoneWindow
对象,PhoneWindow
是Android提供的Window
唯一实现类。且系统会为每个Activity创建一个与之对应的Window
创建Window
后,还会通过Context.getSystemService(Context.WINDOW_SERVICE)
的方式获取WindowManager
的实例,并设置给Window
。WindowManager
是一个窗口管理类,稍后还会对WindowManager
做更加深入的分析
下面来看PhoneWindow
的setContentView()
@Override
public void setContentView(int layoutResID) {
//先判断mContentParent是否初始化
if (mContentParent == null) {
//如果没有初始化调用installDecor
//当前activity第一次调用sentContentView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//如果Activity没有过度动画,多次调用sentContentView会走这里
//移除mContentParent所有内部view
mContentParent.removeAllViews();
}
//判断是否使用了Activity的过度动画
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//设置动画场景
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//将资源文件通过LayoutInflater对象装换为View树
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//这个方法在Activity是一个空实现
//说明在Activity的布局改动时 (setContentView或者addContentView 方法执行完毕后会调用改方法) //所以各种View的findViewById方法什么的可以放在这里
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
这里我们重点关注installDecor()
,看看这个方法做了什么
//只截取部分主要代码
private void installDecor() {
if (mDecor == null) {
//如果mDecor为空则创建一个DecorView实例
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
//如果mContentParent为空则通过generateLayout创建一个
mContentParent = generateLayout(mDecor);
...
}
}
installDecor()
主要干两件事情
- 如果
mDecor
没有初始化,则通过generateDecor()
初始化 - 如果
mContentParent
没有初始化,则通过generateLayout()
初始化
generateDecor()
很简单,就是new
一个DecorView
实例
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
//创建new DecorView实例
return new DecorView(context, featureId, this, getAttributes());
}
下面来分析,generateLayout()
//只截取部分主要代码
protected ViewGroup generateLayout(DecorView decor) {
//根据当前style修饰相应样式
TypedArray a = getWindowStyle();
...
//一堆if判断,根据设置的主题样式来设置DecorView的风格
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
} else if(...){
...
}
//加载窗口布局
int layoutResource;
int features = getLocalFeatures();
//根据features选择不同的layoutResource(布局文件资源)
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if(...){
...
}
//加载layoutResource
View in = mLayoutInflater.inflate(layoutResource, null);
//DecorView是FrameLayout,往DecorView中添加根布局View
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//这里获取的就是mContentParent
//ID_ANDROID_CONTENT = com.android.internal.R.id.content;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
return contentParent;
}
根据设置的Window
主题样式来设置DecorView
的风格,接着为DecorView
添加子View
,而这里的子View
则是上面提到的mContentParent
,如果上面设置了FEATURE_NO_ACTIONBAR
,那么DecorView
就只有mContentParent
一个子View
。可以看我们文章一开始的那张图,图中就很好的阐述了DecorView
、TitleView
、mContentParent
三者之间的关系
所以这也就是为什么,在设置Activity属性的时候,比如
requestWindowFeature(Window.FEATURE_NO_TITLE)
需要在setContentView()
之前调用才会生效
小结
- 一个
Activity
对应一个Window
,Window
是在activity.attach()
的时候被创建的 - 在
Activity
中调用setContentView()
实际上是调用了Window
的setContentView()
- 调用
setContentView()
时,会去初始化DecorView
以及其内部的TitleView
和mContentParent
,可以通过设置Window.FEATURE_NO_TITLE
使得DecorView
内部只存在mContentParent
- 我们在xml中定义的
layout_activity.xml
实际上是mContentParent
的一个子View
通过WindowManager管理Window
上文中我们主要讲的是Window
的创建、DecorView
的创建以及DecorView
的子View
是如何被添加到DecorView
中去的。每个Activity
都有一个与之关联的Window
,那如何管理Window
呢?这时候就需要借助我们的WindowMananger
类。WindowManager
管理Window
实际上就是在管理Window
中的DecorView
下面来看一下DecorView
和WindowManager
是如何关联起来的
从Activity的启动开始分析(对Activity的启动过程感兴趣的还可以看Android应用进程的创建 — Activity的启动流程)
- 使用代理模式启动到
ActivityManagerService
中执行 - 创建
ActivityRecord
到mHistory
记录中 - 通过
socket
通信到Zgote
相关类创建process
- 通过
ApplicatonThread
与ActivityManagerService
建立通信 -
ActivityManagerService
通知ActiveThread
启动Activity
的创建 -
ActivityThread
创建Activity
加入到mActivities
中并开始调度Activity
执行 activityThread.handleLaunchActivity()
上面就是Activity启动的大致流程,紧接上面第7步,从ActivityThread
开始执行到DecorView
被添加到WindowManager
中的过程如下(代码这里就省略了,有兴趣的自己可以搜索关键代码看一下),这里重点关注handleResumeActivity()
方法
activityThread.handleLaunchActivity()
—> activityThread.performLaunchActivity()
—> activity.attach()
—> activity.onCreate()
—> activityThread.handleResumeActivity()
—>activityThread.performResumeActivity() —> activity.onResume()
—> windowManager.addView()
//只截取部分主要代码
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
...
// 这里会调用到activity.onResume()方法
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
...
if (r.window == null && !a.mFinished && willBeVisible) {
// 获得Window对象
r.window = r.activity.getWindow();
// 获得Window中的DecorView对象
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// 获得WindowManager对象
//这个WindowManager就是在activity.attach()的时候和window一起创建的
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
// 调用windowManager.addView方法
wm.addView(decor, l);
}
...
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
//设置decorView可见
r.activity.makeVisible();
}
}
}
}
}
通过调用windowManager.addView(decor, lp)
,这样一来,DecorView
和WindowManager
就建立了联系。WindowManager
继承了ViewManger
接口,但实际上其本身依然是一个接口,实现类是WindowManagerImpl
//WindowManagerImpl implements WindowManager
//WindowManager extends 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);
}
//只截取部分主要代码
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
public WindowManagerImpl(Context context) {
this(context, null);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
return new WindowManagerImpl(displayContext, mParentWindow);
}
//往Window中添加view
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
//更新布局
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
//将删除View的消息发送到MessageQueue中,稍后删除
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
//立刻删除Window中的view
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
}
注意:同样实现了
ViewManger
接口的还有ViewGroup
,我们知道ViewGroup
也有addView
方法,但是在ViewGroup
中是将普通的view
或者viewGroup
作为Children
加入,而在WindowManagerImpl
是将DecorView
作为根布局加入到PhoneWindow
中去,所以两个方法的作用是截然不同的
我们发现,WindowManagerImpl
并没有直接实现操作View的相关方法,而是全部交给WindowManagerGlobal
。WindowManagerGlobal
是一个单例类—即一个进程中最多仅有一个。创建WindowManagerGlobal
对象的方式如下
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
ViewManager、WindowManager、WIndowManagerImpl、WIndowManagerGlobal关系UML图
深入分析WindowManagerGlobal
分析WindowManangerGlobal
实际上就是分析其内部的三个方法
addView(View view, ViewGroup.LayoutParams params)
该方法的主要作用是将decorView
添加到viewRootImp
中,通过viewRootImp
对其内部的view
进行管理
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//参数检查
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
...
//判断是否有父Window,从而调整当前窗口布局参数(layoutParams)
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
//有,调整title和token
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
//无,应用开启了硬件加速的话,decorview就开启
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) {
if (mSystemPropertyUpdater == null) {
//创建一个Runnable,用来遍历更新所有ViewRootImpl,更新系统参数
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);
}
//看需要add的view是否已经在mViews中
//WindowManager不允许同一个View被添加两次
int index = findViewLocked(view, false);
if (index >= 0) {
// 如果被添加过,就看是否在死亡队列里也存在
if (mDyingViews.contains(view)) {
// 如果存在,就将这个已经存在的view对应的window移除
mRoots.get(index).doDie();
} else {
//否则,说明view已经被添加,不需要重新添加了
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
}
// 如果属于子Window层级
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
//遍历viewRootImpl集合,看是否存在一个window的IBinder对象和需要添加的window的token一 //致,之后赋值引用
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
//创建一个ViewRootImpl对象并保存在root变量中
root = new ViewRootImpl(view.getContext(), display);
//给需要添加的view设置params,也就是decorView
view.setLayoutParams(wparams);
//将decoView、布局参数以及新建的ViewRootImpl保存在三个集合中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
//将decorView设置给ViewRootImpl
//ViewRootImpl向WMS添加新的窗口、申请Surface以及decorView在Surface上的重绘动作
//这才是真正意义上完成了窗口的添加操作
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
- 父窗口修改新窗口的布局参数。可能修改的只有
LayoutParams.token
和LayoutParams.mTitle
两个属性。mTitle
属性不必赘述,仅用于调试。而token
属性则值得一提,每一个新窗口必须通过LayoutParams.token
向WMS
出示相应的令牌才可以。在addView()
函数中通过父窗口修改这个token
属性的目的是为了减少开发者的负担。开发者不需要关心token
到底应该被设置为什么值,只需将LayoutParams
丢给一个WindowManager
,剩下的事情就不用再关心了。父窗口修改token
属性的原则是:如果新窗口的类型为子窗口(其类型大于等于LayoutParams.FIRST_SUB_WINDOW
并小于等于LayoutParams.LAST_SUB_WINDOW
),则LayoutParams.token
所持有的令牌为其父窗口的ID(也就是IWindow.asBinder()
的返回值)。否则LayoutParams.token
将被修改为父窗口所属的Activity的ID(也就是在第4章中所介绍的AppToken
),这对类型为TYPE_APPLICATION
的新窗口来说非常重要。从这点来说,当且仅当新窗的类型为子窗口时addView()
的parentWindow
参数才是真正意义上的父窗口。这类子窗口有上下文菜单、弹出式菜单以及游标等等,在WMS
中,这些窗口对应的WindowState
所保存的mAttachedWindow
既是parentWindow
所对应的WindowState
。然而另外还有一些窗口,如对话框窗口,类型为TYPE_APPLICATION
, 并不属于子窗口,但需要AppToken
作为其令牌,为此parentWindow
将自己的AppToken赋予了新窗口的的LayoutParams.token
中。此时parentWindow
便并不是严格意义上的父窗口了 - 为新窗口创建一个
ViewRootImpl
对象。顾名思义,ViewRootImpl
实现了一个控件树的根。它负责与WMS
进行直接的通讯,负责管理Surface
,负责触发控件的测量与布局,负责触发控件的绘制,同时也是输入事件的中转站。总之,ViewRootImpl
是整个控件系统正常运转的动力所在,无疑是本章最关键的一个组件。 - 将控件、布局参数以及新建的
ViewRootImpl
对象以相同的索引值添加到三个对应的集合mViews
、mParams
以及mRoots
中,以供之后的查询之需。控件、布局参数以及ViewRootImpl
三者共同组成了客户端的一个窗口。或者说,在控件系统中的窗口就是控件、布局参数与ViewRootImpl
对象的一个三元组
updateViewLayout(View view, ViewGroup.LayoutParams params)
该方法的主要作用是更新decorView
的layoutParams
,如layoutParams.width
从100变为了200,则需要将这个变化通知给WMS
使其调整Surface
的大小,并让窗口进行重绘
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
//参数检查
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
//将layoutparams保存到decorView中
view.setLayoutParams(wparams);
synchronized (mLock) {
// 获取decorView在三个集合中的索引
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
//更新layoutParams到集合中
mParams.add(index, wparams);
//调用viewRootImpl的setLayoutParams()使得新的layoutParams生效
root.setLayoutParams(wparams, false);
}
}
removeView(View view)
该方法的作用是从3个集合中删除此Window
所对应的元素,包括decorView
、layoutPrams
以及viewRootImpl
,并要求viewRootImpl
从WMS
中删除对应的Window
,并释放一切需要回收的资源
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);//其内部会调用root.die(immediate)
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
要求viewRootImpl
从WMS
中删除窗口并释放资源的方法是调用viewRootImpl.die()
函数。因此可以得出这样一个结论:viewRootImpl
的生命从setView()
开始,到die()
结束
通过ViewRootImp管理View
ViewRootImpl
实现了ViewParent
接口,它是WindowManagerGlobal
工作的实际实现者
我们先来看setView()
做了什么
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//mView保存了decorView
mView = view;
//mWindowAttributes保存了窗口所对应的LayoutParams
mWindowAttributes.copyFrom(attrs);
attrs = mWindowAttributes;
...
//请求UI开始绘制
requestLayout();
//初始化mInputChannel
//InputChannel是窗口接受来自InputDispatcher的输入事件的管道
//注意,仅当窗口的属性inputFeatures不含有INPUT_FEATURE_NO_INPUT_CHANNEL时
//才会创建InputChannel,否则mInputChannel为空,从而导致此窗口无法接受任何输入事件
mInputChannel = new InputChannel();
try {
//通知WindowManagerService添加一个窗口,注册一个事件监听管道
//用来监听 按键(KeyEvent)和触摸(MotionEvent)事件
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mInputChannel);
}
...
}
}
setView()内部的执行过程viewRootImp.setView()
—> viewRootImp.requestLayout()
—> viewRootImp.scheduleTraversals()
—> viewRootImp.doTraversal()
—> viewRootImp.performTraversals()
—>进入view的绘制流程
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//线程检查,这里就是判断更新UI的操作是否在主线程的方法
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
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");
}
//开始view的绘制
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
总结
本文主要帮助我们了解View
的绘制开始之前经过了哪些流程,这有利于我们对整个View
的绘制体系有一个更全面的认识,下面我们来具体分析View的
最全的View绘制流程(下)— Measure、Layout、Draw