Android View 绘制流程 过程分析

本文详细分析了Android View的绘制流程,从如何获取View的宽高开始,逐步探讨了从attach()方法、setContentView()方法到ActivityThread的handleResumeActivity()方法,再到ViewRootImpl类和Choreographer的页面刷新机制。最后总结了View绘制开始的整个过程,包括performTraversals()和performMeasure()、performLayout()、performDraw()等关键步骤。

前言

今天整理了一下Android View的绘制流程,记录一下


一、如何获取View的宽高

我们经常会遇到要获取控件宽高的情况,可以通过以下两种方式来获取。

1. 通过View的post()

	MFragmentTabView tabView = findViewById(R.id.fragment_tab_view);
	tabView.post(new Runnable() {
	    @Override
	    public void run() {
	        tabView.getWidth();
	        tabView.getHeight();
	    }
	});

2. 通过ViewTreeObserver

       MFragmentTabView tabView = findViewById(R.id.fragment_tab_view);
       tabView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
           @Override
           public void onGlobalLayout() {
               tabView.getWidth();
               tabView.getHeight();
           }
       });

二、View真正开始绘制是从哪里开始的呢

我们知道,在Activity的onCreate(),onResume() 中,都无法通过View的getHeight()、getWidth() 获取真正的宽高,说明此时还没有进行完View的绘制流程,那么View的绘制到底是从哪里开始的呢,我们从Activity的启动流程里已经知道,Activity生命周期被执行的第一个方法是attach().

1. 检查attach()方法

	final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
		......
		//实例化Window对象,并设置各种回调方法
        mWindow = new PhoneWindow(this, window, activityConfigCallback); 
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ......
    }

这里并没有关于View绘制的内容,只是创建了Window接口的唯一实现类PhoneWindow的对象实例,还有一些入参的赋值操作。然后我们接下来按照声明周期,去看onCreate()方法,发现onCreate()方法里也没有View绘制的方法,但是我们会在onCreate()回调中调用setContentView()方法,传入我们的布局文件。因为从Android21以后出现了AppCompatActivity来支持Meterial Design等特性,我们现在的Activity都是继承自AppCompatActivity,然后我们会发现setContentView方法最后是在 AppCompatDelegateImpl中实现

2. 检查setContentView()方法

@Override
public void setContentView(View v) {
    //生成DecorView的子View
    ensureSubDecor();
    //找到R.id.content控件,并清空子View
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    //将我们activity中设置的xml文件转成的View放到R.id.content控件中
    contentParent.addView(v);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

进入到ensureSubDecor()方法中,发现是通过createSubDecor()创建的SubDector,

mSubDecor = createSubDecor();

在createSubDecor() 方法中,

private ViewGroup createSubDecor() {
    // 此处省略了根据主题Theam设置Window feature代码
    .......
    
    // 确保创建了Window对象,并将Window和Activity关联起来
    ensureWindow();
    // 创建DecorView及其子View contentParent,并关联到Window
    mWindow.getDecorView();

    // 此处省略了根据不同条件,给subDecor赋值不同的布局文件,生成SubDecor对象
    .......
   
    // 获取SubDecor中R.id.action_bar_activity_content控件
    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
            R.id.action_bar_activity_content);
    // 获取DecorView中R.id.content控件
    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
        // 将DecorView中content控件的子View移除并添加到SubDecor的content控件中
        while (windowContentView.getChildCount() > 0) {
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }
        windowContentView.setId(View.NO_ID);
        //将R.id.content赋值给SubDecor的content控件
        contentView.setId(android.R.id.content);
        if (windowContentView instanceof FrameLayout) {
            ((FrameLayout) windowContentView).setForeground(null);
        }
    }

    //将SubDecor设置到Window中->将subDecor设置为DecorView的子View
    mWindow.setContentView(subDecor);
	.......
    return subDecor;
}

关于DecorView的创建,从PhoneWindow的getDecorView中,进入到installDecor()方法中,

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        //生成DecorView并关联Window
        mDecor = generateDecor(-1);
        ......
    }
    if (mContentParent == null) {
        //生成DecorView的子View
        mContentParent = generateLayout(mDecor);
        ......
    }
}

