内容简介:1.整个View中并没有ViewGroup的身影,而是依靠接口[ViewParent]全权负责这有一个问题:ViewParent的实现类是谁? 明面有一个ViewGroup的实现, 但别忘了幕后还有个大佬ViewRootImpl也是实现了ViewParent的,那这个p到底是谁呢?
[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/5c6b71…
2.更多关于我
笔名 | 微信 | |
---|---|---|
张风捷特烈 | 1981462002 | zdl1994328 |
我的github: github.com/toly1994328
我的简书: www.jianshu.com/u/e4e52c116…
我的掘金: juejin.im/user/5b42c0…
个人网站:www.toly1994.com
3.声明
1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持
以上所述就是小编给大家介绍的《invalidate方法知多少[-View-] 源码级》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 【MyBatis源码分析】insert方法、update方法、delete方法处理流程(上篇)
- 【MyBatis源码分析】insert方法、update方法、delete方法处理流程(上篇)
- Runtime源码 方法调用的过程
- Vue源码分析系列二:$mount()方法
- SpringBoot源码解析之main方法推断
- 源码编译LNMP出现的问题及解决方法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。