-
Notifications
You must be signed in to change notification settings - Fork 116
View的绘制流程
Activity在ActivityThread的performLaunchActivity中创建,创建完成后会首先执行attach方法,在attach方法中实例化PhoneWindow并关联WindowManager:
// Activity#attach():
final void attach(...) {
attachBaseContext(context);
//初始化 PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
//初始化 WindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
}
// Window#setWindowManager():
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
//...
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
接下来在Activity的onCreate方法中会通过setContentView初始化DecorView,并将Activity的布局文件添加到DecorView的content布局中。在AppCompactActivity中还为DecorView设置了主题等布局。
在handleResumeActivity中,通过WindowManager的实现类WindowManagerImpl的addView方法将DecorView添加到了Window,在addView方法中通过WindowManagerGlobal的实例去addView,并且会实例化一个ViewRootImpl,最后把DecorView传递给了ViewRootImpl的setView。ViewRootImpl是DecorView的管理者,负责View树的测量、布局、绘制,以及通过Choreographer来控制View的刷新。
// ActivityThread
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// ... 省略无关代码
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 此处调用WindowManagerImpl的addView将DecorView添加到了WindowManagerImpl中
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
// ... 省略无关代码
}
// WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// ... 省略无关代码
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 初始化ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
try {
// 将DocerView添加到ViewRootImpl
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
}
}
}
WMS是所有Window窗口的管理者,负责Window的添加和删除、Surface的管理和事件派发等,因此在Activity中的PhoneWindow对象需要显示等操作,就必须与WMS交互才能进行。
在ViewRootImpl的setView方法中,会调用requestLayout,并且通过WindowSession的addToDisplay与WMS进行交互。WMS会为每一个Window关联一个WindowStatus.
SurfaceFlinger 主要是进行 Layer 的合成和渲染。
在 WindowStatus 中,会创建 SurfaceSession,SurfaceSession 会在 Native 层构造一个 SurfaceComposerClient 对象,它是应用程序与 SurfaceFlinger 沟通的桥梁。
经过步骤四和步骤五之后,ViewRootImpl 与 WMS、SurfaceFlinger 都已经建立起连接,但此时 View 还没显示出来,我们知道,所有的 UI 最终都要通过 Surface 来显示,那么 Surface 是什么时候创建的呢?
这就要回到前面所说的 ViewRootImpl 的 requestLayout 方法了,首先会 checkThread 检查是否是主线程,然后调用 scheduleTraversals 方法,scheduleTraversals 方法会先设置同步屏障,然后通过 Choreographer 类在下一帧到来时去执行 doTraversal 方法。简单来说,Choreographer 内部会接受来自 SurfaceFlinger 发出的 Vsync 垂直同步信号,这个信号周期一般是 16ms 左右。doTraversal 方法首先会先移除同步屏障,然后 performTraversals 真正进行 View 的绘制流程,即调用 performMeasure、performLayout、performDraw。不过在它们之前,会先调用 relayoutWindow 通过 WindowSession 与 WMS 进行交互,即把 Java 层创建的 Surface 与 Native 层的 Surface 关联起来。
// ViewRootImpl
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 通过Handler发送同步屏障阻塞同步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 通过Choreographer发出一个mTraversalRunnable,会在这里执行
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的绘制流程,会调用performMeasure方法、performLayout方法和performDraw方法。
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
View的绘制是从performTraversals开始,进行测量、布局和绘制的流程。
// ViewRootImpl
private void performTraversals() {
// 根据Window的宽高与LayoutParams来计算DecorView的MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...............
//measur过程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...............
//layout过程
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...............
//draw过程
performDraw();
}
// ViewRootImpl
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
上述代码中用到了MeasureSpec,MeasureSpec是一个32位的int值,高位代表测量模式,低30位代表测量大小,MeasureSpec将SpecMode和SpecSize封装成一个int值避免了过多的内存分配。
MeasureSpec是有Parent的SpecMode与子View的LayoutParams共同确定的,其计算规则如下表:
childLayoutParams/ParentSpecMode | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
dp/px | EXACTLY childSize |
EXACTLY childSize |
EXACTLY childSize |
match_parent | EXACTLY parentSize |
AT_MOST parentSize |
UNSPECIFIED 0 |
wrap_content | AT_MOST parentSize |
AT_MOST parentSize |
UNSPECIFIED 0 |
其中测量模式表示控件对应的宽高模式:
- UNSPECIFIED:父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小;日常开发中自定义View不考虑这种模式,可暂时先忽略;
- EXACTLY:父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;这里我们理解为控件的宽或者高被设置为 match_parent 或者指定大小,比如20dp;
- AT_MOST:子元素至多达到指定大小的值;这里我们理解为控件的宽或者高被设置为wrap_content。
对于模式和大小值的获取,只需要通过位运算即可。例如,通过mode和size相加就可以得到MeasureSpec: 当需要获得Mode的时候只需要用measureSpec与MODE_TASK相与即可: 想获得size的话只需要只需要measureSpec与~MODE_TASK相与即可,如下图:
首先根据Window的宽高与LayoutParamters来生成MeasureSpec,然后执行performMeasure流程。performMeasure方法会去调用DecorView的measure方法,mesaure是一个final修饰的方法,开发者无法重写measure,在measure会进行一些公用的测量,然后会调用onMeasure,并将MeasureSpec传递给onMeasure.
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
通过mView.measure将绘制流程交给了DecorView,即执行View中的measure方法。measure方法中会进行一些公用的逻辑处理,接着就会调用onMeasure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
而onMeasure的逻辑也非常简单,仅仅是设置了View的默认大小
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
getSuggestedMinimumWidth/Height会查看是否给View设置了背景和最小宽高,然后取最大值作为建议宽高
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
getDefaultSize中只有会根据measureSpec拿到测量模式和测量宽高,可以看到在AT_MOST和EXACTLY时都返回了MeasureSpec的specSize。即默认情况下要么是固定值要么是match_parent.
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
由上述可以看出对于View的测量流程而言,其本身宽高直接受限于父View的 布局要求,举例来说,父View被限制宽度为40px,子View的最大宽度同样也需受限于这个数值。因此,在测量子View之时,子View必须已知父View的布局要求,这个 布局要求, Android中通过使用 MeasureSpec 类来进行描述。
首先需要理解的是,每种ViewGroup
的子类的测量策略(也就是onMeasure()
函数内的逻辑)不尽相同,比如RelativeLayout
或者LinearLayout
宽高的测量策略自然不同,但整体思路都大同小异,即 遍历 测量所有子控件,根据父控件自身测量策略进行宽高的计算并得出测量结果。
以 竖直方向布局 的LinearLayout
为例,如何完成LinearLayout
高度的测量?本文抛去不重要的细节,化繁为简,将LinearLayout
高度的测量策略简单定义为 遍历获取所有子控件,将高度累加 ,所得值即自身高度的测量结果——如果不知道每个子控件的高度,LinearLayout
自然无法测量出本身的高度。
因此对于View
树整体的测量而言,控件的测量实际上是 自底向上 的,正如文章开篇 整体思路 一节所描述的:
对于完整的测量流程而言,父控件必然依赖子控件宽高的测量;若子控件本身未测量完毕,父控件自身的测量亦无从谈起。
此外,因为子控件的测量逻辑受限于父控件传过来的 布局要求(MeasureSpec), 因此整体逻辑应该是:
- 1.测量开始时,由顶层的父控件将布局要求传递给子控件,以通知子控件开始执行测量;
- 2.子控件根据测量策略计算出自身的布局要求,再传递给下一级的子控件,通知子控件开始测量,如此往复,直至到达最后一级的子控件;
- 3.最后一级的子控件测量完毕后,执行
setMeasuredDimension()
函数,其父控件根据自己的测量策略,将所有child
的宽高和布局属性进行对应的计算(比如上文中LinearLayout
就是计算所有子控件高度的和),得到自己本身的测量宽高; - 4.该控件通过调用
setMeasuredDimension()
函数完成测量,这之后,它的父控件再根据其自身测量策略完成测量,如此往复,直至完成顶层级View
的测量,自此,整个页面测量完毕。
这里的设计体现出了经典的 递归思想,1、2步骤,开始测量的通知自顶至下,我们称之为测量步骤的 递流程,3、4步骤,测量完毕的顺序却是自底至顶,我们称之为测量步骤的 归流程。
Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有子元素并调用其layout方法,在layout方法中onLayout方法又会被调用。layout方法确定View本身的位置,而onLayout方法会确定所有子元素的位置。
// 伪代码实现
public void layout(int l, int t, int r, int b) {
// 1.决定是否需要重新进行测量流程(onMeasure)
if(needMeasureBeforeLayout) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec)
}
// 先将之前的位置信息进行保存
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 2.将自身所在的位置信息进行保存;
// 3.判断本次布局流程是否引发了布局的改变;
boolean changed = setFrame(l, t, r, b);
if (changed) {
// 4.若布局发生了改变,令所有子控件重新布局;
onLayout(changed, l, t, r, b);
// 5.若布局发生了改变,通知所有观察布局改变的监听发送通知
mOnLayoutChangeListener.onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
layout中首先会通过setFrame方法来设定View四个顶点的位置,View四个顶点的位置一旦确定那么View在父容器中的位置也就确定了。
在View确定自身位置后,接着调用onLayout方法,这个方法的用途是父View如容器确定子元素的位置。onLayout的具体实现和具体的布局有关,所以View和ViewGroup中都没有真正实现onLayout方法。以LinearLayout为例:
// LinearLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
以竖直布局为例,来看layoutVertical方法
void layoutVertical(int left, int top, int right, int bottom) {
// ...
final int count = getVirtualChildCount();
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
// ...
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
在这里该方法会遍历所有子元素并调用setChildFrame方法来为子元素指定对应的位置,其中childTop会逐步增大,意味着后边的子元素会被放在靠下的位置。这刚好是竖直方向LinearLayout的特性。至于setChildFrame仅仅是调用了子元素的layout方法而已,这样父元素在layout方法中完成自己的定位后,就通过onLayout方法调用子元素的layout方法,子元素又会通过自己的layout方法来确定自己的位置,这样一层一层的传递下去就完成了整个View树的layout过程。setFrameLayout代码如下:
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
setFrame中的width和height就是子元素的测量宽高。而在layout方法中会通过setFrame取设置子元素四个顶点的位置,在setFrame中有如下赋值语句,这样一来子元素的位置就确定了:
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
draw的过程是将View绘制到屏幕上,View的绘制过程遵循如下几步:
- 绘制背景 drawBackground(canvas)
- 绘制自己(onDraw)
- 绘制child(dispatchDraw)
- 绘制装饰(onDrawScrollBars)
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
drawBackground(canvas);
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
// ...
}
View绘制过程的传递是通过dispatchDraw来实现的,dispatchDraw会通过遍历所有子元素的draw方法,如此draw事件就一层一层的
View中getWidth与getHeight的代码如下:
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
从getWidht和getHeightd 源码再结合mLeft、mRight、mTop和mBottom这四个遍历值的赋值来看,getWidth的返回值刚好就是View的测量值。因此,在View的默认实现汇总View的测量宽高和最终宽高是相等的,只不过测量宽高形成以Measure过程,而最终宽高形成与View的layout过程,即两者的赋值实际不同。测量宽高的赋值实际稍微早了一些。因此,在日常的开发中,我们可以认为View的测量宽高就等于最终宽高。
requestLayout():该方法会递归调用父窗口的requestLayout()方法,直到触发ViewRootImpl的performTraversals()方法,此时mLayoutRequestede为true,会触发onMesaure()与onLayout()方法,不一定会触发onDraw()方法。
invalidate():该方法递归调用父View的invalidateChildInParent()方法,直到调用ViewRootImpl的invalidateChildInParent()方法,最终触发ViewRootImpl的performTraversals()方法,此时mLayoutRequestede为false,不会触发onMesaure()与onLayout()方法,当时会触发onDraw()方法。
postInvalidate():该方法功能和invalidate()一样,只是它可以在非UI线程中调用。一般说来需要重新布局就调用requestLayout()方法,需要重新绘制就调用invalidate()方法。
- JMM与volatile关键字
- synchronized的实现原理
- synchronized等待与唤醒机制
- AQS的实现原理
- ReentrantLock的实现原理
- ReentrantLock等待与唤醒机制
- CAS、Unsafe类以及Automic并发包
- ThreadLocal的实现原理
- 线程池的实现原理
- Java线程中断机制
- 多线程与并发常见面试题
- Android基础知识汇总
- MVC、MVP与MVVM
- SparseArray实现原理
- ArrayMap的实现原理
- SharedPreferences
- Bitmap
- Activity的启动模式
- Fragment核心原理
- 组件化项目架构搭建
- 组件化WebView架构搭建
- 为什么 Activity.finish() 之后 10s 才 onDestroy ?
- Binder与AIDL
- Binder实现原理
- Android系统启动流程
- InputManagerService
- WindowManagerService
- Choreographer详解
- SurfaceFlinger
- ViewRootImpl
- ActivityManagerService
- APP启动流程
- PMS安装与签名校验
- Dalvik与ART
- 内存优化策略
- UI界面及卡顿优化
- App启动优化
- ANR问题
- 包体积优化
- APK打包流程
- 电池电量优化
- Android屏幕适配
- 线上性能监控1--线上监控切入点
- 线上性能监控2--Matrix实现原理
- Glide实现原理
- OkHttp实现原理
- Retrofit实现原理
- RxJava实现原理
- RxJava中的线程切换与线程池
- LeakCanary实现原理
- ButterKnife实现原理
- ARouter实现原理
- Tinker实现原理
- 2. 两数相加
- 19.删除链表的倒数第 N 个结点
- 21. 合并两个有序链表
- 24. 两两交换链表中的节点
- 61. 旋转链表
- 86. 分隔链表
- 92. 反转链表 II
- 141. 环形链表
- 160. 相交链表
- 206. 反转链表
- 206 反转链表 扩展
- 234. 回文链表
- 237. 删除链表中的节点
- 445. 两数相加 II
- 面试题 02.02. 返回倒数第 k 个节点
- 面试题 02.08. 环路检测
- 剑指 Offer 06. 从尾到头打印链表
- 剑指 Offer 18. 删除链表的节点
- 剑指 Offer 22. 链表中倒数第k个节点
- 剑指 Offer 35. 复杂链表的复制
- 1. 两数之和
- 11. 盛最多水的容器
- 53. 最大子序和
- 75. 颜色分类
- 124.验证回文串
- 167. 两数之和 II - 输入有序数组 -169. 多数元素
- 189.旋转数组
- 209. 长度最小的子数组
- 283.移动0
- 303.区域和检索 - 数组不可变
- 338. 比特位计数
- 448. 找到所有数组中消失的数字
- 643.有序数组的平方
- 977. 有序数组的平方