在 generateDecor()中,会new一个DecorView对象,然后我们看一下generateLayout()如何生成mContentParent对象

protected ViewGroup generateLayout(DecorView decor) {
	//此处省略了各种设置flag和feature的方法
    ......

    //此处省略了生成布局id的代码 
    ......
    
    //实例化上面确定的布局,并将该布局添加到DecorView里
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    //从DecorView中找到R.id.content控件
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	......
    return contentParent;
}

再解释一下mWindow.setContentView(subDecor)方法的作用

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        //清空View
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        .......
    } else {
        //将SubDecor添加到mContentParent中
        mContentParent.addView(view, params);
    }
    ......
}

至此,setContentView()方法介绍完了,但是也没有找到开始绘制的源头,根据声明周期,我们去查onResume()方法,通过之前的启动Activity的文章,我们知道Activity的onResume是在ActivityThread的handleResumeActivity()中调用的,源码如下

3. 检查ActivityThread的handleResumeActivity()方法

ActivityThread.java类

@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, String reason) {
    ......

    //调用onResume()
    if (!performResumeActivity(r, finalStateRequest, reason)) {
        return;
    }
    ......

    final Activity a = r.activity;
    ......
    
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        //通过PhoneWindow获取DecorView
        View decor = r.window.getDecorView();
        //先隐藏,防止谈起软件盘
        decor.setVisibility(View.INVISIBLE);
        //获取WindowManager对象
        ViewManager wm = a.getWindowManager();
        ......
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                //将DecorView添加到WindowManager中
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    }
    ......
}

然后我们在WindowManager的实现类WindowManagerImpl中,发现addView()方法委托给WindowManagerGlobal类来实现,

WindowManagerGlobal.java类
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
    //省略检测代码
    ......
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
        ......
        //创建ViewRootImpl对象
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        try {
            //将View和ViewRootImpl关联起来
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {
            ......
        }
    }
}

到此,终于到了我们View绘制最关键的类ViewRootImpl了

4. 检查ViewRootImpl类

先看一下ViewRootImpl的构造函数中几个重要的数据

public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
        boolean useSfChoreographer) {
    ......
    //IWindowSession对象,是一个IBinder接口,客户端通过WindowSession操作WMS
    mWindowSession = session; 
    ......
    //View关联到Window上的信息,每个View都有一个AttachInfo对象
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
            context);
    ......
    //接收系统硬件发出的Vsync垂直同步信号,例如60Hz的屏幕,16.7ms发出一次信号,
    //通知页面进行View绘制、手势操作、View动画等操作 
    mChoreographer = useSfChoreographer
            ? Choreographer.getSfInstance() : Choreographer.getInstance();
    ......
}

接下来我们看一下setView()方法

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    synchronized (this) {
        if (mView == null) {
			mView = view;
			......
            // Schedule the first layout -before- adding to the window
            // manager, to make sure we do the relayout before receiving
            // any other events from the system.
            // 真正开启三大流程的地方
            requestLayout(); 
            // 输入监听频道,mWindowSession.addToDisplayAsUser的参数,因此ViewRootImpl也是输入监听的开始类
            InputChannel inputChannel = null;
            if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                inputChannel = new InputChannel();
            }
            ......
            try {
                ......
                //通过WindowSession将window注册到WMS
                res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), userId,
                        mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
                        mTempControls);
                ......
            } 
            ......
            if (inputChannel != null) {
                if (mInputQueueCallback != null) {
                    mInputQueue = new InputQueue();
                    mInputQueueCallback.onInputQueueCreated(mInputQueue);
                }
                //系统输入事件的监听 
                mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                        Looper.myLooper());
				......
            }
            //ViewRootImpl是最顶层的父节点,但不是View的子类,而是实现了ViewParent接口
            view.assignParent(this);
            ......
        }
    }
}

