博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
View.Post () 的身世大揭秘
阅读量:6986 次
发布时间:2019-06-27

本文共 17509 字,大约阅读时间需要 58 分钟。

作者:Insane 时间: 2018.7.31

View.post( ),大家肯定都用过,也就不陌生了。一般使用View.Post ( ) 的场景最常见的就是
1.子线程更UI,
2.获取View的宽高

那就让我们再带着问题去看看原因咯。

public boolean post(Runnable action) {     //判断 attachInfo 是否为空,而进行不同的操作     //那么其实就是要知道 mAttachInfo 是在哪里被赋值的?        final AttachInfo attachInfo = mAttachInfo;        if (attachInfo != null) {            return attachInfo.mHandler.post(action);        }        // Postpone the runnable until we know on which thread it needs to run.        // Assume that the runnable will be successfully placed after attach.        getRunQueue().post(action);        return true;    }复制代码
mAttachInfo的赋值

我们会发现他两个被赋值的地方,分别为 dispatchAttachedToWindowdispatchDetachedFromWindow

//dispatchAttachedToWindow:    void dispatchAttachedToWindow(AttachInfo info, int visibility) {        //这里赋值        mAttachInfo = info;        if (mOverlay != null) {            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);        }        mWindowAttachCount++;        // We will need to evaluate the drawable state at least once.        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;        if (mFloatingTreeObserver != null) {            info.mTreeObserver.merge(mFloatingTreeObserver);            mFloatingTreeObserver = null;        }       registerPendingFrameMetricsObservers();        if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {            mAttachInfo.mScrollContainers.add(this);            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;        }        // Transfer all pending runnables.        //缓存不为空的是时候去执行 缓存的 action        if (mRunQueue != null) {            mRunQueue.executeActions(info.mHandler);            mRunQueue = null;        }        performCollectViewAttributes(mAttachInfo, visibility);        //当对应的 Activity 被添加到 Window的时候调用,只调用一次        onAttachedToWindow();   //  .......省略代码//dispatchDetachedFromWindow:  void dispatchDetachedFromWindow() {        AttachInfo info = mAttachInfo;        if (info != null) {            int vis = info.mWindowVisibility;            if (vis != GONE) {                onWindowVisibilityChanged(GONE);                if (isShown()) {                    // Invoking onVisibilityAggregated directly here since the subtree                    // will also receive detached from window                    onVisibilityAggregated(false);                }            }        }        onDetachedFromWindow();        onDetachedFromWindowInternal();        InputMethodManager imm = InputMethodManager.peekInstance();        if (imm != null) {            imm.onViewDetachedFromWindow(this);        }        ListenerInfo li = mListenerInfo;        final CopyOnWriteArrayList
listeners = li != null ? li.mOnAttachStateChangeListeners : null; if (listeners != null && listeners.size() > 0) { // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. for (OnAttachStateChangeListener listener : listeners) { listener.onViewDetachedFromWindow(this); } } if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) { mAttachInfo.mScrollContainers.remove(this); mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED; } //这里赋空值 mAttachInfo = null; if (mOverlay != null) { mOverlay.getOverlayView().dispatchDetachedFromWindow(); } notifyEnterOrExitForAutoFillIfNeeded(false); }复制代码

但是会发现,到这里的时候我们无法再追踪这两个方法在哪里被调用了,于是我们可以通过网上那些Android源码阅读的网站,或者自己有下载Android源码的来找一找看看 究竟在什么地方被调用的: 推荐一个:http://androidxref.com/ 搜索 dispatchAttachedToWindow,可以发现如下:

可以发现,在 ViewGroupViewRootImpl 均有被调用,那么我们就去看看。

ViewGroup
@Override    void dispatchAttachedToWindow(AttachInfo info, int visibility) {        mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;       // super.dispatchAttachedToWindow(info, visibility); 这句话就是说明他执行了父类的方法      //也就是我们一开始看到的 View 的dispatchAttachedToWindow()的方法。        super.dispatchAttachedToWindow(info, visibility);        mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;        final int count = mChildrenCount;        final View[] children = mChildren;        for (int i = 0; i < count; i++) {            final View child = children[i];    //这里又会把 mAttachInfo 作为参数传递进去,分别让自己的子类去执行 dispatchAttachedToWindow () 方法,    //让自己的子类 分别给 mAttachInfo  赋值。            child.dispatchAttachedToWindow(info,                    combineVisibility(visibility, child.getVisibility()));        }        final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();        for (int i = 0; i < transientCount; ++i) {            View view = mTransientViews.get(i);            view.dispatchAttachedToWindow(info,                    combineVisibility(visibility, view.getVisibility()));        }    }/*但是这样一来我们还是不知道 mAttachInfo  是在那里被赋值的发,只是知道 ViewGroup 会去执行 View 类和掉用子 View 的 dispatchAttachedToWindow () 方法。方法。*/复制代码

继续看看 ViewGroup 里面还有什么地方调用了: addViewInner()方法是 viewGroup addView( )内部都会调用的一个方法

private void addViewInner(View child, int index, LayoutParams params,            boolean preventRequestLayout) {        if (mTransition != null) {            // Don't prevent other add transitions from completing, but cancel remove            // transitions to let them complete the process before we add to the container            mTransition.cancel(LayoutTransition.DISAPPEARING);        }         //判断View是否被添加        if (child.getParent() != null) {            throw new IllegalStateException("The specified child already has a parent. " +                    "You must call removeView() on the child's parent first.");        }        if (mTransition != null) {            mTransition.addChild(this, child);        }        if (!checkLayoutParams(params)) {            params = generateLayoutParams(params);        }        if (preventRequestLayout) {            child.mLayoutParams = params;        } else {            child.setLayoutParams(params);        }        if (index < 0) {            index = mChildrenCount;        }        //添加到 ViewGroup        addInArray(child, index);        // tell our children        if (preventRequestLayout) {            child.assignParent(this);        } else {            child.mParent = this;        }        final boolean childHasFocus = child.hasFocus();        if (childHasFocus) {            requestChildFocus(child, child.findFocus());        }       //这里判断 mAttachInfo 的对象是否为空,如果不为空就把 mAttachInfo 作为参数调用子类的 dispatchAttachedToWindow ( ),那么      //还是回到了 View 的 dispatchAttachedToWindow (),我们还是不知道 mAttachInfo 再哪里给赋值的.        AttachInfo ai = mAttachInfo;        if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {            boolean lastKeepOn = ai.mKeepScreenOn;            ai.mKeepScreenOn = false;            child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));            if (ai.mKeepScreenOn) {                needGlobalAttributesUpdate(true);            }            ai.mKeepScreenOn = lastKeepOn;        }       .......省略复制代码

既然ViewGroup没有,那么我们就去看看 ViewRootImpl。

ViewRootImpl

我们在 ViewRootImpl 的 performTraversals(),发现了dispatchAttachedToWindow()被调用,而 performTraversals() 作用就是遍历整个View树,并且按照要求进行measure,layout和draw流程。

private void performTraversals() {        // cache mView since it is used so much below...        final View host = mView;  //判断是不是第一次  if (mFirst) {       .....      //这里调用了 dispatchAttachedToWindow,并且把 mAttachInfo 给子view       host.dispatchAttachedToWindow(mAttachInfo, 0);       mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);       dispatchApplyInsets(host);     //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);      .....}    mFirst=false    ...  // Execute enqueued actions on every traversal in case a detached view enqueued an action    getRunQueue().executeActions(mAttachInfo.mHandler);    ...    performMeasure();    ...    performLayout();    ...    performDraw();    ...复制代码

上面的代码,等下我们再回来看,我们先找找在 ViewRootImpl 里面 mAttachInfo 是在哪被赋值的

...       final View.AttachInfo mAttachInfo;        ...      // mAttachInfo 就是在这里被赋值了,其中在多个参数之中,我们发现了 mHandler。       mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,                context);    //继续看看 mHandler 是在哪被初始化的。  final ViewRootHandler mHandler = new ViewRootHandler();/*通过这句代码我们就可以知道。这里 new 的时候是无参构造函数,那默认绑定的就是当前线程的 Looper,而这句 new 代码是在主线程中执行的,所以这个 Handler 绑定的也就是主线程的 Looper*/再结合:getRunQueue().executeActions(mAttachInfo.mHandler);   public void executeActions(Handler handler) {        synchronized (this) {            final HandlerAction[] actions = mActions;            for (int i = 0, count = mCount; i < count; i++) {                final HandlerAction handlerAction = actions[i];                handler.postDelayed(handlerAction.action, handlerAction.delay);            }            mActions = null;            mCount = 0;        }    }复制代码
为什么能更新UI:

总结回顾一下: 我们知道了 mAttachInfo 是在 ViewRootImpl 初始化的,再结合刚说等下回去看的 performTraversals 的方法,可以知道ViewRootImpl 会调用子view的 dispatchAttachedToWindow。我们还可以知道为什么 View.post(Runnable),可以更新UI了,因为这些 Runnable 操作都通过 ViewRootImpl 的 mHandler 切到主线程来执行了。

为什么能获取宽高

那么我们再次回到 一开始的的地方,我们知道 View 里面的 mAttachInfo 是在 ViewdispatchAttachedToWindow 被赋值,那么 dispatchAttachedToWindow()是在什么时候执行的呢?我们上面分析的是在哪调用了他,和 mAttachInfo的初始化,细心的朋友,会发现在ViewdispatchAttachedToWindow()onAttachedToWindow();,那么我们就可简单写个测试。

class TestView : TextView {    constructor(context: Context) : super(context) {}    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}    override fun onAttachedToWindow() {        Log.e("TAG---AttachedToWindow", "onAttachedToWindow");        super.onAttachedToWindow();    }}//MainActivity :class MainActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)                Log.e("TAG---没有Post", "mButton width : " + tv_test.getMeasuredWidth() + " - height : " + tv_test.getMeasuredHeight());        tv_test.post {            Log.e("TAG---Post", "mButton width : " + tv_test.getMeasuredWidth() + " - height : " + tv_test.getMeasuredHeight());        }    }}//结果:07-29 17:17:24.201 31814-31814/? E/TAG---没有Post: mButton width : 0 - height : 007-29 17:17:24.261 31814-31814/? E/TAG---AttachedToWindow: onAttachedToWindow07-29 17:17:24.351 31814-31814/? E/TAG---Post: mButton width : 84 - height : 57复制代码

那么结果就出来了,在 onCreate 中获取宽高,AttachedToWindow ( ) 是还没执行的,那就说明一开始的时候 mAttachInfo 是为空值的,那么我们再看开头的第一段代码:

public boolean post(Runnable action) {        // mAttachInfo 为空        final AttachInfo attachInfo = mAttachInfo;        if (attachInfo != null) {            return attachInfo.mHandler.post(action);        }        // Postpone the runnable until we know on which thread it needs to run.        // Assume that the runnable will be successfully placed after attach.        getRunQueue().post(action);        return true;    }那么他就会执行: getRunQueue().post(action); public class HandlerActionQueue {    private HandlerAction[] mActions;    private int mCount;    public void post(Runnable action) {        postDelayed(action, 0);    }    public void postDelayed(Runnable action, long delayMillis) {        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);        synchronized (this) {            if (mActions == null) {                mActions = new HandlerAction[4];            }            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);            mCount++;        }    }复制代码

我们post()传进来的 Runnable 会先经过 HandlerAction 包装一下,然后再缓存起来。HandlerActionQueue 是通过一个默认大小为4的数组保存这些 Runnable 操作的,如果数组不够时,就会通过 GrowingArrayUtils 来扩充数组。那么既然是被缓存起来的,那么他是什么时候执行呢?我又会发现 他执行的方法还是在 dispatchAttachedToWindow 里面:

dispatchAttachedToWindow :     //缓存不为空的是时候去执行 缓存的 action        if (mRunQueue != null) {            mRunQueue.executeActions(info.mHandler);            mRunQueue = null;        }executeActions:    public void executeActions(Handler handler) {        synchronized (this) {            final HandlerAction[] actions = mActions;            for (int i = 0, count = mCount; i < count; i++) {                final HandlerAction handlerAction = actions[i];                handler.postDelayed(handlerAction.action, handlerAction.delay);            }            mActions = null;            mCount = 0;        }    }复制代码

既然我们知道了 post()传进来的 Runnable 会在 dispatchAttachedToWindow 执行,结合我们上面的分析,我们就可以知道,post 的操作是要经过 ViewRootImpl 的 performTraversals(),而它的作用就是遍历整个View树,并且按照要求进行measure,layout和draw流程。但是仔细看看代码,我们会发现:

private void performTraversals() {        // cache mView since it is used so much below...        final View host = mView;  //判断是不是第一次  if (mFirst) {       .....      //这里调用了 dispatchAttachedToWindow,明显是在 performMeasure 之前,     //为什么在测量之前调用还能得到宽高呢?       host.dispatchAttachedToWindow(mAttachInfo, 0);       mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);       dispatchApplyInsets(host);     //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);      .....}    mFirst=false    ...  // Execute enqueued actions on every traversal in case a detached view enqueued an action    getRunQueue().executeActions(mAttachInfo.mHandler);    ...    performMeasure();    ...    performLayout();    ...    performDraw();    ...复制代码

那么 为什么明明测量 performMeasure(); 的是在 dispatchAttachedToWindow 之后执行,但是我们却能得到测量后的宽高?请看下面的代码:

final class TraversalRunnable implements Runnable {        @Override        public void run() {          // doTraversal 这里执行            doTraversal();        }    }    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();    void doTraversal() {        if (mTraversalScheduled) {            mTraversalScheduled = false;          //向Looper中移除了Barrier(监控器),同步的消息可以执行            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);            if (mProfile) {                Debug.startMethodTracing("ViewAncestor");            }            //performTraversals() 在这里被执行            performTraversals();            if (mProfile) {                Debug.stopMethodTracing();                mProfile = false;            }        }    }复制代码
mTraversalBarrier 是什么东东?

为了让View能够有快速的布局和绘制,android中定义了一个Barrier的概念,当View在绘制和布局时会向Looper中添加了Barrier(监控器),这样后续的消息队列中的同步的消息将不会被执行,以免会影响到UI绘制,但是只有异步消息才能被执行。 所谓的异步消息也只是体现在这,添加了Barrier后,消息还可以继续被执行,不会被推迟运行。 如何使用异步消息,只有在创建Handler(构造方法的参数上标识是否异步消息)的时候或者在发送Message(Mesasge#setAsynchronous(true))时进行设置。而异步消息应用层是无法设置,因为相关设置的方法均是Hide的。

那就是什么意思呢? 首先,我们搞清楚 mTraversalScheduled 这个对象是在哪被赋值。

void scheduleTraversals() {        if (!mTraversalScheduled) {           //这里            mTraversalScheduled = true;           //向Looper中添加了Barrier(监控器),这样后续的消息队列中的同步的消息将不会被执行,           //以免会影响到UI绘制,但是只有异步消息才能被执行            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();            //这里又调用了  mTraversalRunnable。            mChoreographer.postCallback(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);            if (!mUnbufferedInputDispatch) {                scheduleConsumeBatchedInput();            }            notifyRendererOfFramePending();            pokeDrawLockIfNeeded();        }    }    void unscheduleTraversals() {        if (mTraversalScheduled) {           //这里            mTraversalScheduled = false;           //向Looper中移除了Barrier(监控器),同步的消息可以执行            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);        //这里又调用了  mTraversalRunnable。            mChoreographer.removeCallbacks(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);        }    }复制代码

scheduleTraversals ( ) 方法是在 requestLayout( )被调用的, requestLayout( )是什么?额,这里就不解释了,不然又要写一大堆,哈哈。只要知道 第一次调用requestLayout( ) 就是引起整个 View 的绘制流程

@Override public void requestLayout() {     if (!mHandlingLayoutInLayoutRequest) {       // 检查当前线程         checkThread();         mLayoutRequested = true;        // 调用绘制         scheduleTraversals();复制代码

那么就是 scheduleTraversals( )——》TraversalRunnable ( )——》doTraversal( )——》performTraversals( ),而且 doTraversal ( )中向Looper中移除了Barrier(监控器),同步的消息可以执行, 并且我们知道了 ViewRootImp 的 Handler 就是主线程的 ,而我们一开始 post 进来的 Runnable 也是在主线程的,而主线程的 Handler 是同步的,就是执行完一个 Message 才回继续往下执行。那么 我们再次回到 performTraversals( )

// 往下看的时候就会发现,在下面 又会再一次执行 scheduleTraversals( ),也就是代表会再一次执行  performTraversals( ),  if (!cancelDraw && !newSurface) {            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {                for (int i = 0; i < mPendingTransitions.size(); ++i) {                    mPendingTransitions.get(i).startChangingAnimations();                }                mPendingTransitions.clear();            }            performDraw();        } else {            if (isViewVisible) {                // Try again                scheduleTraversals();            } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {                for (int i = 0; i < mPendingTransitions.size(); ++i) {                    mPendingTransitions.get(i).endChangingAnimations();                }                mPendingTransitions.clear();            }        }复制代码

再一次执行的时候,已经是在 performMeasure( )之后了,那当执行到 我们 Post 的时候 自然而然就能得到宽高了。

转载地址:http://knqpl.baihongyu.com/

你可能感兴趣的文章
Balluff推出刀具识别系统
查看>>
美国支付巨头Verifone遭遇网络攻击
查看>>
开平推进智慧城市等领域信息化建设及公共数据资源共享
查看>>
宜兴电信成功跨界合作开拓农村物联网市场
查看>>
Oracle业务适合用PostgreSQL去O的一些评判标准
查看>>
多个常见代码设计缺陷
查看>>
今年光伏市场规模可达30GW 分布式有望占据三分江山
查看>>
因新漏洞问题 Firefox 49发布时间将延期一周
查看>>
WLAN产品形态之分层架构
查看>>
Chrome 隐藏 SSL 证书信息 禁止禁用 DRM
查看>>
AngularJS 的自定义指令
查看>>
《CCNA ICND2(200-101)认证考试指南(第4版)》——第1章定义生成树协议
查看>>
什么样的 RPC 才是好用的 RPC
查看>>
《Adobe Premiere Pro CC经典教程》——14.6 特殊颜色效果
查看>>
Debian 项目不再提供 CD 格式的 ISO 镜像
查看>>
《设计团队协作权威指南》—第1章1.3节甘为螺丝钉
查看>>
android 屏幕保持唤醒 不锁屏 android.permission.WAKE_LOCK
查看>>
《Unity 3D 游戏开发技术详解与典型案例》——1.3节第一个Unity 3D程序
查看>>
Airbnb数据科学团队进化论:如何由内而外实现数据驱动
查看>>
如何用机器学习预测超售,避免美联航“暴力赶客”悲剧
查看>>