美文网首页源码分析
Android View绘制流程

Android View绘制流程

作者: Archer_J | 来源:发表于2021-08-24 17:37 被阅读0次

Window创建过程中了解到:
在WM创建了ViewRootImpl之后,ViewRootImpl执行了requestLayout操作
这个requestLayout就是整个绘制的起点

ViewRootImpl

子View调用requestLayout最终会调用ViewRootImpl.requestLayout进行绘制

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
        
    // 创建ViewRootImpl时的线程
    final Thread mThread;
    
    // 是否正在进行布局,即正在执行
    boolean mHandlingLayoutInLayoutRequest = false;
    
    // 是否已经请求了布局
    boolean mLayoutRequested;
    
    // 是否有待处理的布局请求
    public boolean mTraversalScheduled;
    
    // 是否正在进行布局传递 layout()
    private boolean mInLayout = false;
    
    // handle消息屏障标识
    int mTraversalBarrier;
    
    // VSync垂直信号
    final Choreographer mChoreographer;
    
    final View.AttachInfo mAttachInfo;
    
    public ViewRootImpl(Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) {
            
            ...
        mThread = Thread.currentThread();
        
        // 创建mAttachInfo
        mAttachInfo = new View.AttachInfo(.., this, mHandler, ..);
    
    }
    
   // 将Window添加到窗口管理器
   public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
            
       mView = view;
       // 屏幕输入事件
       mInputChannel = new InputChannel();
       ...
       
       // 在添加到窗口管理器之前请求一次布局
       // 以确保我们从系统接收任何其他事件之前进行重新布局。
       requestLayout();
       
       // Window的添加等操作
       mWindowSession.addToDisplayAsUser(mWindow, ...., mInputChannel, ...);
       
       ...
       
       // 将ViewRootImpl设置为DecorView的parent (继承ViewParent)
       view.assignParent(this);
       
       ...
   }  
        
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }  
    
    // 这边检测了当前线程是否与创建ViewRootImpl的线程一致
    // 不一致,则抛异常
    // 子线程创建ViewRootImpl(在子线程调用WindowManager.addView),则在当前线程就能更新ui?
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
    
    // 利用同步消息屏障 + Vsync信号机制 
    // 在下一个Vsync信号来之前阻止同步消息的执行
    // 在下一个Vsync信号来时执行刷新操作
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            //此字段保证同时间多次更改只会刷新一次,例如TextView连续两次setText(),也只会走一次绘制流程
            mTraversalScheduled = true;
            
            // 发送同步消息屏障,只有处理异步消息能够得到处理
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            
            // mChoreographer内部发送了一个异步消息
            // 在下一个Vsync信号来时,会执行异步消息执行
            // 在异步消息执行时,执行了mTraversalRunnable
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ...
        }
    }
    
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            // 移除同步屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            
            // 执行测量/布局/绘制
            performTraversals();
            
        }
    }
    
    private void performTraversals() {
        // DecorView
        final View host = mView;
        ....
        // 第一次调用
            if (mFirst) {
             // mAttachInfo 初始化
           ...
           host.dispatchAttachedToWindow(mAttachInfo, 0);
        }
        
        getRunQueue().executeActions(mAttachInfo.mHandler);
        
        // 当前是否有布局请求的副本
        boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
      
        if (layoutRequested) {
            // 清除状态
            mLayoutRequested = false;
        }
        
        boolean windowShouldResize = layoutRequested && ...
        
        if (mFirst || windowShouldResize ...) {
                
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                        // 测量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
            ...
            
            // 如果有设置权重,比如LinearLayout设置了weight,需要测量两次
            if (measureAgain) {
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
            ... 
                
        }
        
        // 布局
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        
        if (didLayout) {
        
            performLayout(lp, mWidth, mHeight);
            
            ...
        }
        
        // 绘制
        performDraw();
    }
    
    
    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
         
         mInLayout = true;
         
         final View host = mView;
         
         // 执行第一次布局
         host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
         
         mInLayout = false;
         
         // 布局完成后,检查是否有待处理的请求,有则处理(主要是布局期间调用requestLayout的操作)
         int numViewsRequestingLayout = mLayoutRequesters.size();
         if (numViewsRequestingLayout > 0) {
                
             mHandlingLayoutInLayoutRequest = true;
             
             // 这边主要是校验,请求requestLayout的View是否扔有效
             // 有几个条件:mParent不为null,设置了强制刷新标识PFLAG_FORCE_LAYOUT
             ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false);
             
             
             int numValidRequests = validLayoutRequesters.size();
             for (int i = 0; i < numValidRequests; ++i) {
             
                  final View view = validLayoutRequesters.get(i);
                       
                  view.requestLayout();
            }
            
            mInLayout = true;
            
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
                    
            mHandlingLayoutInLayoutRequest = false;
         }
         
         mInLayout = false;
    }
}
public class View implements ... {