接下来我们看一下requestLayout()方法

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        //检查线程
        checkThread();
        mLayoutRequested = true;
        //执行遍历
        scheduleTraversals();
    }
}

此时有一个问题,子线程中真的不能操作View吗,答案是可以操作,但是必须要在ViewRootImpl调用checkThread()方法之前,就不会报错。
接下来我们看scheduleTraversals()方法

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        //标志位,防止同一帧的情况下执行多次
        mTraversalScheduled = true;
        //发送消息屏障,为了让Handler的异步消息先执行,从而使ViewRootImpl的measure、layout、draw方法优先执行
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); 
        //发送回调消息,类型是遍历,用来执行三大流程的
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

5. Choreographer 页面刷新机制介绍

我们发现mChoreographer发送了一个CALLBACK_TRAVERSAL类型的回调。让我们看一下Choreographer这个类,先看一下源码注释

/**
 * Coordinates the timing of animations, input and drawing.
 * <p>
 * The choreographer receives timing pulses (such as vertical synchronization)
 * from the display subsystem then schedules work to occur as part of rendering
 * the next display frame.
 */

Choreographer这个类是用来协调动画、用户输入和绘制的,它可以接收显示系统发出的时间脉冲,例如垂直同步信号,来安排下一帧的工作。同时可以过滤同一帧内的重复操作。

//构造方法
private Choreographer(Looper looper, int vsyncSource) {
    mLooper = looper;
    mHandler = new FrameHandler(looper);
    //向native层注册监听,监听vsync信号,当有新的vsync信号时,
    //会执行FrameDisplayEventReceiver的onVsync()方法
    mDisplayEventReceiver = USE_VSYNC
            ? new FrameDisplayEventReceiver(looper, vsyncSource)
            : null;
    ......
    //用来记录各种Callback类型的数组,CallBackQueen是一个由CallbackRecord组成的链表
    mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
    for (int i = 0; i <= CALLBACK_LAST; i++) {
        mCallbackQueues[i] = new CallbackQueue();
    }
    ......
}

然后我们看之前调用的postCallback()方法,发现又调用了postCallbackDelayed()方法、postCallbackDelayedInternal()方法

public void postCallback(int callbackType, Runnable action, Object token) {
    postCallbackDelayed(callbackType, action, token, 0);
}

public void postCallbackDelayed(int callbackType,
        Runnable action, Object token, long delayMillis) {
    if (action == null) {
        throw new IllegalArgumentException("action must not be null");
    }
    if (callbackType < 0 || callbackType > CALLBACK_LAST) {
        throw new IllegalArgumentException("callbackType is invalid");
    }
    postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}

private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
	......
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        //添加Callback记录,封装成CallbackRecord对象
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        //判断是否现在执行
        if (dueTime <= now) {
            //立即执行
            scheduleFrameLocked(now);
        } else {
            //发送异步消息,执行Callback
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}

现在callback分成2种状态,一种是立即操作,一种是通过异步消息执行

1. 立即操作
private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) {
        //标记位,防止多次执行
        mFrameScheduled = true;
        if (USE_VSYNC) { //Android 4.1 以后默认为true
            ......
            // If running on the Looper thread, then schedule the vsync immediately,
            // otherwise post a message to schedule the vsync from the UI thread
            // as soon as possible.
            //判断当前Looper是创建Choreographer对象线程的Looper
            if (isRunningOnLooperThreadLocked()) { 
                //发送Vsync信号
                scheduleVsyncLocked();
            } else {
                //发送异步消息,执行发送Vsync信号
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtFrontOfQueue(msg);
            }
        } else {
            ......
        }
    }
}
2. 异步消息Handler处理
private final class FrameHandler extends Handler {
    public FrameHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_DO_FRAME:
                //执行这一帧的操作
                doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());
                break;
            case MSG_DO_SCHEDULE_VSYNC:
                //发送Vsync信号
                doScheduleVsync();
                break;
            case MSG_DO_SCHEDULE_CALLBACK:
                //执行上面的 1.立即操作
                doScheduleCallback(msg.arg1);
                break;
        }
    }
}

