零、前言
本文聚焦:
[1].View#invalidate做了什么,为什么会触发View的重绘?
[2].View是如何被添加到ViewGroup中的?
[3].ViewGroup和ViewRootImpl在invalidate孩子上做了什么?
[4].源码的层次上分析invalidate和postInvalidate的差异与联系?
复制代码
1.View#invalidate
方法
---->[View#invalidate]--------------------
public void invalidate() {
invalidate(true);
}
---->[View#invalidate(boolean)]--------------------
* @hide
*/
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
---->[View#invalidateInternal]--------------------
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
...
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);
///调用父控件的invalidateChild()刷新本View
p.invalidateChild(this, damage);
}
...
}
}
|--- 到这里暂停一下
这mGhostView真跟鬼一样,View中出现了8次,竟没有一次对它赋值
由于是包访问的可能在其他类里吧,这里注意一下,毕竟如果他不为空,就画他然后return了
复制代码
2.谁是我爸?View的滴血认亲
整个View中并没有ViewGroup的身影,而是依靠接口[ViewParent]全权负责
这有一个问题:ViewParent的实现类是谁? 明面有一个ViewGroup的实现, 但别忘了幕后还有个大佬ViewRootImpl也是实现了ViewParent的,那这个p到底是谁呢?
|--可以看到p是承接mParent的局部变量,全文搜索[mParent =]来查看他何时初始化或被赋值的
---->[View#成员变量]-----------------------------
//The parent this view is attached to. -- 该View添加到的父View
protected ViewParent mParent; //注意是protected的访问权限
---->[View#assignParent]-----------------------------
|-- 这里可见assignParent是初始化mParent的核心方法
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
|-- 搜索了一下assignParent在View中并未被调用,那只能说是别人调的
|-- 和View认老爸关系最密切的当属ViewGroup中的addView了,来看一下
---->[ViewGroup#addView(View)]-----------------------------
public void addView(View child) {
--->addView(child, -1);
}
---->[ViewGroup#addView(View,int)]-----------------------------
public void addView(View child, int index) {
...
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
...
}
--->addView(child, index, params);
}
---->[ViewGroup#addView(View,int,LayoutParams)]-----------------------------
public void addView(View child, int index, LayoutParams params) {
...
requestLayout();
invalidate(true);
--->addViewInner(child, index, params, false);
}
---->[ViewGroup#addViewInner]-----------------------------
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
...
--->addInArray(child, index);//将孩子加入到自己的数组里
// tell our children -- 告诉我们的孩子们,他们有爹了
if (preventRequestLayout) {
---> child.assignParent(this);// 便是我们要寻的
} else {
---> child.mParent = this; //这直接让孩子的mParent赋值
}
...
}
|-- 现在再看一下ViewRootImpl,我就单刀直入了,从setView开始,不懂的,看前面几篇相关内容
---->[ViewRootImpl#setView]-------------------------
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
... view.assignParent(this);
}
|-- 此时的View前几篇分析过是DecorView,其中调用了DecorView的assignParent,
所以DecorView是认ViewRootImpl为老爹的,虽然ViewRootImpl不是View,但它却是是个ViewParent
所以当爹是没问题的,那么View的invalidate方法走的是ViewGroup还是ViewRootImpl的invalidateChild?
答:如果是一个ViewGroup,它添加了子View,该子View的爹就是ViewGroup,
走的当然也是ViewGroup#invalidateChild,这是我们日常开发中最常见的
但对于最顶层的DecorView,谁敢当他爹?ViewRootImpl就是他老爸,所以对于DecorView的invalidate方法
当然走的是ViewRootImpl#invalidateChild,所以这就是为什么ViewRootImpl为什么那么厉害的原因
换句话来说,协天子以令诸侯有没有。ViewRootImpl说你们不要在子线程给我刷新UI,View们就乖乖照做
复制代码
3.ViewGroup#invalidateChild
方法
---->[ViewGroup#invalidateChild]--------------------
|--- ViewGroup作为ViewParent的实现类, invalidateChild方法我们看到了
public final void invalidateChild(View child, final Rect dirty) {
...
ViewParent parent = this;
if (attachInfo != null) {
...
}
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
...
//循环找到根view,并调用invalidateChildInParent()方法
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
...
}
} while (parent != null);
}
}
|-- 这里通过 while 来遍历 this ,都执行了一个invalidateChildInParent的方法
该方法返回了一个ViewParent对象,来看一下这个方法:
---->[ViewGroup#invalidateChildInParent]--------------------
public ViewParent invalidateChildInParent(final int[] location, final Rect dir
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
...
return mParent;
}
return null;
}
|-- 这方法看起来只是设置了一下自己的区域和摆位,并没有什么实质性的东西
|-- 不过亮点是他的返回值mParent,也就是它把自己整理一下,把老爸跑出去了
|-- 这样看来上面的invalidateChild就是一直抛老爸,直到DecorView
|-- 因为DecorView 的老爸是ViewRootImpl,所以[parent instanceof View]的条件不满足
|-- 这时候就调用了ViewRootImpl#invalidateChild(ViewGroup全程打酱油的既视感...)
复制代码
4.绘制更新核心:ViewRootImpl#invalidateChild
方法
ViewGroup并不给力,并没有触发孩子绘制方法,ViewRootImpl大佬出场,一招定乾坤
---->[ViewRootImpl#invalidateChild]--------------------
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
---->[ViewRootImpl#invalidateChildInParent]--------------------
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();//划重点...这里检查线程。曹操说:子线程不能更新UI
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
...
--->invalidateRectOnScreen(dirty);
return null;
}
---->[ViewRootImpl#invalidateRectOnScreen]--------------------
private void invalidateRectOnScreen(Rect dirty) {
...
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
---> scheduleTraversals();//开启了一个遍历的计划
}
}
---->[ViewRootImpl#scheduleTraversals]--------------------
|---Choreographer 翻译一下:舞蹈指导者?--大佬真会起名字...
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
---> Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
--------下一段不想看可以跳过,主要追了一下传入的Runnable是什么时候被执行的-------
|---Choreographer的postCallback核心调用的是下面的这个方法:
|--- 主要看入参Runnable的去向,下面的action便是Runnable
---->[Choreographer##postCallbackDelayedInternal]--------------------
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
//这里对action做了处理
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
public void addCallbackLocked(long dueTime, Object action, Object token) {
CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
...
}
private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
CallbackRecord callback = mCallbackPool;
if (callback == null) {
callback = new CallbackRecord();
} else {
mCallbackPool = callback.next;
callback.next = null;
}
callback.dueTime = dueTime;
--->callback.action = action;
callback.token = token;
return callback;
}
|-- 可见action流转到了CallbackRecord的action字段中了
---->[Choreographer#CallbackRecord]------------------------------------------
|-- 可见CallbackRecord的run方法触发了action的run
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
|-- 全局搜了一下,代码就不贴了,最后[c.run(frameTimeNanos)]在[doCallbacks]方法中触发
|-- 而[doCallbacks]在[doFrame]触发,[doFrame]在handler接收[MSG_DO_FRAME]时触发
---------------------------------------------------------------------------------------
|--言归正传:mTraversalRunnable是一个Runnable,通过Choreographer#postCallback最终会被执行
|-- 看一下mTraversalRunnable是什么,干了啥
---->[Choreographer#CallbackRecord]--------------------------------------
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
|-- 简单易懂,执行doTraversal()操作,至于doTraversal()是干嘛的...
|-- 简单讲一下,doTraversal()操作遍历所有节点,进行测量、布局、绘制--(这曹操当得也不容易啊)
|-- 同样的分析我不想写第二遍,详见:所得与所见:[-View周边-] 框架层#三#4
复制代码
到这里总算解开我:invalidate怎样触发View重绘的谜题了。
5.postInvalidate()和 invalidate的区别
---->[View#postInvalidate]-----------------------
public void postInvalidate() {
postInvalidateDelayed(0);
}
---->[View#postInvalidateDelayed]-----------------------
public void postInvalidateDelayed(long delayMilliseconds) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
|-- 感觉挺直爽,直接拿ViewRootImpl#dispatchInvalidateDelayed
---->[ViewRootImpl#dispatchInvalidateDelayed]-----------------------
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
|-- Handler通过obtainMessage将view放在一个MSG_INVALIDATE标识的Message中
|-- 如果Handler不熟悉的,还请移驾:Android点将台:烽火狼烟[-Handler-]
|-- 看Handler,首先不是看它的handleMessage是怎么处理的,而是看Handler在哪个线程创建的
|-- 也就是Handler的Looper是在哪个线程。
---->[ViewRootImpl#mHandler]-----------------------
|-- 并没有在子线程,加上ViewRootImpl是在主线程被创建的(不知道到的看前文),所以mHandler是主线程
final ViewRootHandler mHandler = new ViewRootHandler();
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();//调用了View的invalidate,已切至主线程
break;
|-- 到这里就到头了,也就是说谷歌的大佬怕我们在子线程invalidate烦神
|-- 就内置的了一个Handler帮我们省去麻烦,至于用invalidate还是postInvalidate?
一条直线能到家,你还非要拐个弯吗?毕竟postInvalidate也是触发了View#invalidate
还要额外发个消息才能玩。所以主线程用invalidate,在子线程可以用postInvalidate
当然你觉得postInvalidate太长不好看,可以也无视大佬的一片好心,自己新建Handler,只要你开心...
复制代码
总的看来,View的invalidate方法也并没有我相信中的那么复杂,半天就写完了...
后记:捷文规范
1.本文成长记录及勘误表
项目源码 | 日期 | 附录 |
---|---|---|
V0.1-- | 2018-2-23 | 无 |
发布名:
invalidate方法知多少[-View-] 源码级
捷文链接:juejin.im/post/5c70ce…
2.更多关于我
笔名 | 微信 | |
---|---|---|
张风捷特烈 | 1981462002 | zdl1994328 |
我的github:github.com/toly1994328
我的简书:www.jianshu.com/u/e4e52c116…
我的掘金:juejin.im/user/5b42c0…
个人网站:www.toly1994.com
3.声明
1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持