        protected ViewParent mParent;
    
    AttachInfo mAttachInfo;
    
    private HandlerActionQueue mRunQueue;
    
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    
        mAttachInfo = info;
        ...
        // 如果调用post时,mAttachInfo为null,会将消息延迟到dispatchAttachedToWindow执行
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        ...
    }
        
    // 如果ViewRootImpl正在执行布局传递时,再次调用requestLayout()
    // 1.请求会延迟到当前布局请求完成后再次执行布局
    // 2.或者当前帧绘制完成后,下一次布局请求发起时执行
    @CallSuper
    public void requestLayout() {
    
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            ViewRootImpl viewRoot = getViewRootImpl();
            
            // 正在执行布局传递
            if (viewRoot != null && viewRoot.isInLayout()) {
                    // 如果正在执行延迟的布局请求,则当前请求再次延迟到下一次布局请求发起时执行
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }
        
        // 设置强制刷新标识,ViewTree重新测量布局(measure、layout)的时候
        // 会过滤没有设置这个标识的View
        // 相关代码在measure、layout方法中,这边就不贴了
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;
        
                // 递归调用mParent的requestLayout,最终调用到ViewRootImpl
        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
       
    }
    
    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");
        }
    }
    
}

1.requestLayout最终传递到ViewRootImpl,并且传递的时候,给路过的View设置强制刷新标识
2.ViewRootImpl使用同步屏障+Choreographer+异步消息,在下一个VSync信号执行刷新
3.ViewRootImpl会依次递归执行测量、布局、绘制
4.未设置强制刷新标识的View,不会执行测量、布局
5.在layout()递归期间,再次调用requestLayout,不会再次发起请求,会延迟到当前布局请求完成后再次执行布局;或者当前帧绘制完成后,下一次布局请求发起时执行

View.post

View.post为何能拿到View的宽高信息
1.拿到 View 内部的 mAttachInfo,拿到 mAttachInfo.handler,调用post将runnable插入消息队列
2.又下可知,mAttachInfo 在 ViewRootImpl 执行测量时初始化
3.此时,刚插入消息队列的message肯定会在测量完成后才会执行
5.所以,View.post 能拿到View的宽高信息

public class View implements ... {

    // 当视图附加到其父窗口时提供给视图的一组信息。
    AttachInfo mAttachInfo;
    
    private HandlerActionQueue mRunQueue;
    
    // 由于DecorView是一个ViewGroup,所以会将dispatchAttachedToWindow传递给每一个子View
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    
        mAttachInfo = info;
        ...
        // 如果调用post时,mAttachInfo为null,会将消息延迟到dispatchAttachedToWindow执行
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        ...
    }
    
    // 由上述 ViewRootImpl 可知,
    // mAttachInfo 在 ViewRootImpl 执行测量时,调用View.dispatchAttachedToWindow 传进来
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        
        // 如果 mAttachInfo 还未初始化,延迟到dispatchAttachedToWindow执行
        getRunQueue().post(action);
        return true;
     }
    
}

invalidate

最终调用的是ViewRootImp的scheduleTraversals()
由于mLayoutRequested为false,所以不会执行测量和布局
硬件绘制和软件绘制具有不一样的调用流程

绘制

前面经过View的测量、布局,收集到了绘制需要的信息之后,需要将其绘制到屏幕上,这里需要区分软件绘制和硬件绘制

这里就不细讲了,主要参考:
屏幕刷新机制
关于UI渲染,你需要了解什么
RenderThread:异步渲染动画

相关文章

网友评论

    本文标题:Android View绘制流程

    本文链接:https://www.haomeiwen.com/subject/ocodiltx.html