现在我们知道,无论是立即操作,还是发送异步消息处理,最后都是操作了一个发送Vsync信号的操作,这时我们在Choreographer构造方法中创建的mDisplayEventReceiver对象,就会被触发 onVsync()方法,

FrameDisplayEventReceiver@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
        VsyncEventData vsyncEventData) {
    try {
        ......
        //发送了异步消息,注意,Message的创建方法参数二 Runnable 传了 this,
        //根据Handler特性,当执行到这条消息时,handle方法执行的是Runnable的run方法,见下方
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

@Override
public void run() {
    mHavePendingVsync = false;
    //执行这一帧的操作
    doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}

然后我们来看doFrame方法,发现4中类型的callback的执行是有优先级的, input > animation > traversal > commit

void doFrame(long frameTimeNanos, int frame,
        DisplayEventReceiver.VsyncEventData vsyncEventData) {
    final long startNanos;
    final long frameIntervalNanos = vsyncEventData.frameInterval;
    try {
        ......
        
        mFrameInfo.markInputHandlingStart();
        //执行输入类型callback
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);

        mFrameInfo.markAnimationsStart();
        //执行动画类型callback
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
                frameIntervalNanos);

        mFrameInfo.markPerformTraversalsStart();
        //执行遍历绘制类型callback
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
        //执行提交类型callback
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
    } finally {
        AnimationUtils.unlockAnimationClock();
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    ......
}

接下来看一下 doCallbacks方法,发现最终,执行的是CallbackQueen里每一个CallbackRecrod的run方法。

void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
    CallbackRecord callbacks;
    ......
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
        for (CallbackRecord c = callbacks; c != null; c = c.next) {
            //遍历执行了CallbackRecord对象的run方法
            c.run(frameTimeNanos);
        }
    } finally {
        ......
    }
}
CallbackRecordprivate static final class CallbackRecord {
    public CallbackRecord next;
    public long dueTime;
    public Object action; // Runnable or FrameCallback
    public Object token;

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void run(long frameTimeNanos) {
        if (token == FRAME_CALLBACK_TOKEN) {
            ((FrameCallback)action).doFrame(frameTimeNanos);
        } else {
            // 这是的action是在ViewRootImpl类中scheduleTraversals方法里,
            // mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 传入的 mTraversalRunnable对象
            ((Runnable)action).run();
        }
    }
}

6. 最终执行了doTraversal()

在mTraversalRunnable的run中执行了 doTraversal()方法,如下

void doTraversal() {
    if (mTraversalScheduled) {
        //取消标记
        mTraversalScheduled = false;
        //移除屏障消息,因为已经要开始进行三大流程了
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
		......
        //执行遍历
        performTraversals();
        ......
    }
}

然后我们会发现performTraversals()是一个近800行的方法,这个方法主要就是用来计算是否需要执行三大流程的

//执行测量
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        //执行View的measure方法
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

//执行布局
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
	......
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try {
        //执行View的layout方法
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        .......
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

//执行绘制
private void performDraw() {
	......
	//调用draw()
	draw(fullRedrawNeeded);
	......
}
private boolean draw(boolean fullRedrawNeeded) {
	......
	//调用drawSoftware()
	drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets);
	......
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
     boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
	......
	//执行View的draw()
	mView.draw(canvas);
	......
}

至此,我们终于捋清了View的三大流程是如何被一步步执行的。

三、总结一下

总结一下View的绘制是怎么开始的

  1. ActivityThread的handleResumeActivity()
  2. WindowManager的addView()
  3. ViewRootImpl的setView() -> requestLayout() -> scheduleTraversals()
  4. Choreographer的postCallback() ->scheduleFrameLocked()发送Vsync信号
  5. FrameDisplayEventReceiver的onVsync()接收Vsync信号发送异步消息 -> run()
  6. ViewRootImpl 的doTraversal() -> performTraversals() -> performMeasure() performLayout() performDraw()

个人理解,如有问题,请指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ByeMoon丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值