Android UI绘制之UI绘制流程(包含屏幕刷新机制)

这是AndroidUI绘制流程分析的第一篇文章,主要分析当我们点开应用程序的时候,到界面中的View显示出来,这中间的一系列流程。

在介绍View绘制流程之前,咱们先简单介绍一下Android视图层次结构以及DecorView,因为View的绘制流程的入口和DecorView有着密切的联系。


472002-20190518164042396-2093768305.png

ActivityThread

我们平时所说的主线程,实际上指的就是ActivityThread这个类,它里面有一个main()函数:

/==========ActivityThread===========
 public static void main(String[] args) {
       ......
       ActivityThread thread = new ActivityThread();
       ......
 }

在main中,实例了一个ActivityThread(),该类中有如下的代码:

//========ActivityThread.java=========
......
final H mH = new H();
......
private class H extends Handler {
      public static final int LAUNCH_ACTIVITY         = 100;
      public static final int RESUME_ACTIVITY         = 107;
      public static final int RELAUNCH_ACTIVITY       = 126;
      ......
      public void handleMessage(Message msg) {
          switch (msg.what) {
                case LAUNCH_ACTIVITY:
                      ......
                 case RESUME_ACTIVITY:
                      handleResumeActivity(...)
                      ......
                 case RELAUNCH_ACTIVITY:
                      ......
}
......
final void handleResumeActivity(...) {
     ......
     ViewManager wm = a.getWindowManager();
     ......
     wm.addView(decor, l);
     ......
}

Activity 的启动是在 ActivityThread 里完成的,handleLaunchActivity() 会依次间接的执行到 ActivityonCreate(), onStart(), onResume(),会调用到ActivityThread.handleResumeActivity(...)方法,关于视图的绘制过程最初就是从这个方法开始的。

View绘制起源UML时序图

整个调用链如下图所示,直到ViewRootImpl类中的performTraversals()中,才正式开始绘制流程了,所以一般都是以该方法作为正式绘制的源头。


472002-20191209172930816-1347556760.png

查看handleResumeActivity()方法

//===========ActivityThread.java==========
final void handleResumeActivity(...) {
    ......
    //调用activity的onResume方法
    ActivityClientRecord r = performResumeActivity(token, clearHide);
    //跟踪代码后发现其初始赋值为mWindow = new PhoneWindow(this, window, activityConfigCallback);
    r.window = r.activity.getWindow();
       //从PhoneWindow实例中获取DecorView
    View decor = r.window.getDecorView();
    ......
    //跟踪代码后发现,vm值为上述PhoneWindow实例中获取的WindowManager。
   // focus--1
    ViewManager wm = a.getWindowManager();
    ......
    //当前window的属性,从代码跟踪来看是PhoneWindow窗口的属性
    WindowManager.LayoutParams l = r.window.getAttributes();
    ......
    wm.addView(decor, l);
    ......
}

handleResumeActivity方法中的performResumeActivity调用activityonResume方法

public final ActivityClientRecord performResumeActivity(IBinder token,
        boolean clearHide) {
    ActivityClientRecord r = mActivities.get(token);
    if (r != null && !r.activity.mFinished) {
       .....................
        try {
            r.activity.onStateNotSaved();
            r.activity.mFragments.noteStateNotSaved();
            //调用activity的onResume方法
            r.activity.performResume();
...............................
        } catch (Exception e) {
       ........   
        }
    }
    return r;
}
#Activity.java
final void performResume(boolean followedByPause, String reason) {
        performRestart();
        ...
        mInstrumentation.callActivityOnResume(this);
}
//==========Instrumentation.java========
public void callActivityOnStart(Activity activity) {
    activity.onStart();
}
......
public void callActivityOnRestart(Activity activity) {
    activity.onRestart();
}
......
public void callActivityOnResume(Activity activity) {
    activity.onResume();
    ......
}

这三个方法最终调用的就是对应的如下方法:

//===========Activity.java========
protected void onStart() {
    ......
}

protected void onReStart() {
    ......
}

protected void onResume() {
    ......
}

handleResumeActivity执行流程来看onResume调用时候,Activity中的UI界面并没有经过measurelayoutdraw等流程,所以直接在onResume或者之前的onCreate中执行ui操纵都是无用的,因为这个时候Ui界面不可见,没有绘制。

也就是说,其实打开一个 Activity,当它的 onCreate---onResume 生命周期都走完后,才将它的 DecoView 与新建的一个 ViewRootImpl 对象绑定起来,同时开始安排一次遍历 View 任务也就是绘制 View 树的操作等待执行,然后将 DecoViewparent 设置成 ViewRootImpl对象。

注意focus--1处的代码,ViewManager是一个接口,addView是其中定义的一个空方法,WindowManager是其子类,WindowManagerImplWindowManager的实现类。

代码中的r.window的值可以根据Activity.java的如下代码得知,其值为PhoneWindow实例。

//===============Activity.java=============
private Window mWindow;
public Window getWindow() {
   return mWindow;
}

final void attach(...){
   ......
   mWindow = new PhoneWindow(this, window, activityConfigCallback);
   ......
}

在代码ViewManager.addView(decorView,layoutParams)方法中,执行了View的绘制过程。查看它的参数:

  • DecorView

这是window的顶级视图

  • WindowManager.LayoutParams
//===================Window.java===================
//The current window attributes.
    private final WindowManager.LayoutParams mWindowAttributes =
        new WindowManager.LayoutParams();
......
public final WindowManager.LayoutParams getAttributes() {
        return mWindowAttributes;
    }
......


//==========WindowManager.java的内部类LayoutParams extends ViewGroup.LayoutParams=============
public LayoutParams() {
            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            ......
        }


//==============ViewGroup.java内部类LayoutParams====================
public LayoutParams(int width, int height) {
            this.width = width;
            this.height = height;
        }

该参数表示的是PhoneWindowLayoutParams属性,其widthheight值均为LayoutParams.MATCH_PARENT

PhoneWindow和DecorView通过组合方式联系在一起的,而DecorView是整个View体系的根View。

回到ViewManager.addView(decorView,layoutParams),ViewManager是接口,我们直接看它的实现类WindowManagerImpl:

#WindowManagerImpl.java
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
 ...
@Override
 public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

WindowManagerImpl又交给了WindowManagerGlobal来实现

#WindowManagerGlobal.java
// mViews保存的是View对象,DecorView
private final ArrayList<View> mViews = new ArrayList<View>();
//mRoots保存和顶层View关联的ViewRootImpl对象
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//mParams保存的是创建顶层View的layout参数。
private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();

// 核心实现
...
root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
...
ViewRootImpl

每个 Activity 对应一颗以 DecorView 为根布局的 View 树,但其实 DecorView 还有 mParent,而且就是 ViewRootImpl,而且每个界面上的 View 的刷新,绘制,点击事件的分发其实都是由 ViewRootImpl 作为发起者的,由 ViewRootImpl 控制这些操作从 DecorView 开始遍历 View 树去分发处理。

ViewRootImpl是一个视图层次结构的顶部,它实现了View与WindowManager之间所需要的协议,作为WindowManagerGlobal中大部分的内部实现。它是连接WindowManager和DecorView的纽带。View的三大流程measure、layout、draw都是通过ViewRootImpl来完成。

查看ViewRootImpl.setView:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
        ...
        // 核心实现
        requestLayout();
}
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

View绘制,先判断当前线程

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

判断完线程后,接着调用scheduleTraversals()

ViewRootImpl#scheduleTraversals
  void scheduleTraversals() {
       if (!mTraversalScheduled) {
            mTraversalScheduled = true;
           // 添加同步屏障消息,相当于在MessageQueue中插入了一个Message,并且是在onResume之后插入的
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

scheduleTraversals中设置了同步障碍消息,就是相当于在MessageQueue中插入了一个Message,并且是在onResume之后插入的,所以在onResume中handler.post(Runnable)之后,这个消息会在同步障碍Message之前,会先被执行,这个时候依然没有刷新绘制界面,待查询到同步障碍Message时候,会等待下个异步Message(刷新绘制界面的Message)出现。

mTraversalScheduled 这个 boolean 变量的作用等会再来看,先看看mChoreographer.postCallback()这个方法,传入了三个参数,第二个参数是一个 Runnable 对象,先来看看这个 mTraversalRunnable,scheduleTraversals中会通过handler去异步调用mTraversalRunnable接口

  final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

接着

  void doTraversal() {
            ...
       if (mTraversalScheduled) {
           mTraversalScheduled = false;
           mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
           performTraversals();
            ...
    }

看看这个方法做的事,跟scheduleTraversals()正好相反,一个将变量置成 true,这里置成false,一个是postSyncBarrier(),这里是 removeSyncBarrier(),具体作用等会再说,继续先看看 performTraversals(),这个方法也是屏幕刷新的关键,这个方法很长核心便是

// 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(); 
} 

dispatchAttachedToWindow方法后面再说。

View 的测量、布局、绘制三大流程都是交由ViewRootImpl 发起,而且还都是在performTraversals()方法中发起的

了解了 performTraversals() 是刷新界面的源头后,接下去就需要了解下它是什么时候执行的,和 scheduleTraversals() 又是什么关系?

performTraversals() 是在 doTraversal() 中被调用的,而 doTraversal()又被封装到一个 Runnable 里,那么关键就是这个 Runnable什么时候被执行了?

Choreographer

scheduleTraversals()里调用了 ChoreographerpostCallback() 将 Runnable 作为参数传了进去,所以跟进去看看:

#Choreographer.java
 public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }
1924341-1b4f5fb7244d6c4b (1).png

因为 postCallback()调用 postCallbackDelayed()时传了 delay = 0 进去,所以在 postCallbackDelayedInternal() 里面会先根据当前时间戳将这个 Runnable 保存到一个 mCallbackQueue队列里,这个队列跟 MessageQueue 很相似,里面待执行的任务都是根据一个时间戳来排序。然后走了 scheduleFrameLocked()方法这边,看看做了些什么:

    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {// 默认true
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }
                if (isRunningOnLooperThreadLocked()) {//判断当前线程是否在主线程
                   // focus1
                    scheduleVsyncLocked();
                } else {
                   // focus2
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }

focus1 和 focus2处的代码也就是if和else分支分别处理。先看else代码,对这个 Message 设置了异步的标志而且用了sendMessageAtFrontOfQueue() 方法,这个方法是将这个 Message 直接放到 MessageQueue 队列里的头部,可以理解成设置了这个 Message 为最高优先级,那么先看看这个 Message 做了些什么:

public void handleMessage(Message msg) {
            switch (msg.what) {
               ...
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
                    break;
                ...
            }
        }

void doScheduleVsync() {
        synchronized (mLock) {
            if (mFrameScheduled) {
                scheduleVsyncLocked();
            }
        }
    }

所以这个 Message 最后做的事就是 scheduleVsyncLocked()。我们回到scheduleFrameLocked() 这个方法里,当走 if 里的代码时,直接调用了scheduleVsyncLocked(),当走 else 里的代码时,发了一个最高优先级的 Message,这个 Message 也是执行 scheduleVsyncLocked()

关键在于 if 条件里那个方法,我的理解那个方法是用来判断当前是否是在主线程的,我们知道主线程也是一直在执行着一个个的 Message,那么如果在主线程的话,直接调用这个方法,那么这个方法就可以直接被执行了,如果不是在主线程,那么 post 一个最高优先级的 Message 到主线程去,保证这个方法可以第一时间得到处理。

查看scheduleVsyncLocked

 private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

#DisplayEventReceiver#scheduleVsync
    public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else {
            nativeScheduleVsync(mReceiverPtr);
        }
    }

调用了 native 层的一个方法,那跟到这里就跟不下去了。

那到这里,我们先来梳理一下:

到这里为止,我们知道一个 View 发起刷新的操作时,会层层通知到 ViewRootImpl 的 scheduleTraversals()里去,然后这个方法会将遍历绘制 View 树的操作 performTraversals() 封装到 Runnable 里,传给 Choreographer,以当前的时间戳放进一个 mCallbackQueue 队列里,然后调用了 native 层的一个方法就跟不下去了。所以这个 Runnable 什么时候会被执行还不清楚。那么,下去的重点就是搞清楚它什么时候从队列里被拿出来执行了?

既然这个 Runnable 操作被放在一个 mCallbackQueue 队列里,那就从这个队列着手,看看这个队列的取操作在哪被执行了:


还记得我们说过在ViewRootImplscheduleTraversals() 里会将遍历 View 树绘制的操作封装到 Runnable 里,然后调用 ChoreographerpostCallback() 将这个 Runnable 放进队列里么,而当时调用 postCallback() 时传入了多个参数,这是因为 Choreographer 里有多个队列,而第一个参数 Choreographer.CALLBACK_TRAVERSAL 这个参数是用来区分队列的,可以理解成各个队列的 key 值。

那么这样一来,就找到关键的方法了:doFrame(),这个方法里会根据一个时间戳去队列里取任务出来执行,而这个任务就是 ViewRootImpl封装起来的 doTraversal()操作,而 doTraversal() 会去调用performTraversals() 开始根据需要测量、布局、绘制整颗 View 树。所以剩下的问题就是 doFrame() 这个方法在哪里被调用了。

有几个调用的地方,但有个地方很关键:


关键的地方来了,这个继承自 DisplayEventReceiver 的 FrameDisplayEventReceiver 类的作用很重要。跟进去看注释,我只能理解它是用来接收底层信号用的。但看了网上的解释后,所有的都理解过来了:

FrameDisplayEventReceiver继承自DisplayEventReceiver接收底层的VSync信号开始处理UI过程。VSync信号由SurfaceFlinger实现并定时发送。FrameDisplayEventReceiver收到信号后,调用onVsync方法组织消息发送到主线程处理。这个消息主要内容就是run方法里面的doFrame了,这里mTimestampNanos是信号到来的时间参数。

也就是说,onVsync() 是底层会回调的,可以理解成每隔 16.6ms 一个帧信号来的时候,底层就会回调这个方法,当然前提是我们得先注册,这样底层才能找到我们 app 并回调。当这个方法被回调时,内部发起了一个 Message,注意看代码对这个 Message 设置了 callback 为 this,Handler 在处理消息时会先查看 Message 是否有 callback,有则优先交由 Message 的 callback 处理消息,没有的话再去看看Handler 有没有 callback,如果也没有才会交由 handleMessage() 这个方法执行。

这里这么做的原因,我猜测可能 onVsync() 是由底层回调的,那么它就不是运行在我们 app 的主线程上,毕竟上层 app 对底层是隐藏的。但这个 doFrame() 是个 ui 操作,它需要在主线程中执行,所以才通过 Handler 切到主线程中。

还记得我们前面分析scheduleTraversals() 方法时,最后跟到了一个 native 层方法就跟不下去了么,现在再回过来想想这个 native 层方法的作用是什么,应该就比较好猜测了。

大概是注册监听, 注册的监听应该只是监听下一个屏幕刷新信号的事件。比如说当前监听了第一帧的刷新信号事件,那么当第一帧的刷新信号来的时候,上层 app 就能接收到事件并作出反应。但如果还想监听第二帧的刷新信号,那么只能等上层 app 接收到第一帧的刷新信号之后再去监听下一帧。

小结一下
  • 我们知道一个 View 发起刷新的操作时,最终是走到了 ViewRootImpl 的 scheduleTraversals() 里去,然后这个方法会将遍历绘制 View 树的操作 performTraversals() 封装到 Runnable 里,传给 Choreographer,以当前的时间戳放进一个 mCallbackQueue 队列里,然后调用了 native 层的方法向底层注册监听下一个屏幕刷新信号事件。

-当下一个屏幕刷新信号发出的时候,如果我们 app 有对这个事件进行监听,那么底层它就会回调我们 app 层的 onVsync() 方法来通知。当 onVsync() 被回调时,会发一个 Message 到主线程,将后续的工作切到主线程来执行。

