视图的绘制工作大概分为Measure、Layout和Draw三个主要的流程。而启动这些流程的一个入口则是ViewRootImpl类的performTraversals,具体请参考
SurfaceFlinger学习笔记(一)应用启动流程
SurfaceFlinger学习笔记(二)之Surface
SurfaceFlinger学习笔记(三)之SurfaceFlinger进程
SurfaceFlinger学习笔记(四)之HWC2
SurfaceFlinger学习笔记(五)之HWUI
SurfaceFlinger学习笔记(六)之View Layout Draw过程分析
DecorView在PhoneWindow中被创建,在Activity的onResume方法回调的时候继而调用Activity的makeVisible()方法,Activity对应的PhoneWindow对象才被WindowManager添加
WindowManager.addView()->WindowManagerImpl.addView()->WindowManagerGlobal.addView()->创建ViewRootImpl对象->ViewRootImpl.setView(DecorView对象)->ViewRootImpl.requestLayout()->ViewRootImpl.performTraversals()->然后触发performMeasure()、performLayout()、performDraw()
private void performTraversals() {
...
// host是DecorView
// 每个ViewRootImpl包含一个AttachInfo
// 为所有的childView关联AttachInfo,即同一个View Hierarchy内共用一个AttachInfo
// AttachInfo 中包含ViewTreeObserver
host.dispatchAttachedToWindow(mAttachInfo, 0);
// 回调 OnWindowAttachedListener
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
...
// 开始测量
performMeasure();
...
// 开始布局
performLayout();
...
if (triggerGlobalLayoutListener) {
// 布局阶段完成之后回调 OnGlobalLayoutListener,
// 在该方法中可以正确获取 View 的实际宽高
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
...
// 调用 AttachInfo 内 ViewTreeObserver 的 dispatchOnPreDraw方法
//OnPreDrawListener 表示当视图即将绘制时回调
// 如果 dispatchOnPreDraw() 方法返回 true,此时将拦截 performDraw() 的绘制任务,重新发起绘制流程 scheduleTraversals()
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
...
// 开始绘制
performDraw();
...
}
ViewTreeObserver 内部使用 CopyOnWriteArrayList 保存相关 Listener,这主要考虑可能出现的多线程场景,之所以叫做 CopyOnWrite 就是当添加元素的时候,不直接向当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。如果频发的申请与释放其实对内存还是有一定影响的
DecorView的MeasureSpec是由窗口的尺寸(PhoneWindow)和其自身的LayoutParams共同决定的,(对于普通View,MeasureSpec是父容器的MeasureSpec和其自身的LayoutParams共同决定),MeasureSpec一旦确定,onMeasure中就可以确定View的测量宽高
private void performTraversals() {
......
//最外层的根视图的widthMeasureSpec和heightMeasureSpec由来
//lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(lp, mWidth, mHeight);
......
performDraw();
......
}
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.
//Window无法调整大小,强制根视图的宽高为Window的宽高,即全屏
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
...
}
return measureSpec;
}
* frameworks/base/core/java/android/view/View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 判断当前view的LayoutMode是否为opticalbounds
boolean optical = isLayoutModeOptical(this);
// 判断当前view的父容器的LayoutMode是否为opticalbounds
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// 根据我们传入的widthMeasureSpec和heightMeasureSpec计算key值,用key值作为键在mMeasureCache中存储我们view的信息
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
// 如果mPrivateFlags包含PFLAG_FORCE_LAYOUT那么表示当前View需要强制进行layout(比如执行了View的forceLayout方法),所以这种情况下要尝试进行量算
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// mOldWidthMeasureSpec和mOldHeightMeasureSpec分别表示上次对View进行量算时的widthMeasureSpec和heightMeasureSpec
// 执行View的measure方法时,View总是先检查一下是不是真的有必要费很大力气去做真正的量算工作
// mPrivateFlags是一个Int类型的值,其记录了View的各种状态位
// 如果新传入的widthMeasureSpec/heightMeasureSpec与上次量算时的mOldWidthMeasureSpec/mOldHeightMeasureSpec不等,
// 那么也就是说该View的父ViewGroup对该View的尺寸的限制情况有变化,这种情况下要尝试进行量算
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// 移除measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
//对阿拉伯语、希伯来语等从右到左书写、布局的语言进行特殊处理
resolveRtlPropertiesIfNeeded();
//在View真正进行量算之前,View还想进一步确认能不能从已有的缓存mMeasureCache中读取缓存过的量算结果
//如果是强制layout导致的量算,那么将cacheIndex设置为-1,即不从缓存中读取量算结果
//如果不是强制layout导致的量算,那么我们就用上面根据measureSpec计算出来的key值作为缓存索引cacheIndex。
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
//sIgnoreMeasureCache是一个boolean类型的成员变量,其值是在View的构造函数中计算的,而且只计算一次
//一些老的App希望在一次layou过程中,onMeasure方法总是被调用,
//具体来说其值是通过如下计算的: sIgnoreMeasureCache = targetSdkVersion < KITKAT;
//也就是说如果targetSdkVersion的API版本低于KITKAT,即API level小于19,那么sIgnoreMeasureCache为true
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
//onMeasure执行完后,通过位操作,重置View的状态mPrivateFlags,将其标记为在layout之前不必再进行量算的状态
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
//通过setMeasuredDimensionRaw()方法设置我们的测量值,然后重置View的状态
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// 如果View的状态没有改变,则会抛出异常"我们没有调用”setMeasuredDimension()"方法,
//一般出现在我们重写onMeasure方法,但是没有调用setMeasuredDimension方法导致的。
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
//最后用上面计算出的key作为键,量算结果作为值,将该键值对放入成员变量mMeasureCache中,
//这样就实现了对本次量算结果的缓存,以便在下次measure方法执行的时候,有可能将其从中直接读出,
//从而省去实际量算的步骤
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
首先判断当前View的layoutMode模式,通过调用isLayoutModeOptical方法进行判断。这个方法就是判断view是否为ViewGroup类型,然后判断layoutMode设定是否为opticalBounds。如果和父节点的不一致,则对传入的widthMeasureSpec、heightMeasureSpec进行重新计算封装。
ViewGroup的android:layoutMode属性
android:layoutMode属性我们很少用的到,设置layoutMode属性有两个常量值,默认是clipBounds,clipBounds 表示了getLeft()、getRight()等返回的原始值组成的边界;opticalBounds描述了视觉上的边界,视觉边界位于Clip bounds中,Clip bounds会更大,因为用来显示一些其他效果比如阴影和闪光等。比如Button,它的clipBounds比opticalBounds大一圈
判断当前view是否强制重新计算,或者传入进来的MeasureSpec是否和上次不同。这两种情况满足一种则进行测量运算。
如果为强制测量或者忽略缓存,则调用onMeasure()方法进行测量,反之,从mMeasureCache缓存中读取上次的测量数据。
将最新的widthMeasureSpec和heightMeasureSpec进行存储到mMeasureCache。
当View类的成员函数measure决定要重新测量当前视图的宽度和高度之后,它就会首先将成员变量mPrivateFlags的MEASURED_DIMENSION_SET位设置为0,接着再调用另外一个成员函数onMeasure来真正执行测量宽度和高度的操作。View类的成员函数onMeasure执行完成之后,需要再调用另外一个成员函数setMeasuredDimensionRaw来将测量好的宽度和高度设置到View类的成员变量mMeasuredWidth和mMeasuredHeight中,并且将成员变量mPrivateFlags的PFLAG_MEASURED_DIMENSION_SET位设置为1。这个操作是强制的,因为当前视图最终就是通过View类的成员变量mMeasuredWidth和mMeasuredHeight来获得它的宽度和高度的。为了保证这个操作是强制的,View类的成员函数measure再接下来就会检查成员变量mPrivateFlags的PFLAG_MEASURED_DIMENSION_SET位是否被设置为1了。如果不是的话,那么就会抛出一个类型为IllegalStateException的异常来
MeasureSpec的概念
ViewGroup在调用View的measure方法时,会传入ViewGroup对View的宽度及高度的限制条件MeasureSpec变量,它通过一个Int类型的值来表示的,该Int值会同时包含两种信息:mode和size,即模式和尺寸。Android会用第一个高位字节存储mode,然后用剩余的三个字节存储size
View有一个静态内部类MeasureSpec,该类有几个静态方法以及静态常量,我们可以用这些方法将mode和size打包成一个Int值或者是从一个Int值中解析出mode和size。
- 通过调用MeasureSpec.getSize(int measureSpec)即可从measureSpec解析出三个字节所包含的尺寸size信息,该方法返回Int类型,也就是说我们得到的size实际上就是对原有的measureSpec的高位字节的8个二进制位都设置为0,该方法的返回值size虽然也是4个字节的Int值,但是已经完全不包含mode信息。
- 通过调用MeasureSpec.getMode(int measureSpec)即可从measureSpec解析出高位字节所包含的模式mode信息,该方法返回Int类型,也就是说我们得到的mode实际上对原有的measureSpec的低位的三个字节的24个二进制码都设置为0,该方法的返回值mode虽然也是4个字节的Int值,但是已经完全不包含size信息
SpecMode有三种模式:
- UNSPECIFIED(未指定),父容器不对子元素施加任何束缚,子元素可以得到任意想要的大小,一般用于系统内部,标识一种测量的状态。
- EXACTLY(精准),表示View必须使用其父ViewGroup指定的尺寸,父容器已经检测出子元素所需要的确切大小,这个时候子元素的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。
- AT_MOST(至多),父容器指定了一个可用大小,即SpecSize,该值表示View最大可以取其父ViewGroup给其指定的尺寸,具体是什么要看不同子元素的具体实现,它对应于LayoutParams中的wrap_content。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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:
//此处当mode是UNSPECIFIED时,View就直接用自己想要的尺寸size作为量算的结果
result = size;
break;
//如果mode是AT_MOST,那么表示View最大可以取其父ViewGroup给其指定的尺寸
//如果mode是EXACTLY,那么表示View必须使用其父ViewGroup指定的尺寸
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
//将量算完成的宽度measuredWidth保存到View的成员变量mMeasuredWidth中
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
//最后将View的状态位mPrivateFlags设置为已量算状态
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
getDefaultSize只需要看AT_MOST和EXACTLY两种情况,返回的就是measureSpec的specSize ,而这传入的measureSpec是在父容器中由父容器的measureSpec和当前子元素的LayoutParams已经共同确定的,所以由measureSpec拆分出来的specSize 就是子元素测量后的大小。
当执行getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)时,我们就得到了最终量算到的宽度值
getSuggestedMinimumWidth()的主要工作:简单理解即返回View的最小宽度和高度。如果View没有设置背景就返回android:mMinWidth 这个属性的值,可以人为设置该值,默认为0;如果View设置了背景,则返回mMinWidth和背景最小宽度两者中的最大值。getSuggestedMinimumWidth()的返回值就是View在UNSPECIFIED情况下的测量宽
mMeasuredWidth
mMeasuredWidth是一个Int类型的值,其是由4个字节组成的
我们先假设mMeasuredWidth只存储了量算完成的宽度信息,而且View的父ViewGroup可以通过相关方法得到该值。但是存在这样一种情况:View在量算时,父ViewGroup给其传递的widthMeasureSpec中的specMode的值是AT_MOST,specSize是100,但是View的最小宽度是200,显然父ViewGroup指定的specSize不能满足View的大小,但是由于specMode的值是AT_MOST,View在getDefaultSize方法中不得不妥协,只能含泪将量算的最终宽度设置为100。然后其父ViewGroup通过某些方法获取到该View的量算宽度为100时,ViewGroup以为子View只需要100就够了,最终给了子View宽度为100的空间,这就导致了在UI界面上View特别窄,用户体验也就不好。
Android为让其View的父控件获取更多的信息,就在mMeasuredWidth上下了很大功夫,虽然是一个Int值,但是想让它存储更多信息,具体来说就是把mMeasuredWidth分成两部分:
- 其高位的第一个字节为第一部分,用于标记量算完的尺寸是不是达到了View想要的宽度,我们称该信息为量算的state信息。
- 其低位的三个字节为第二部分,用于存储实际的量算到的宽度
一个变量能包含两个信息,这个有点类似于measureSpec的道理,但是二者又有不同
- measureSpec是将限制条件mode从ViewGroup传递给其子View。
- mMeasuredWidth、mMeasuredHeight是将带有量算结果的state标志位信息从View传递给其父ViewGroup。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
...
}
FrameLayout类是从ViewGroup类继承下来的,后者用来描述一个视图容器,它有一个类型为View的数组mChildren,里面保存的就是它的各个子视图。ViewGroup类所供了两个成员函数getChildCount和getChildAt,它们分别用来获得一个视图容器所包含的子视图的个数,以及获得每一个子视图。
FrameLayout.onMeasure首先是调用measureChildWithMargins来测量每一个子视图的宽度和高度,并且找到这些子视图的最大宽度和高度值,保存在变量maxWidth和maxHeight 中,然后调用child.getMeasuredState获取state,并进行合并,在resolveSizeAndState中设置。
FrameLayout类的成员函数onMeasure接着再将前面得到的宽度maxWidth和高度maxHeight分别加上当前视图所设置的Padding值,其中,(mPaddingLeft,mPaddingRight,mPaddingTop,mPaddingBottom )表示当前视图的内容区域的左右上下四条边分别到当前视图的左右上下四条边的距离,它们是父类View的四个成员变量,(mForegroundPaddingLeft,mForegroundPaddingRight,mForegroundPaddingTop,mForegroundPaddingBottom)表示当前视图的各个子视图所围成的区域的左右上下四条边到当前视视的前景区域的左右上下四条边的距离。从这里就可以看出,当前视图的内容区域的大小就等于前景区域的大小,而前景区域的大小大于等于各个子视图的所围成的区域,这是因为前景区域本来就是用来覆盖各个子视图所围成的区域的
measureChildWithMargins
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
参数child用来描述当前要测量大小的子视图,参数parentWidthMeasureSpec和parentHeightMeasureSpec用来描述当前子视图可以获得的最大宽度和高度,参数widthUsed和heightUsed用来描述父窗口已经使用了的宽度和高度。ViewGroup类的成员函数measureChildWithMargins必须要综合考虑上述参数,以及当前正在测量的子视图child所设置的大小和Margin值,还有当前视图容器所设置的Padding值,来得到当前正在测量的子视图child的正确宽度childWidthMeasureSpec和高度childHeightMeasureSpec,这是通过调用ViewGroup类的另外一个成员函数getChildMeasureSpec来实现的。
得到了当前正在测量的子视图child的正确宽度childWidthMeasureSpec和高度childHeightMeasureSpec之后,就可以调用它的成员函数measure来设置它的大小了,即执行前面View.measure的操作。注意,如果当前正在测量的子视图child描述的也是一个视图容器,那么它又会重复执行VIewGroup.onMeasure的操作,直到它的所有子孙视图的大小都测量完成为止。
resolveSizeAndState
resolveSizeAndState方法与getDefaultSize方法类似,其内部实现的逻辑是一样的,但是又有区别,getDefaultSize仅仅返回最终量算的尺寸信息,但resolveSizeAndState除了返回最终尺寸信息还会有可能返回量算的state标志位信息
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
//当specMode为AT_MOST,并且父控件指定的尺寸specSize小于View自己想要的尺寸时,
//我们就会用掩码MEASURED_STATE_TOO_SMALL向量算结果加入尺寸太小的标记
//这样其父ViewGroup就可以通过该标记其给子View的尺寸太小了,
//然后可能分配更大一点的尺寸给子View
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
getMeasuredWidth和getMeasuredHeight方法:该组方法只返回量算结果中的的尺寸信息,去掉了高位字节的state信息
getMeasuredWidthAndState和getMeasuredHeightAndState:该组方法返回的量算结果中同时包含尺寸和state信息
getMeasuredState:该方法返回的Int值中同时包含宽度量算的state以及高度量算的state,不包含任何的尺寸信息
public final int getMeasuredWidth() {
//MEASURED_SIZE_MASK的值为0x00ffffff,用mMeasuredWidth与掩码MEASURED_SIZE_MASK进行按位与运算,
//可以将返回值中的高位字节的8个bit位全置为0,从而去掉了高位字节的state信息
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredState() {
//将宽度量算的state存储在Int值的第一个字节中,即高位首字节
//将高度量算的state存储在Int值的第三个字节中
return (mMeasuredWidth&MEASURED_STATE_MASK)
| ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
掩码MEASURED_STATE_MASK的值为常量0xff000000,其高位字节的8个bit位全为1,剩余低位字节的三个字节的24个bit位全为0
MEASURED_HEIGHT_STATE_SHIFT的值为常量16
- 当执行(mMeasuredWidth&MEASURED_STATE_MASK)时,将mMeasuredWidth与MEASURED_STATE_MASK进行按位与操作,该表达式的值高位字节保留了量算后宽度的state,过滤掉了其低位三个字节所存储的宽度size
- 由于我们已经用高位首字节存储了量算后宽度的state,所以高度的state就不能存储在高位首字节了。Android打算把它存储在第三个字节中。(mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)表示将mMeasuredHeight向右移16位,这样高度的state字节就从原来的第一个字节右移动到了第三个字节,由于高度的state向右移动了,所以其对应的掩码也有相应移动。(MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT)表示state的掩码也从第一个字节右移16位到了第三个字节,即掩码从0xff000000变成了0x0000ff00。然后用右移后的state与右移后的掩码执行按位与操作,这样就在第三个字节保留了高度的state信息,并且过滤掉了第1、2、4字节中的信息,即将这三个字节中的24个bit位置为0。
- 最后,将我们得到的宽度的state与高度的state进行按位或操作,这样就将宽度和高度的state都保存在一个Int值中:第一个字节存储宽度的state,第三个字节存储高度的state
public void layout(int l, int t, int r, int b) {
//假如包含PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT标示,则执行onMeasure,这个标示在Measure中
//假如从mMeasureCache中获取到cacheIndex,则设置标示
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//如果isLayoutModeOptical()返回true,那么就会执行setOpticalFrame()方法,
//否则会执行setFrame()方法。并且setOpticalFrame()内部会调用setFrame(),
//所以无论如何都会执行setFrame()方法。
//setFrame()方法会将View新的left、top、right、bottom存储到View的成员变量中
//并且返回一个boolean值,如果返回true表示View的位置或尺寸发生了变化,
//否则表示未发生变化
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//PFLAG_LAYOUT_REQUIRED标志在执行完Measure后,会设置
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//继承自ViewGroup的类都需要实现onLayout方法,从而在onLayout方法中依次循环子View,
//并调用子View的layout方法
onLayout(changed, l, t, r, b);
...
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
//我们可以通过View的addOnLayoutChangeListener(View.OnLayoutChangeListener listener)方法
//向View中添加多个Layout发生变化的事件监听器
//这些事件监听器都存储在mListenerInfo.mOnLayoutChangeListeners这个ArrayList中
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
final boolean wasLayoutValid = isLayoutValid();
//从mPrivateFlags中移除强制Layout的标签PFLAG_FORCE_LAYOUT
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
//向mPrivateFlags3中加入Layout完成的标签PFLAG3_IS_LAID_OUT
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
...
}
setFrame
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
//将新旧left、right、top、bottom进行对比,只要不完全相对就说明View的布局发生了变化
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
//先保存一下mPrivateFlags中的PFLAG_DRAWN标签信息
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
//比较View的新旧尺寸是否相同,如果尺寸发生了变化,那么sizeChanged的值为true
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
//mRenderNode.setLeftTopRightBottom()方法会调用RenderNode中原生方法的nSetLeftTopRightBottom()方法,
//该方法会根据left、top、right、bottom更新用于渲染的显示列表
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
//向mPrivateFlags中增加标签PFLAG_HAS_BOUNDS,表示当前View具有了明确的边界范围
mPrivateFlags |= PFLAG_HAS_BOUNDS;
if (sizeChanged) {
//如果View的尺寸和之前相比发生了变化,那么就执行sizeChange()方法,
//该方法中又会调用onSizeChanged()方法,并将View的新旧尺寸传递进去
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
//有可能在调用setFrame方法之前,invalidate方法就被调用了,
//这会导致mPrivateFlags移除了PFLAG_DRAWN标签。
//如果当前View处于可见状态就将mPrivateFlags强制添加PFLAG_DRAWN状态位,
//这样会确保下面的invalidate()方法会执行到其父控件级别
mPrivateFlags |= PFLAG_DRAWN;
invalidate(sizeChanged);
//invalidateParentCaches()方法会移除其父控件的PFLAG_INVALIDATED标签,
//这样其父控件就会重建用于渲染的显示列表
invalidateParentCaches();
}
// 重新恢复mPrivateFlags中原有的PFLAG_DRAWN标签信息
mPrivateFlags |= drawn;
//设置为true,以便可以表示当前视图的背景大小发生了变化
mBackgroundSizeChanged = true;
mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
会将新旧left、right、top、bottom进行对比,只要不完全相同就说明View的布局发生了变化,则将changed变量设置为true。然后比较View的新旧尺寸是否相同,如果尺寸发生了变化,并将其保存到变量sizeChanged中。如果尺寸发生了变化,那么sizeChanged的值为true
将成员变量mPrivateFlags的DRAWN位记录在变量drawn中,并且调用另外一个成员函数invalidate来检查当前视图上次请求的UI绘制操作是否已经执行。如果已经执行了的话,那么就会再请求执行一个UI绘制操作,以便可以在修改当前视图的大小和位置之前,将当前视图在当前位置按照当前大小显示一次
然后将新的left、top、right、bottom存储到View的成员变量中保存下来。并执行mRenderNode.setLeftTopRightBottom()方法会,其会调用RenderNode中原生方法的nSetLeftTopRightBottom()方法,该方法会根据left、top、right、bottom更新用于渲染的显示列表
如果View的尺寸和之前相比发生了变化,那么就执行sizeChange()方法,该方法中又会调用onSizeChanged()方法,并将View的新旧尺寸传递进去
判断当前视图是否是可见的,即成员变量mViewFlags的VISIBILITY_MASK位的值是否等于VISIBLE。如果是可见的话,那么就需要将成员变量mPrivateFlags的DRAWN位设置为1,以便接下来可以调用另外一个成员函数invalidate来成功地执行一次UI绘制操作,目的是为了将当前视图马上显示出来
View类的成员变量mPrivateFlags的DRAWN位描述的是当前视图上一次请求的UI绘制操作是否已经执行过了。如果它的值等于1,就表示已经执行过了,否则的话,就表示还没在等待执行。前面第一次调用View类的成员函数invalidate来检查当前视图上次请求的UI绘制操作是否已经执行时,如果发现已经执行了,那么就会重新请求执行一次新的UI绘制操作,这时候会导致当前视图的成员变量mPrivateFlags的DRAWN位重置为0。注意,新请求执行的UI绘制只是为了在修改当前视图的大小以及大小之前,先将它在上一次设置的大小以及位置中绘制出来,这样就可以使得当前视图的大小以及位置出现平滑的变换。换句话说,新请求执行的UI绘制只是为了获得一个中间效果,它不应该影响当前视图的绘制状态,即不可以修改当前视图的成员变量mPrivateFlags的DRAWN位。因此,我们就需要在前面第一次调用View类的成员函数invalidate前,先将当前视图的成员变量mPrivateFlags的DRAWN位保存下来,即保存在变量drawn中,然后等到调用之后,再将变量drawn的值恢复到当前视图的成员变量mPrivateFlags的DRAWN位中去
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
...
if (skipInvalidate()) {
return;
}
// Reset content capture caches
mCachedContentCaptureSession = null;
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;
}
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
// Damage the entire projection receiver, if necessary.
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
}
}
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
...
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
invalidateRectOnScreen(dirty);
return null;
}
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
// Add the new dirty rect to the current one
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
FrameLayout.onLayout
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
private void performDraw() {
。。。
final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;
mFullRedrawNeeded = false;
...
boolean canUseAsync = draw(fullRedrawNeeded);
}
fullRedrawNeeded:它的作用是判断是否需要重新绘制全部视图,如果是第一次绘制视图,mFullRedrawNeeded为真,则绘制所以的视图,如果由于某些原因,导致了视图重绘,那么就没有必要绘制所有视图
mReportNextDraw则在relayoutWindow返回包含WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME标志时为真
private boolean draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
...
//将界面滑动至所需位置
scrollToRectOrFocus(null, false);
//界面如果进行滑动,分发滑动监听事件
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
//根据滑动是否完成做出相应的处理
boolean animating = mScroller != null && mScroller.computeScrollOffset();
...
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
//RootView滑动监听回调
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
}
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
//获取需要重新绘制的区域
final Rect dirty = mDirty;
...
//当需要完全重新绘制时,将dirty的大小设置为整个屏幕大小
if (fullRedrawNeeded) {
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
...
//通知已注册的监听器,绘制过程即将开始
mAttachInfo.mTreeObserver.dispatchOnDraw();
...
//无障碍模式相关处理
boolean accessibilityFocusDirty = false;
...
mAttachInfo.mDrawingTime = mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
boolean useAsyncReport = false;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
//根据是否开启了硬件加速,是否开启硬件加速,View 的绘制流程都是一样的,区别就是 Canvas 不同
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
...
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
//未开启硬件加速,则执行该方法,直接调用 view 的 draw 方法
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
return useAsyncReport;
}
大概从 Android 4.+开始,默认情况下都是支持跟开启了硬件加速的,也存在手机支持硬件加速,但是部分API不支持硬件加速的情况,如果使用了这些API,就需要主关闭硬件加速,或者在 View 层,或者在Activity 层,比如 Canvas 的 clipPath等。
启用硬件加速的条件,必须支持硬件并且开启了硬件加速才可以,满足,就利用 ThreadedRenderer.draw,否则 drawSoftware(软件绘制)
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
...
canvas = mSurface.lockCanvas(dirty);
...
mView.draw(canvas);//DecorView的draw,调用View.Draw后调用mMenuBackground.draw,
...
surface.unlockCanvasAndPost(canvas);
...
return true;
}
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 //绘制 View 的内容
* 4. Draw children //绘制子 View,子 View 的绘制也是按照这个流程进行
* //如果需要,绘制边框
* 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);
// 通常情况下,会跳过第 2 步和第 5 步
//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);
...
return;
}
...
}
绘制子View的内容通过dispatchDraw来完成的,View.dispatchDraw方法仍然是个空实现,不过ViewGroup对此方法进行了重写,ViewGroup.dispatchDraw主要调用drawChild,让每个子视图绘制自身,进而调用到View中的draw(canvas, this, drawingTime)方法,意这个方法和最开始的那个draw(canvas)方法不同
View.draw(canvas, this, drawingTime)首先判断是否已经有缓存,即之前是否已经绘制过一次了,如果没有,则会调用draw(canvas)方法,开始正常的绘制,否则利用缓存来显示
- mSurfaceHolderCallback不空时,mSurfaceHolder不空,Activity将拥有Surface,利用SurfaceHolder实现绘制,类似SurfaceView。
- mSurfaceHolderCallback是空时,mSurfaceHolder也是空,启动硬件绘制。一个基本的视图都会使用硬件渲染
* frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
//view是DecorView视图,它实现RootViewSurfaceTaker
if (view instanceof RootViewSurfaceTaker) {
mSurfaceHolderCallback =((RootViewSurfaceTaker)view).willYouTakeTheSurface();
if (mSurfaceHolderCallback != null) {
mSurfaceHolder = new TakenSurfaceHolder();
mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
}
}
// If the application owns the surface, don't enable hardware acceleration
if (mSurfaceHolder == null) {
enableHardwareAcceleration(attrs);//开启硬件加速
}
...
}
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
//根据配置,获取硬件加速的开关
final boolean hardwareAccelerated = (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
...
mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
attrs.getTitle().toString());
}
Create:
JNI方法创建一个底层rootRenderNode节点,创建一个上层RenderNode节点,封装底层的rootRenderNode为mNativeRenderNode指针保存在java层。
JNI方法创建一个底层RenderProxy代理。RenderProxy的构造方法,将创建CanvasContext和RenderThread,CanvasContext封装底层RootRenderNode节点
底层的详细实现,请参考SurfaceFlinger学习笔记(五)之HWUI
* frameworks/base/core/java/android/view/ThreadedRenderer.java
public static ThreadedRenderer create(Context context, boolean translucent, String name) {
ThreadedRenderer renderer = null;
if (isAvailable()) {
renderer = new ThreadedRenderer(context, translucent, name);
}
return renderer;
}
* frameworks/base/graphics/java/android/graphics/HardwareRenderer.java
public HardwareRenderer() {
mRootNode = RenderNode.adopt(nCreateRootRenderNode());
mRootNode.setClipToBounds(false);
mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
if (mNativeProxy == 0) {
throw new OutOfMemoryError("Unable to create hardware renderer");
}
Cleaner.create(this, new DestroyContextRunnable(mNativeProxy));
ProcessInitializer.sInstance.init(mNativeProxy);
}
Draw:
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
choreographer.mFrameInfo.markDrawStart();
//入参view是顶层视图DecorView
//遍历更新视图树每一个节点,构建View的DrawOp树
updateRootDisplayList(view, callbacks);
...
//通知RenderThread线程绘制
int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
...
}
* frameworks/base/core/java/android/view/ThreadedRenderer.java
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
//从顶层视图开始,更新所有视图的DisplayList。
updateViewTreeDisplayList(view);
// Consume and set the frame callback after we dispatch draw to the view above, but before
// onPostDraw below which may reset the callback for the next frame. This ensures that
// updates to the frame callback during scroll handling will also apply in this frame.
final FrameDrawingCallback callback = mNextRtFrameCallback;
mNextRtFrameCallback = null;
if (callback != null) {
setFrameCallback(callback);
}
//根节点绘制顶层视图RenderNode。
if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
try {
final int saveCount = canvas.save();
canvas.translate(mInsetLeft, mInsetTop);
callbacks.onPreDraw(canvas);
//调用nInsertReorderBarrier插入栅栏,隔离canvas操作
canvas.enableZ();
//绘制顶层视图RenderNode。
canvas.drawRenderNode(view.updateDisplayListIfDirty());
canvas.disableZ();
callbacks.onPostDraw(canvas);
canvas.restoreToCount(saveCount);
mRootNodeNeedsUpdate = false;
} finally {
mRootNode.endRecording();
}
}
}
mRenderNode.beginRecording->RecordingCanvas.obtain构造RecordingCanvas,RecordingCanvas继承DisplayListCanvas ,DisplayListCanvas 继承BaseRecordingCanvas
判断视图的PFLAG_INVALIDATED标志,有这个标志,在调用每一个View的updateDisplayListIfDirty方法时,才会创建Canvas,当一个视图需要绘制时,上层肯定会设置该标志。最后会将重建标志还原
* frameworks/base/core/java/android/view/ThreadedRenderer.java
private void updateViewTreeDisplayList(View view) {
view.mPrivateFlags |= View.PFLAG_DRAWN;
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
view.updateDisplayListIfDirty();
view.mRecreateDisplayList = false;
}
每个视图的流程是一样的,都有三个步骤,第一次绘制时,每个视图都要建立Canvas
public RenderNode updateDisplayListIfDirty() {
...
//创建DisplayListCanvas
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
try {
if (layerType == LAYER_TYPE_SOFTWARE) {
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
} else {
//一般视图走硬件渲染都执行下面程序
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
// Fast path for layouts with no backgrounds
//没有背景视图则跳过,直接派发给子视图
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
} else {
draw(canvas);//绘制,包括绘制本身,修饰,以及派发,共六个步骤。
}
}
} finally {
//绘制结束,保存canvas记录内容
renderNode.endRecording();
setDisplayListProperties(renderNode);
}
return renderNode;
}
* frameworks/base/core/jni/android_view_ThreadedRenderer.cpp
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
"Mismatched size expectations, given %d expected %d",
frameInfoSize, UI_THREAD_FRAME_INFO_SIZE);
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
return proxy->syncAndDrawFrame();
}
*frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
int RenderProxy::syncAndDrawFrame() {
return mDrawFrameTask.drawFrame();
}
* frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp
int DrawFrameTask::drawFrame() {
LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
mSyncResult = SyncResult::OK;
mSyncQueued = systemTime(CLOCK_MONOTONIC);
postAndWait();
return mSyncResult;
}
void DrawFrameTask::postAndWait() {
AutoMutex _lock(mLock);
mRenderThread->queue().post([this]() { run(); });
mSignal.wait(mLock);
}
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
ATRACE_CALL();
int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
mRenderThread->timeLord().vsyncReceived(vsync);
bool canDraw = mContext->makeCurrent();
mContext->unpinImages();
for (size_t i = 0; i < mLayers.size(); i++) {
mLayers[i]->apply();
}
mLayers.clear();
mContext->setContentDrawBounds(mContentDrawBounds);
mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
// This is after the prepareTree so that any pending operations
// (RenderNode tree state, prefetched layers, etc...) will be flushed.
if (CC_UNLIKELY(!mContext->hasSurface() || !canDraw)) {
if (!mContext->hasSurface()) {
mSyncResult |= SyncResult::LostSurfaceRewardIfFound;
} else {
// If we have a surface but can't draw we must be stopped
mSyncResult |= SyncResult::ContextIsStopped;
}
info.out.canDrawThisFrame = false;
}
if (info.out.hasAnimations) {
if (info.out.requiresUiRedraw) {
mSyncResult |= SyncResult::UIRedrawRequired;
}
}
if (!info.out.canDrawThisFrame) {
mSyncResult |= SyncResult::FrameDropped;
}
// If prepareTextures is false, we ran out of texture cache space
return info.prepareTextures;
}