对于Android View的自定义来说,这三兄弟简直是我们必须要拿下的坎,那么这三个方法有什么本质上的区别呢?
我个人认为要从本质上来认识这三者的区别,那么你肯定要对Android View的绘制流程了如指掌。首先我们先来看看View 的绘制流程:
从上面View的绘制流程图上我们对invalidate 、requestLayout会触发哪些流程一目了然。
接下来我们用源码的角度上来进行更深入的剖析!
requestLayout
当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view重新调用他的onMeasure onLayout来对重新设置自己位置。特别的当view的layoutparameter发生改变,并且它的值还没能应用到view上,这时候适合调用这个方法。
具体的源码:frameworks/base/core/java/android/view/ViewRootImpl.java
我们先对比invalidate 跟 requestLayout的本质区别
1、requestLayout
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
if (DEBUG_LAYOUT) {
Log.d(TAG, "requestLayout: mView = " + mView + ", this = " + this);
}
if (DEBUG_REQUESTLAYOUT) {
Log.d(TAG, "requestLayout: mView = " + mView + ", this = " + this,
new Throwable("requestLayout"));
}
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
2、invalidate
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
分析:
从上面的源码上可以看出这两个方法不约而同的调用了scheduleTraversals(),区别就是requsetLayout检查当前是否在主线程中,并且置位mLayoutRequsted = true,而invalidate方法没有检查。
3、检查线程checkThread
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
4、进一步追踪下去看看scheduleTraversals()做什么事
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
if (!scheduleByHandler) {
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
这里会post一个runnable请求就是mTraversalRunnable
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
5、进入doTraversal()方法
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
注意这个方法中,我们重点关注performTraversals()方法:
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) {
Log.v(TAG, "Ooops, something changed! mWidth=" + mWidth
+ " measuredWidth=" + host.getMeasuredWidth() + " mHeight="
+ mHeight + " measuredHeight=" + host.getMeasuredHeight()
+ " contentInsetsChanged=" + contentInsetsChanged
+ " this = " + this);
}
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
if (DEBUG_LAYOUT) {
Log.v(TAG, "And hey let's measure once more: width=" + width
+ " height=" + height + " this = " + this);
}
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
只要没有stop 并且
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged)
这个条件成立,就是触发
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
经过Debug模式调试得出以下结论:
- requsetLayout触发performTraversal方法,进而调用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec),进一步的调用performLayout方法,此时layoutRequested = true
- invalidate触发performTraversal方法,因为上面的条件不成立,所以不会调用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec),同样也不会调用performLayout方法
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (DEBUG_LAYOUT || DEBUG_MEASURE_LAYOUT) {
Log.v(TAG, "ViewRoot layout+ : " + host + ", layoutRequested = " + layoutRequested
+ ", mStopped = " + mStopped + ", this = " + this);
}
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
所以到这里可以确认的是:
requsetLayout方法会调用performMeasure和performLayout方法
而invalidate方法则一个都不会调用。
我们接着往下看是否会接着调用performDraw方法
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
//FLYME:HuangTao@Shell.Glass Blur.Feature {@
performBlurGlass();
//@}
}
由上面的代码可知:需满足以下几个条件才会触发performDraw方法
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
}
一般情况下 以上条件都会满足,除非设置了明显的标志位,所以performDraw方法会执行。
那么接下来我们再来研究上面的几个标志位:
cancelDraw 默认是false
skipDraw 默认是false
newSurface 位于:
if (!hadSurface) {
if (mSurface.isValid()) {
if (DEBUG_DRAW) {
Log.v(TAG, "Create new surface: " + mSurface
+ ",surfaceGenerationId = " + surfaceGenerationId
+ ", mSurface.getGenerationId() = " + mSurface.getGenerationId()
+ ",mPreviousTransparentRegion = " + mPreviousTransparentRegion
+ ",this = " + this);
}
// If we are creating a new surface, then we need to
// completely redraw it. Also, when we get to the
// point of drawing it we will hold off and schedule
// a new traversal instead. This is so we can tell the
// window manager about all of the windows being displayed
// before actually drawing them, so it can display then
// all at once.
/// M: [APP launch time enhancenment feature] Ignore newSurface flag, if
/// this window is a kind of starting type. @{
if (!DBG_APP_LAUNCH_ENHANCE || lp.type !=
WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
newSurface = true;
}
/// @}
mFullRedrawNeeded = true;
mPreviousTransparentRegion.setEmpty();
// Only initialize up-front if transparent regions are not
// requested, otherwise defer to see if the entire window
// will be transparent
if (mAttachInfo.mHardwareRenderer != null) {
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "HW init");
hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
mSurface);
if (hwInitialized && (host.mPrivateFlags
& View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
// Don't pre-allocate if transparent regions
// are requested as they may not be needed
mSurface.allocateBuffers();
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
}
}
首先最开始的判断分支不会成立,因为 hadSurface为true,所以newSurface = false
最后一个标志位mReportNextDraw:
// Remember if we must report the next draw.
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mReportNextDraw = true;
}
这里在执行判断该标志位之前就置位为true。所以一般会进入performDraw方法
总结:
requsetLayout方法会触发performMeasure和performLayout,而performDraw方法是否触发视情况而定
如果以下条件满足
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw)
}
则触发performDraw方法,反之,不会触发performDraw方法。
而invalidate方法则只会触发performDraw方法,因为一般情况下条件都会满足:
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw)
}
至此我们从源码上已经分析完Invalidate 跟 requestLayout,那么我接下来大概说一下postInvalidate
6、postInvalidate
postInvalidate跟invalidate是一样的,只不过它不是在UI线程上更新而已。
结论:三者的区别
- If you want to re draw your view from UI Thread you can call invalidate() method.
- If you want to re draw your view from Non UI Thread you can call postInvalidate() method.
- onDraw是在View初化完成之后开始调用postInvalidate()是重绘的,也就是调用postInvalidate()后系统会重新调用onDraw方法画一次
- Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用。
- Android程序中可以使用的界面刷新方法有两种,分别是利用Handler和利用postInvalidate()来实现在线程中刷新界面
- Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。 invalidate()是用来刷新View的,必须是在UI线程中进行工作。比如在修改某个view的显示时,调用invalidate()才能看到重新绘制的界面。invalidate()的调用是把之前的旧的view从主UI线程队列中pop掉。 一个Android 程序默认情况下也只有一个进程,但一个进程下却可以有许多个线程。在这么多线程当中,把主要是负责控制UI界面的显示、更新和控件交互的线程称为UI线程,由于onCreate()方法是由UI线程执行的,所以也可以把UI线程理解为主线程。其余的线程可以理解为工作者线程。invalidate()得在UI线程中被调动,在工作者线程中可以通过Handler来通知UI线程进行界面更新。而postInvalidate()在工作者线程中被调用