先放上结论
- requestLayout会直接递归调用父窗口的requestLayout,直到ViewRootImpl,然后触发peformTraversals,由于mLayoutRequested为true,会导致onMeasure和onLayout被调用。不一定会触发OnDraw
- requestLayout触发onDraw可能是因为在在layout过程中发现l,t,r,b和以前不一样,那就会触发一次invalidate,所以触发了onDraw,也可能是因为别的原因导致mDirty非空(比如在跑动画)
- view的invalidate不会导致ViewRootImpl的invalidate被调用,而是递归调用父view的invalidateChildInParent,直到ViewRootImpl的invalidateChildInParent,然后触发peformTraversals,会导致当前view被重绘,由于mLayoutRequested为false,不会导致onMeasure和onLayout被调用,而OnDraw会被调用
结合requestLayout和invalidate与View三大流程关系,有如下图:
1、当视图的内容,可见性发生变化,会调用invalidate,调用后只会触发Draw 过程。 2、requestLayout 会触发Measure、Layout过程,如果尺寸发生改变,则会调用invalidate。 3、当涉及View的尺寸、位置变化时使用requestLayout。 4、当仅仅需要重绘时调用invalidate。 5、如果不确定requestLayout 是否触发invalidate,可在requestLayout后继续调用invalidate。
源码解析:Android requestLayout与invalidate的区别 - 简书
1. invalidate不调用performMeasure和performLayout先看下 requestLayout 方法
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
//这里设置为true
mLayoutRequested = true;
scheduleTraversals();
}
}
再来看 performTraversal方法的大致逻辑,对应的逻辑我都写到注释中了,挺清晰的。
private void performTraversals() {
......
// 调用invalidate的话,mLayoutRequested为false,所以layoutRequested为false
// 调用requestLayout,会把mLayoutRequested设置为true
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
......
if (layoutRequested) {
// Clear this now, so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
// layout pass.
//每次使用performTraversals都会把mLayoutRequested重置为false
mLayoutRequested = false;
}
// 如果layoutRequested为false,那windowShouldResize一定是false了,不可能可以调用到performMeasure了
boolean windowShouldResize = layoutRequested && windowSizeMayChange && ......;
if (mFirst || windowShouldResize || ......) {
......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
}
.......
// invalidate没有把mLayoutRequested设置为true,因此didLayout将为false,因此也无法调用performLayout
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
performLayout(lp, mWidth, mHeight);
......
}
......
performDraw()
}
2. performDraw方法与requestLayout,view.invalidate的关系
private boolean draw(boolean fullRedrawNeeded) {
······
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
······
// 执行真正的绘制流程
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
······
}
······
}
可以看到,如果!dirty.isEmpty()为true,才会去绘制。
而view#invalidate时,会直接把自身的 left,right,top,bottom传递过去,并一路调用到 ViewRootImpl#invalidateRectOnScreen(Rect dirty),该方法中会调用scheduleTraversals()
。从而保证可以调用performDraw()
。而requestLayout是没有这一步的,因此往往不会执行绘制方法。
上面仅仅说明了单个布局Invalidate/RequestLayout联系,那么如果父布局调用了invalidate,那么子布局会走重绘过程吗?接下来列举这些关系。
这两个方法的使用场景:ImageView的使用:
可以非常明显看到在ImageView中刷新图片时,如果宽高变了,则需要调用requestLayout。否则直接使用invalidate。
TextView的使用:
textView中大多数的都是两个方法需要同时调用:
子布局Invalidate 如果是软件绘制或者父布局开启了软件缓存绘制,父布局会走重绘过程(前提是WILL_NOT_DRAW标记没设置)。
子布局RequestLayout 父布局会重走Measure、Layout过程。
父布局Invalidate 如果是软件绘制,则子布局会走重绘过程。
父布局RequestLayout 如果父布局尺寸发生了改变,则会触发子布局Measure过程、Layout过程。
比较一下requestLayout和invalidate方法比较一下requestLayout和invalidate方法 - 掘金
调用 View.requestLayout 方法后会依次调用 performMeasure, performLayout 和 performDraw 方法,调用者 View 及其父 View 会从上往下重新进行 measure, layout 流程,一般情况下不会执行 draw 流程(子 View 会通过判断其尺寸/顶点是否发生改变而决定是否重新 measure/layout/draw 流程)。
子线程真不能绘制UI吗在Activity onCreate里创建子线程并展示对话框:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_group);
new Thread(new Runnable() {
@Override
public void run() {
TextView textView = new TextView(MainActivity.this);
textView.setText("hello thread");
Looper.prepare();
Dialog dialog = new Dialog(MainActivity.this);
dialog.setContentView(textView);
dialog.show();
Looper.loop();
}
}).start();
}
答案是可以的,接下来分析为什么可以。
在分析ViewRootImpl里requestLayout/invalidate过程中,发现其内部调用了checkThread()方法:
#ViewRootImpl.java
void checkThread() {
//当前调用线程与mThread不是同一线程则会抛出异常
if (mThread != Thread.currentThread()) {
//简单翻译来说:只有创建了ViewTree的线程才能操作里边的View
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
问题的关键是mThread是什么?从哪里来?
#ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
...
//mThread 为Thread类型
//也就是说哪个线程执行了构造ViewRootImpl对象,那么mThread就是指向那个线程
mThread = Thread.currentThread();
...
}
而创建ViewRootImpl对象是在调用WindowManager.addView(xx)过程中创建的。 关于WindowManager/Window 请移步:Window/WindowManager 不可不知之事
现在回过头来看Dialog创建就比较明朗了:
1、dialog.show() 调用WindowManager.addView(xx),此时是子线程调用,因此ViewRootImpl对象是在子线程调用的,进而mThread指向子线程。 2、当ViewRootImpl对象构建成功后,调用其setView(xx)方法,里面调用了requestLayout,此时还是子线程。 3、checkThread()判断是同一线程,因此不会抛出异常。
实际上,"子线程不能更新ui" 更合理的表述应为:View只能被构建了ViewTree的线程操作。只是通常来说,Activity 构建ViewTree的线程被称作UI(主)线程,因此才会有上述说法。
postInvalidate 流程postInvalidate 通过ViewRootImpl 里的handler切换到UI线程,最终执行 invalidate()。 ViewRootImpl 里的hanlder绑定的线程即是UI线程。
源码分析_Android UI何时刷新_Choreographer我们调用View的requestLayout或者invalidate时,最终都会触发ViewRootImp执行scheduleTraversals()方法。这个方法中ViewRootImp会通过Choreographer来注册个接收Vsync的监听,当接收到系统体层发送来的Vsync后我们就执行doTraversal()来重新绘制界面。通过上面的分析我们调用invalidate等刷新操作时,系统并不会立即刷新界面,而是等到Vsync消息后才会刷新页面。
源码分析_Android UI何时刷新_Choreographer - 简书