UI绘制流程及原理

作者: migill | 来源:发表于2019-10-10 18:17 被阅读0次

    1、View是如何被添加到屏幕窗口上

    1、创建顶层布局容器DecorView
    2、在顶层布局中加载基础布局ViewGroup
    3、将ContentView添加到基础布局中的FrameLayout中

    从我们的activity的onCreate()中的setContentView开始



    跳到了Window类中的setContentView方法,从描述The only existing implementation of this abstract class is android.view.PhoneWindow我们知道PhoneWindow是Window的唯一实现类。



    PhoneWindow中的setContentView方法主要做了两件事情

    第一件事情:


    generateLayout这个方法比较长,主要有三个步骤:
    1、通过Window Style样式,设置Window风格
    2、加载系统中对应的layoutResource,作为基础布局(基础容器),根据不同的features,初始化不同的布局。如:layoutResource = R.layout.screen_simple;(开发者自己通过setContentView(int layoutId)设置的布局文件就是添加到其中的)
    3、当资源加载完毕,会调用DecorView的onresourcesLoaded()方法,进而将上述基础布局添加到顶层布局mDecorView中。

    第二件事情:



    布局层次如下图:


    2、将View树添加到Window中

    ActivityThread是Activity的启动入口, 在它有一个内部类H继承Handler,专门用来处理主线程的消息。

    class H extends Handler {
    
        public void handleMessage(Message msg) {
    
            switch (msg.what) {
    
                case LAUNCH_ACTIVITY: {
    
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
    
                    r.packageInfo = getPackageInfoNoCheck(
    
                    r.activityInfo.applicationInfo, r.compatInfo);
    
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
    
            }
    
            break;
    

    启动一个Activity,会触发handleLaunchActivity()方法

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    
    ......
    
    // 1、创建Activity
    
    Activity a = performLaunchActivity(r, customIntent);
    
    ......
    
    // 2、调用activity的onResume方法
    
    handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
    
    我们先看一下performLaunchActivity()方法的源码:
    
    private ActivityperformLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    
        Activity activity =null;
    
        try {
    
                java.lang.ClassLoader cl = appContext.getClassLoader();
    
                // 1、创建Activity
    
                activity =mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    
             } catch (Exception e) {
    
             }
    
        // 2、创建Application
    
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    
        // 3、创建Window
    
        Window window =null;
    
        if (r.mPendingRemoveWindow !=null && r.mPreserveWindow) {
    
                window = r.mPendingRemoveWindow;
    
                r.mPendingRemoveWindow =null;
    
                r.mPendingRemoveWindowManager =null;
    
        }
    
        // 4、关联activity和window
    
        activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID,                     r.lastNonConfigurationInstances, config,  r.referrer, r.voiceInteractor, window, r.configCallback);
    

    通过源码,可以看到,performLaunchActivity()方法主要是创建Activity、Application、Window对象,并关联到一起。紧接着,调用activity的onCreate()方法,进而调用setContentView()方法,构建View树。

    然后我们再看一下handleResumeActivity()方法的源码:

    final void handleResumeActivity(IBinder token,  boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {            
     // 1、创建 ViewManager 对象
    
            ViewManager wm = a.getWindowManager();
    
            WindowManager.LayoutParams l = r.window.getAttributes();
    
            if (a.mVisibleFromClient) {
    
                if (!a.mWindowAdded) {
    
                        a.mWindowAdded =true;
    
                        // 2、将DecorView添加到Window中
    
                        wm.addView(decor, l);
    
            }else {
    
                a.onWindowAttributesChanged(l);
    
            }
    
    }
    

    在该方法内部,创建ViewManager对象,并调用addView()方法,将DecorView添加到Window中。(通过查看源码,ViewManager.addView() --> WindowManager.addView() --> WindowManagerGlobal.addView(),这不是本文的重点,就不一一列举了)

    至此,View树就已经添加到Window中,但是此时用户还看不到界面,因为还没有进行绘制。所以接下来就需要将该View绘制出来

    3、View的绘制流程
    我们看一下WindowManagerGlobal.addView(),在该方法内部会调用ViewRootImpl的setView()方法,在该方法内部,会调用requestLayout()方法,进而出发View的绘制流程:

    public void requestLayout() {
    
        if (!mHandlingLayoutInLayoutRequest) {
    
                // 1、检查是否在主线程
    
                checkThread();
    
                // 2、执行traversal
    
                scheduleTraversals();
    
        }
    
    }
    
    void scheduleTraversals() {
    
            if (!mTraversalScheduled) {
    
                    mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    
            }
    
    }
    
    
    final TraversalRunnablemTraversalRunnable =new TraversalRunnable();
    
    final class TraversalRunnableimplements Runnable {
    
        @Override
    
        public void run() {
    
            doTraversal();
    
        }
    
    }
    
    void doTraversal() {
    
        if (mTraversalScheduled) {
    
                performTraversals();
    
            }
    
        }
    
    }
    
    private void performTraversals() {
    
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
            performLayout(lp, mWidth, mHeight);
    
            performDraw();
    }
    

    从源码中,可以看到,最终在performTraversals()方法中,执行了View的测量、布局和绘制过程。

    至此,View绘制完毕后,就可以显示到屏幕上了。

    MeasureSpec

    MeasureSpec 表示的是一个 32 位的整数值,它的高 2 位表示测量模式 SpecMode,低 30 位表示某种测量模式下的规格大小 SpecSize。MeasureSpec 是 View 类的一个静态内部类,用来说明应该如何测量这个View。
    三种测量模式。

    UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。
    EXACTLY:精确测量模式,当该视图的 layout_width 或者 layout_height 指定为具体数值或者 match_parent 时生效,表示父视图已经决定了子视图的精确大小,这种模式下 View 的测量值就是 SpecSize 的值。
    AT_MOST:最大值模式,当前视图的 layout_width 或者 layout_height 指定为 wrap_content 时生效,此时子视图的尺寸可以是不超过父视图运行的最大尺寸的任何尺寸。
    对 DecorView 而言,它的 MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同决定;对于普通的 View,它的 MeasureSpec 由父视图的 MeasureSpec 和其本身的 LayoutParams 共同决定。

    //父容器不对View做任何限制,系统内部使用
     public static final int UNSPECIFIED = 0 << MODE_SHIFT;//00000000000000000000000000000000
    //父容器检测出View的大小,Vew的大小就是SpecSize LayoutPamras match_parent 固定大小       
    public static final int EXACTLY     = 1 << MODE_SHIFT;//01000000000000000000000000000000
    //父容器指定一个可用大小,View的大小不能超过这个值,LayoutPamras wrap_content    
    public static final int AT_MOST     = 2 << MODE_SHIFT; //10000000000000000000000000000000
    

    Measure

    Measure 用来计算 View 的实际大小。页面的测量流程从 performMeasure 方法开始。

      private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
      }
    
    

    具体操作是分发给 ViewGroup 的,由 ViewGroup 在它的 measureChild 方法中传递给子 View。ViewGroup 通过遍历自身所有的子 View,并逐个调用子 View 的 measure 方法实现测量操作。

      // 遍历测量 ViewGroup 中所有的 View
      protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
      }
    
      // 测量某个指定的 View
      protected void measureChild(View child, int parentWidthMeasureSpec,
                int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
    
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                    mPaddingTop + mPaddingBottom, lp.height);
    
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
      }
    
    

    View (ViewGroup) 的 Measure 方法,最终的测量是通过回调 onMeasure 方法实现的,这个通常由 View 的特定子类自己实现,可以通过重写这个方法实现自定义 View。

      public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ....
      }
    
      // 如果需要自定义测量,子类需重写这个方法
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
      }
    
      // 如果 View 没有重写onMeasure 方法,默认会直接调用 getDefaultSize
       public static int getDefaultSize(int size, int measureSpec) {
         int result = size;
         int specMode = MeasureSpec.getMode(measureSpec);
         int specSize = MeasureSpec.getSize(measureSpec);
    
         switch (specMode) {
           case MeasureSpec.UNSPECIFIED:
              result = size;
              break;
           case MeasureSpec.AT_MOST:
           case MeasureSpec.EXACTLY:
              result = specSize;
              break;
          }
          return result;
       }
    
    

    ViewGroup measure --> onMeasure(测量子控件的宽高)--> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的宽高)
    View measure --> onMeasure --> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的宽高)

    Layout

    Layout 过程用来确定 View 在父容器的布局位置,他是父容器获取子 View 的位置参数后,调用子 View 的 layout 方法并将位置参数传入实现的。ViewRootImpl 的 performLayout 代码如下。
    ViewGroup layout(来确定自己的位置,4个点的位置) -->onLayout(进行子View的布局)
    View layout(来确定自己的位置,4个点的位置)

      private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                int desiredWindowHeight) {
        ...
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ...
      }
    
    

    View 的 layout 方法代码。

      public void layout(int l, int t, int r, int b) {
        onLayout(changed, l, t, r, b);
      }
    
      // 空方法,子类如果是 ViewGroup 类型,则重写这个方法,实现 ViewGroup 中所有 View 控件布局
      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
      }
    
    

    Draw

    ViewGroup
    绘制背景 drawBackground(canvas)
    绘制自己onDraw(canvas)
    绘制子View dispatchDraw(canvas)
    绘制前景,滚动条等装饰onDrawForeground(canvas)

    View
    绘制背景 drawBackground(canvas)
    绘制自己onDraw(canvas)
    绘制前景,滚动条等装饰onDrawForeground(canvas)
    onMeasure --> onLayout(容器) --> onDraw(可选)

    Draw 操作用来将控件绘制出来,绘制的流程从 performDraw 方法开始。performDraw 方法在类 ViewRootImpl 内,其核心代码如下。

      private void performDraw() {
        boolean canUseAsync = draw(fullRedrawNeeded);
      }
    
      private boolean draw(boolean fullRedrawNeeded) {
        ...
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                            scalingRequired, dirty, surfaceInsets)) {
          return false;
        }
      }
    
      private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
         ...
         mView.draw(canvas);
         ...
      }
    
    

    最终调用到每个 View 的 draw 方法绘制每个具体的 View,绘制基本上可以分为六个步骤。

      public void draw(Canvas canvas) {
        ...
        // Step 1, draw the background, if needed
        if (!dirtyOpaque) {
          drawBackground(canvas);
        }
        ...
        // Step 2, save the canvas' layers
        saveCount = canvas.getSaveCount();
        ...
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);
    
        // Step 4, draw the children
        dispatchDraw(canvas);
    
        // Step 5, draw the fade effect and restore layers
        canvas.drawRect(left, top, right, top + length, p);
        ...
        canvas.restoreToCount(saveCount);
        ...
        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
      }
    
    

    参考
    UI绘制流程及原理
    Android View 的绘制流程

    相关文章

      网友评论

        本文标题:UI绘制流程及原理

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