Android进阶之源码中分析View.post()为何获取控件宽高

开发 前端
为什么 View.post() 的操作是可以对 UI 进行操作的呢,即使是在子线程中调用 View.post()? 今天我们就来分析分析

[[425705]]

本文转载自微信公众号「Android开发编程」,作者Android开发编程。转载本文请联系Android开发编程公众号。

前言

为什么 View.post() 的操作是可以对 UI 进行操作的呢,即使是在子线程中调用 View.post()?

今天我们就来分析分析

一、View.post源码深入分析

1、View.post()

View 的 post 方法如下:

public boolean post(Runnable action) { 
    // 1、首先判断AttachInfo是否为null 
    final AttachInfo attachInfo = mAttachInfo; 
    if (attachInfo != null) { 
        // 1.1如果不为null,直接调用其内部Handler的post 
        return attachInfo.mHandler.post(action); 
    } 
    // 2、否则加入当前View的等待队列 
    getRunQueue().post(action); 
    return true

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • AttachInfo 是 View 的静态内部类,每个 View 都会持有一个 AttachInfo,它默认为 null;
  • 如果mAttachInfo为空,就执行:把action加入当前view的等待队列;

2、getRunQueue().post()

看下 getRunQueue().post():

private HandlerActionQueue getRunQueue() { 
    if (mRunQueue == null) { 
        mRunQueue = new HandlerActionQueue(); 
    } 
    return mRunQueue; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • getRunQueue() 返回的是 HandlerActionQueue;
  • 调用了 HandlerActionQueue 的 post 方法:
public void post(Runnable action) { 
    // 调用到postDelayed方法,这有点类似于Handler发送消息 
    postDelayed(action, 0); 

  • 1.
  • 2.
  • 3.
  • 4.
// 实际调用postDelayed 
public void postDelayed(Runnable action, long delayMillis) { 
    // HandlerAction表示要执行的任务 
    final HandlerAction handlerAction = new HandlerAction(action, delayMillis); 
    synchronized (this) { 
        if (mActions == null) { 
            // 创建一个保存HandlerAction的数组 
            mActions = new HandlerAction[4]; 
        } 
        // 表示要执行的任务HandlerAction 保存在 mActions 数组中 
        mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); 
        // mActions数组下标位置累加1 
        mCount++; 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

3、HandlerAction

HandlerAction 表示一个待执行的任务,内部持有要执行的 Runnable 和延迟时间;=

private static class HandlerAction { 
    // post的任务 
    final Runnable action
    // 延迟时间 
    final long delay; 
    public HandlerAction(Runnable action, long delay) { 
        this.action = action
        this.delay = delay; 
    } 
    // 比较是否是同一个任务 
    // 用于匹配某个 Runnable 和对应的HandlerAction 
    public boolean matches(Runnable otherAction) { 
        return otherAction == null && action == null 
                || action != null && action.equals(otherAction); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

postDelayed() 创建一个默认长度为 4 的 HandlerAction 数组,用于保存 post() 添加的任务;

梳理总结:

  • 我们调用 View.post(Runnable) 传进去的 Runnable 操作,在传到 HandlerActionQueue 里会先经过 HandlerAction 包装一下,然后再缓存起来;
  • 在 执行 View.post(Runnable) 时,因为这时候 View 还没有 attachedToWindow,所以这些 Runnable 操作其实并没有被执行,而是先通过 HandlerActionQueue 缓存起来;

4、AttachInfo

看下 AttachInfo 的创建过程,先看下它的构造方法:

AttachInfo(IWindowSession session, IWindow window, Display display, 
               ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer, 
               Context context) { 
        mSession = session; 
        mWindow = window; 
        mWindowToken = window.asBinder(); 
        mDisplay = display; 
        // 持有当前ViewRootImpl 
        mViewRootImpl = viewRootImpl; 
        // 当前渲染线程Handler 
        mHandler = handler; 
        mRootCallbacks = effectPlayer; 
        mTreeObserver = new ViewTreeObserver(context); 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

AttachInfo 中持有当前线程的 Handler;

4.1、mAttachInfo 赋值:

void dispatchAttachedToWindow(AttachInfo info, int visibility) { 
    // 给当前View赋值AttachInfo,此时所有的View共用同一个AttachInfo(同一个ViewRootImpl内) 
    mAttachInfo = info; 
    if (mOverlay != null) { 
        // 任何一个View都有一个ViewOverlay 
        // ViewGroup的是ViewGroupOverlay 
        // 它区别于直接在类似RelativeLaout/FrameLayout添加View,通过ViewOverlay添加的元素没有任何事件 
        // 此时主要分发给这些View浮层 
        mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility); 
    } 
    mWindowAttachCount++; 
    if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER) != 0) { 
        mAttachInfo.mScrollContainers.add(this); 
        mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED; 
    } 
    //  mRunQueue,就是在前面的 getRunQueue().post() 
    // 实际类型是 HandlerActionQueue,内部保存了当前View.post的任务 
    if (mRunQueue != null) { 
        // 执行使用View.post的任务 
        // 注意这里是post到渲染线程的Handler中 
        mRunQueue.executeActions(info.mHandler); 
        // 保存延迟任务的队列被置为null,因为此时所有的View共用AttachInfo 
        mRunQueue = null
    } 
    performCollectViewAttributes(mAttachInfo, visibility); 
    // 回调View的onAttachedToWindow方法 
    // 在Activity的onResume方法中调用,但是在View绘制流程之前 
    onAttachedToWindow(); 
    ListenerInfo li = mListenerInfo; 
    final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = 
            li != null ? li.mOnAttachStateChangeListeners : null
    if (listeners != null && listeners.size() > 0) { 
        for (OnAttachStateChangeListener listener : listeners) { 
            // 通知所有监听View已经onAttachToWindow的客户端,即view.addOnAttachStateChangeListener(); 
            // 但此时View还没有开始绘制,不能正确获取测量大小或View实际大小 
            listener.onViewAttachedToWindow(this); 
        } 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

5、executeActions

mRunQueue 就是保存了 View.post() 任务的 HandlerActionQueue;此时调用它的 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中,等待执行 
            handler.postDelayed(handlerAction.action, handlerAction.delay); 
        } 
        //此时不在需要,后续的post,将被添加到AttachInfo中 
        mActions = null
        mCount = 0; 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 遍历所有已保存的任务,发送到 Handler 中排队执行;
  • 将保存任务的 mActions 置为 null,因为后续 View.post() 直接添加到 AttachInfo 内部的 Handler;

6、performTraversals

  • dispatchAttachedToWindow() 的调用时机是在 View 绘制流程的开始阶段;
  • 在 ViewRootImpl 的 performTraversals 方法,在该方法将会依次完成 View 绘制流程的三大阶段:测量、布局和绘制;
// View 绘制流程开始在 ViewRootImpl 
private void performTraversals() { 
    // mView是DecorView 
    final View host = mView; 
    if (mFirst) { 
        ..... 
        // host为DecorView 
        // 调用DecorVIew 的 dispatchAttachedToWindow,并且把 mAttachInfo 给子view 
        host.dispatchAttachedToWindow(mAttachInfo, 0); 
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); 
        dispatchApplyInsets(host); 
        ..... 
    }  
   mFirst=false 
   getRunQueue().executeActions(mAttachInfo.mHandler); 
   // View 绘制流程的测量阶段 
   performMeasure(); 
   // View 绘制流程的布局阶段 
   performLayout(); 
   // View 绘制流程的绘制阶段 
   performDraw(); 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

7、dispatchAttachedToWindow

  • 每个 Activity 都有一个关联的 Window 对象,用来描述应用程序窗口,每个窗口内部又包含一个 DecorView 对象,DecorView 对象用来描述窗口的视图 — xml 布局;
  • 通过 setContentView() 设置的 View 布局最终添加到 DecorView 的 content 容器中;
  • DecorView 的 dispatchAttachedToWindow 方法的执行过程,DecorView 并没有重写该方法,而是在其父类 ViewGroup 中:
void dispatchAttachedToWindow(AttachInfo info, int visibility) { 
    mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW; 
    super.dispatchAttachedToWindow(info, visibility); 
    mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW; 
    // 子View的数量 
    final int count = mChildrenCount; 
    final View[] children = mChildren; 
    // 遍历所有子View 
    for (int i = 0; i < count; i++) { 
        final View child = children[i]; 
        // 遍历调用所有子View的dispatchAttachedToWindow 
        // 为每个子View关联AttachInfo 
        child.dispatchAttachedToWindow(info, 
                combineVisibility(visibility, child.getVisibility())); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • for 循环遍历当前 ViewGroup 的所有 childView,为其关联 AttachInfo;
  • 子 View 的 dispatchAttachedToWindow,首先为当前 View 关联 AttachInfo,然后将之前 View.post() 保存的任务添加到 AttachInfo 内部的 Handler;
  • performTraversals() 会先执行 dispatchAttachedToWindow(),这时候所有子 View 通过 View.post(Runnable) 缓存起来的 Runnable 操作就都会通过 mAttachInfo.mHandler 的 post() 方法将这些 Runnable 封装到 Message 里发送到 MessageQueue 里;
  • mHandler 我们上面也分析过了,绑定的是主线程的 Looper,所以这些 Runnable 其实都是发送到主线程的 MessageQueue 里排队,等待执行。然后 performTraversals() 继续往下工作,相继执行 performMeasure(),performLayout() 等操作;
  • 等全部执行完后,表示这个 Message 已经处理完毕,所以 Looper 才会去取下一个 Message,这时候,才有可能轮到这些 Runnable 执行;
  • 所以,这些 Runnable 操作也就肯定会在 performMeasure() 操作之后才执行,宽高也就可以获取到了;

总结

View.post(Runnable) 内部会自动分两种情况处理,当 View 还没 attachedToWindow 时,会先将这些 Runnable 操作缓存下来;否则就直接通过 mAttachInfo.mHandler 将这些 Runnable 操作 post 到主线程的 MessageQueue 中等待执行;

View.post() 任务能够保证在所有 View 绘制流程结束之后被调用,故如果需要依赖 View 绘制任务,此时可以优先考虑使用该机制;

 

责任编辑:武晓燕 来源: Android开发编程
相关推荐

2021-05-17 09:50:06

Kubebuilde源码CURD

2024-06-19 08:32:13

2017-09-05 15:27:33

View Api23Api24

2017-04-28 09:58:21

AndroidLinearLayou宽高

2016-09-22 15:50:38

JavascriptRedux源码解析

2021-08-17 13:41:11

AndroidView事件

2019-12-23 09:13:11

Python数据语言

2021-12-06 14:52:08

动画Android补间动画

2016-03-14 09:43:47

androidview总结

2021-09-30 07:36:51

AndroidViewDraw

2021-09-05 07:35:58

lifecycleAndroid组件原理

2011-03-23 10:30:01

LAMPApache源码

2021-10-03 15:08:32

Android

2021-09-02 07:00:01

Glide流程Android

2022-04-06 14:55:45

Harmony同步机制鸿蒙

2021-08-28 07:48:04

AndroidActivityRecActivitySta

2021-09-09 06:55:43

AndroidViewDragHel原理

2021-09-07 06:40:25

AndroidLiveData原理

2010-02-06 13:28:31

Android源码

2015-02-11 17:49:35

Android源码自定义控件
点赞
收藏

51CTO技术栈公众号