  • 切到主线程的工作就是去 mCallbackQueue 队列里根据时间戳将之前放进去的 Runnable 取出来执行,而这些 Runnable 有一个就是遍历绘制 View 树的操作 performTraversals()。在这次的遍历操作中,就会去绘制那些需要刷新的 View。

-所以说,当我们调用了 invalidate(),requestLayout(),等之类刷新界面的操作时,并不是马上就会执行这些刷新的操作,而是通过 ViewRootImpl 的 scheduleTraversals() 先向底层注册监听下一个屏幕刷新信号事件,然后等下一个屏幕刷新信号来的时候,才会去通过 performTraversals() 遍历绘制 View 树来执行这些刷新操作。

过滤一帧内重复的刷新请求

我们在一个 16.6ms 的一帧内,代码里可能会有多个 View 发起了刷新请求,这是非常常见的场景了,比如某个动画是有多个 View 一起完成,比如界面发生了滑动等等。

按照我们上面梳理的流程,只要 View 发起了刷新请求最终都会走到 ViewRootImpl 中的 scheduleTraversals() 里去。而这个方法又会封装一个遍历绘制 View 树的操作 performTraversals() 到 Runnable 然后扔到队列里等刷新信号来的时候取出来执行。

那如果多个 View 发起了刷新请求,岂不是意味着会有多次遍历绘制 View 树的操作?

当我们调用了一次 scheduleTraversals()之后,直到下一个屏幕刷新信号来的时候,doTraversal() 被取出来执行。在这期间重复调用 scheduleTraversals() 都会被过滤掉的。

postSyncBarrier()---同步屏障消息

我们清楚主线程其实是一直在处理 MessageQueue 消息队列里的 Message,每个操作都是一个 Message,打开 Activity 是一个 Message,遍历绘制 View 树来刷新屏幕也是一个 Message。

而且,上面梳理完我们也清楚,遍历绘制 View 树的操作是在屏幕刷新信号到的时候,底层回调我们 app 的 onVsync(),这个方法再去将遍历绘制 View 树的操作 post 到主线程的 MessageQueue 中去等待执行。主线程同一时间只能处理一个 Message,这些 Message 就肯定有先后的问题,那么会不会出现下面这种情况呢:


也就是说,当我们的 app 接收到屏幕刷新信号时,来不及第一时间就去执行刷新屏幕的操作,这样一来,即使我们将布局优化得很彻底,保证绘制当前 View 树不会超过 16ms,但如果不能第一时间优先处理绘制 View 的工作,那等 16.6 ms 过了,底层需要去切换下一帧的画面了,我们 app 却还没处理完,这样也照样会出现丢帧了吧。而且这种场景是非常有可能出现的吧,毕竟主线程需要处理的事肯定不仅仅是刷新屏幕的事而已,那么这个问题是怎么处理的呢?

所以我们继续回来看 scheduleTraversals():


这个同步屏障的作用可以理解成拦截同步消息的执行,主线程的 Looper 会一直循环调用 MessageQueue 的 next() 来取出队头的 Message 执行,当 Message 执行完后再去取下一个。当 next() 方法在取 Message 时发现队头是一个同步屏障的消息时,就会去遍历整个队列,只寻找设置了异步标志的消息,如果有找到异步消息,那么就取出这个异步消息来执行,否则就让 next() 方法陷入阻塞状态。如果 next() 方法陷入阻塞状态,那么主线程此时就是处于空闲状态的,也就是没在干任何事。所以,如果队头是一个同步屏障的消息的话,那么在它后面的所有同步消息就都被拦截住了,直到这个同步屏障消息被移除出队列,否则主线程就一直不会去处理同步屏幕后面的同步消息。

而所有消息默认都是同步消息,只有手动设置了异步标志,这个消息才会是异步消息。另外,同步屏障消息只能由内部来发送,这个接口并没有公开给我们使用。

最后,仔细看上面 Choreographer 里所有跟 message 有关的代码,你会发现,都手动设置了异步消息的标志,所以这些操作是不受到同步屏障影响的。这样做的原因可能就是为了尽可能保证上层 app 在接收到屏幕刷新信号时,可以在第一时间执行遍历绘制 View 树的工作。

因为主线程中如果有太多消息要执行,而这些消息又是根据时间戳进行排序,如果不加一个同步屏障的话,那么遍历绘制 View 树的工作就可能被迫延迟执行,因为它也需要排队,那么就有可能出现当一帧都快结束的时候才开始计算屏幕数据,那即使这次的计算少于 16.6ms,也同样会造成丢帧现象。

有了同步屏障消息的控制就能保证每次一接收到屏幕刷新信号就第一时间处理遍历绘制 View 树的工作么?

只能说,同步屏障是尽可能去做到,但并不能保证一定可以第一时间处理。因为,同步屏障是在 scheduleTraversals() 被调用时才发送到消息队列里的,也就是说,只有当某个 View 发起了刷新请求时,在这个时刻后面的同步消息才会被拦截掉。如果在 scheduleTraversals() 之前就发送到消息队列里的工作仍然会按顺序依次被取出来执行。

还有一个问题,

为什么在Activity.onResume中调用View.post(Runnable)方法可以获取到View的宽高?

这就涉及到dispatchAttachedToWindow方法。

这里先看一下performTraversals()方法的调用流程:

//========ViewRootImpl=========代码2.3.2
Choreographer mChoreographer;

public ViewRootImpl(...){
    ......
    mChoreographer = Choreographer.getInstance();
    ......
}

void scheduleTraversals() {
    ......
    mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL,
                mTraversalRunnable, null);
    ......
}

final class TraversalRunnable implements Runnable {  //Runnable ②
    @Override
    public void run() {
        doTraversal();
    }
}

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() {
    ......
    performTraversals();
    ......
}

View.post(Runnable runable1)为什么可以拿到View的宽高?
整个performTraversals()方法,是作为TraversalRunnable的一部分被封装成Message,被加入主线程的MessageQueue中的。
当执行到dispatchAttachedToWindow()时,它会再向主线程的MessageQueue中添加一个封装了runable1的Message。由于Looper.loop()取MessageQueue中的Message执行时是有顺序的,所以所以TraversalRunnable会先执行完毕,然后才会执行runable1,也就是说实际执行中,runable1中的view.getWidth()是发生在performMeasure() — performLayout() — performDraw()之后的。

472002-20191211115014217-1728873287.png

结论:performTraversals()方法中,dispatchAttachedToWindow()所产生的Runnable①,是在view绘制流程结束后才执行的。

参考:
屏幕刷新机制

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容