Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析(上)

简介:

   在前面一篇文章中,我们分析了Android应用程序窗口的绘图表面的创建过程。Android应用程序窗口的绘图表面在创建完成之后,我们就可以从上到下地绘制它里面的各个视图了,即各个UI元素了。不过在绘制这些UI元素之前,我们还需要从上到下地测量它们实际所需要的大小,以及对它们的位置进行合适的安排,即对它们进行合适的布局。在本文中,我们就将详细地分析Android应用程序窗口的测量、布局以及绘制过程。

       从前面Android应用程序与SurfaceFlinger服务的关系概述和学习计划这一系列的文章可以知道,Android应用程序窗口请求SurfaceFlinger服务创建了一个绘图表面之后,就可以接着请求为该绘图表面创建图形缓冲区,而当Android应用程序窗口往这些图形缓冲区填充好UI数据之后,就可以请求SurfaceFlinger服务将它们渲染到硬件帧缓冲区中去,这样我们就可以看到应用程序窗口的UI了。

       Android应用程序窗口一般不会直接去操作分配给它的图形缓冲区,而是通过一些图形库API来操作。例如,在前面Android系统的开机画面显示过程分析一文中,使用C++来开发的开机动画应用程序bootanimation,它是通过OpenGL提供的API来绘制UI的。对于使用Java来开发的Android应用程序来说,它们一般是使用Skia图形库提供的API来绘制UI的。在Skia图库中,所有的UI都是绘制在画布(Canvas)上的,因此,Android应用程序窗口需要将它的图形缓冲区封装在一块画布里面,然后才可以使用Skia库提供的API来绘制UI。

       我们知道,一个Android应用程序窗口里面包含了很多UI元素,这些UI元素是以树形结构来组织的,即它们存在着父子关系,其中,子UI元素位于父UI元素里面,因此,在绘制一个Android应用程序窗口的UI之前,我们首先要确定它里面的各个子UI元素在父UI元素里面的大小以及位置。确定各个子UI元素在父UI元素里面的大小以及位置的过程又称为测量过程和布局过程。因此,Android应用程序窗口的UI渲染过程可以分为测量、布局和绘制三个阶段,如图1所示:

%22

图1 Android应用程序窗口渲染三步曲

       从前面Android应用程序窗口(Activity)的视图对象(View)的创建过程分析一文可以知道,Android应用程序窗口的顶层视图是一个类型为DecorView的UI元素,而从前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一文的Step 3又可以知道,这个顶层视图最终是由ViewRoot类的成员函数performTraversals来启动测量、布局和绘制操作的,这三个操作分别由DecorView类的成员函数measure和layout以及ViewRoot类的成员函数draw来实现的。

       接下来,我们就分别从DecorView类的成员函数measure和layout以及ViewRoot类的成员函数draw开始,分析Android应用程序窗口的测量、布局和绘制过程。

       1. Android应用程序窗口的测量过程

       DecorView类的成员函数measure是从父类View继承下来的,因此,我们就从View类的成员函数measure开始分析应用程序窗口的测量过程,如图2所示:

%22

图2 Android应用程序窗口的测量过程

       这个过程可以分为3个步骤,接下来我们就详细分析每一个步骤。

       Step 1. View.measure

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
public  class  View  implements  Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     ......
     int  mPrivateFlags;
     ......
     int  mOldWidthMeasureSpec = Integer.MIN_VALUE;
     ......
     int  mOldHeightMeasureSpec = Integer.MIN_VALUE;
     ......
     public  final  void  measure( int  widthMeasureSpec,  int  heightMeasureSpec) {
         if  ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                 widthMeasureSpec != mOldWidthMeasureSpec ||
                 heightMeasureSpec != mOldHeightMeasureSpec) {
             // first clears the measured dimension flag
             mPrivateFlags &= ~MEASURED_DIMENSION_SET;
             ......
             // measure ourselves, this should set the measured dimension flag back
             onMeasure(widthMeasureSpec, heightMeasureSpec);
             // flag not set, setMeasuredDimension() was not invoked, we raise
             // an exception to warn the developer
             if  ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
                 throw  new  IllegalStateException(\\\"onMeasure() did not set the\\\"
                         + \\\" measured dimension by calling\\\"
                         + \\\" setMeasuredDimension()\\\");
             }
             mPrivateFlags |= LAYOUT_REQUIRED;
         }
         mOldWidthMeasureSpec = widthMeasureSpec;
         mOldHeightMeasureSpec = heightMeasureSpec;
     }
     ......
}

       这个函数定义在文件frameworks/base/core/java/android/view/View.java中。


       参数widthMeasureSpec和heightMeasureSpec用来描述当前正在处理的视图可以获得的最大宽度和高度。对于应用程序窗口的顶层视图来说,我们也可以认为这两个参数是用来描述应用程序窗口的宽度和高度。

       ViewRoot类的成员变量mPrivateFlags的类型为int,如果它的某一个位的值不等于0,那么就隐含着当前视图有一个相应的操作在等待执行中。ViewRoot类的另外两个成员变量mOldWidthMeasureSpec和mOldHeightMeasureSpec用来保存当前视图上一次可以获得的最大宽度和高度。

       当ViewRoot类的成员变量mPrivateFlags的FORCE_LAYOUT位不等于0时,就表示当前视图正在请求执行一次布局操作,这时候函数就需要重新测量当前视图的宽度和高度。此外,当参数widthMeasureSpec和heightMeasureSpec的值不等于ViewRoot类的成员变量mldWidthMeasureSpec和mOldHeightMeasureSpec的值时,就表示当前视图上一次可以获得的最大宽度和高度已经失效了,这时候函数也需要重新测量当前视图的宽度和高度。

       当View类的成员函数measure决定要重新测量当前视图的宽度和高度之后,它就会首先将成员变量mPrivateFlags的MEASURED_DIMENSION_SET位设置为0,接着再调用另外一个成员函数onMeasure来真正执行测量宽度和高度的操作。View类的成员函数onMeasure执行完成之后,需要再调用另外一个成员函数setMeasuredDimension来将测量好的宽度和高度设置到View类的成员变量mMeasuredWidth和mMeasuredHeight中,并且将成员变量mPrivateFlags的EASURED_DIMENSION_SET位设置为1。这个操作是强制的,因为当前视图最终就是通过View类的成员变量mMeasuredWidth和mMeasuredHeight来获得它的宽度和高度的。为了保证这个操作是强制的,View类的成员函数measure再接下来就会检查成员变量mPrivateFlags的EASURED_DIMENSION_SET位是否被设置为1了。如果不是的话,那么就会抛出一个类型为IllegalStateException的异常来。

       View类的成员函数measure最后就会把参数widthMeasureSpec和heightMeasureSpec的值保存在成员变量mldWidthMeasureSpec和mOldHeightMeasureSpec中,以便可以记录当前视图上一次可以获得的最大宽度和高度。

       View类的成员函数onMeasure一般是由其子类来重写的。例如,对于用来应用程序窗口的顶层视图的DecorView类来说,它是通过父类FrameLayout来重写祖父类View的成员函数onMeasure的。因此,接下来我们就分析FrameLayout类的成员函数onMeasure的实现。

       Step 2. rameLayout.onMeasure

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
public  class  FrameLayout  extends  ViewGroup {
     ......
     @Override
     protected  void  onMeasure( int  widthMeasureSpec,  int  heightMeasureSpec) {
         final  int  count = getChildCount();
         int  maxHeight =  0 ;
         int  maxWidth =  0 ;
         // Find rightmost and bottommost child
         for  ( int  i =  0 ; i < count; i++) {
             final  View child = getChildAt(i);
             if  (mMeasureAllChildren || child.getVisibility() != GONE) {
                 measureChildWithMargins(child, widthMeasureSpec,  0 , heightMeasureSpec,  0 );
                 maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
                 maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
             }
         }
         // Account for padding too
         maxWidth += mPaddingLeft + mPaddingRight + mForegroundPaddingLeft + mForegroundPaddingRight;
         maxHeight += mPaddingTop + mPaddingBottom + mForegroundPaddingTop + mForegroundPaddingBottom;
         // Check against our minimum height and width
         maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
         // Check against our foreground\\\'s minimum height and width
         final  Drawable drawable = getForeground();
         if  (drawable !=  null ) {
             maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
             maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
         }
         setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
                 resolveSize(maxHeight, heightMeasureSpec));
     }
     ......
}

       这个函数定义在文件frameworks/base/core/java/android/widget/FrameLayout.java中。


       FrameLayout类是从ViewGroup类继承下来的,后者用来描述一个视图容器,它有一个类型为View的数组mChildren,里面保存的就是它的各个子视图。ViewGroup类所供了两个成员函数getChildCount和getChildAt,它们分别用来获得一个视图容器所包含的子视图的个数,以及获得每一个子视图。

       FrameLayout类的成员函数onMeasure首先是调用另一个成员函数measureChildWithMargins来测量每一个子视图的宽度和高度,并且找到这些子视图的最大宽度和高度值,保存在变量maxWidth和maxHeight 中。

       FrameLayout类的成员函数onMeasure接着再将前面得到的宽度maxWidth和高度maxHeight分别加上当前视图所设置的Padding值,其中,(mPaddingLeft,mPaddingRight,mPaddingTop,mPaddingBottom )表示当前视图的内容区域的左右上下四条边分别到当前视图的左右上下四条边的距离,它们是父类View的四个成员变量,(mForegroundPaddingLeft,mForegroundPaddingRight,mForegroundPaddingTop,mForegroundPaddingBottom)表示当前视图的各个子视图所围成的区域的左右上下四条边到当前视视的前景区域的左右上下四条边的距离。从这里就可以看出,当前视图的内容区域的大小就等于前景区域的大小,而前景区域的大小大于等于各个子视图的所围成的区域,这是因为前景区域本来就是用来覆盖各个子视图所围成的区域的。

      加上各个Padding值之后,得到的宽度maxWidth和高度maxHeight还不是最终的宽度和高度,还需要考虑以下两个因素:

      1. 当前视图是否设置有最小宽度和高度。如果设置有的话,并且它们比前面计算得到的宽度maxWidth和高度maxHeight还要大,那么就将它们作为当前视图的宽度和高度值。

      2. 当前视图是否设置有前景图。如果设置有的话,并且它们比前面计算得到的宽度maxWidth和高度maxHeight还要大,那么就将它们作为当前视图的宽度和高度值。

      经过上述两步检查之后,FrameLayout类的成员函数onMeasure就得到了当前视图的宽度maxWidth和高度maxHeight。由于得到的宽度和高度又必须要限制在参数widthMeasureSpec和heightMeasureSpec所描述的宽度和高度规范之内,因此,FrameLayout类的成员函数onMeasure就会调用从View类继承下来的成员函数resolveSize来获得正确的大小。得到了当前视图的正确大小之后,FrameLayout类的成员函数onMeasure就可以调用从父类View继承下来的成员函数setMeasuredDimension来将它们为当前视图的大小了。

      为了理解参数widthMeasureSpec和heightMeasureSpec的含义,我们继续分析View类的成员函数resolveSize的实现,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public  class  View  implements  Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     ......
     public  static  int  resolveSize( 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:
             result = Math.min(size, specSize);
             break ;
         case  MeasureSpec.EXACTLY:
             result = specSize;
             break ;
         }
         return  result;
     }
     ......
}

       这个函数定义在文件rameworks/base/core/java/android/view/View.java中。


       参数measureSpec的值其实是由两部分内容来组成的,最高2位表示一个测量规范,而低30位表示一个宽度值或者高度值。测量规范有三种,分别是0、1和2,使用常量MeasureSpec.UNSPECIFIED、MeasureSpec.EXACTLY和MeasureSpec.AT_MOST来表示。

       当参数measureSpec描述的规范是MeasureSpec.UNSPECIFIED时,就表示当前视图没有指定它的大小测量模式,这时候就使用参数size的值;当参数measureSpec描述的规范是MeasureSpec.AT_MOST时,就表示当前视图的大小等于参数size和参数measureSpec所指定的值中的较小值;当参数measureSpec描述的规范是MeasureSpec.EXACTLY时,就表示当前视图的大小等于参数measureSpec中所指定的值。

       回到FrameLayout类的成员函数onMeasure中,我们再来看一下View类的成员函数setMeasuredDimension是如何设置当前视图的大小的,如下所示:

1
2
3
4
5
6
7
8
9
public  class  View  implements  Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     ......
     protected  final  void  setMeasuredDimension( int  measuredWidth,  int  measuredHeight) {
         mMeasuredWidth = measuredWidth;
         mMeasuredHeight = measuredHeight;
         mPrivateFlags |= MEASURED_DIMENSION_SET;
     }
     ......
}

     这个函数定义在文件rameworks/base/core/java/android/view/View.java中。


      View类的成员函数setMeasuredDimension首先将参数measuredWidth和measuredHeight的值保存在成员变量mMeasuredWidth和mMeasuredHeight中,用来作为当前视图的宽度和高度,并且将成员变量mPrivateFlags的位MEASURED_DIMENSION_SET设置为1,这样返回到前面的Step 1时,就不会抛出一个类型为IllegalStateException的异常了。

       FrameLayout类的另一个成员函数measureChildWithMargins是从父类ViewGroup继承下来的,接下来我们就继续分析它的实现,以便可以了解一个视图容器的各个子视图的大小的测量过程。

       Step 3. ViewGroup.measureChildWithMargins

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public  abstract  class  ViewGroup  extends  View  implements  ViewParent, ViewManager {
     ......
     protected  void  measureChildWithMargins(View child,
             int  parentWidthMeasureSpec,  int  widthUsed,
             int  parentHeightMeasureSpec,  int  heightUsed) {
         final  MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
         final  int  childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                         + widthUsed, lp.width);
         final  int  childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                         + heightUsed, lp.height);
         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
     }
     ......
}

      这个函数定义在文件rameworks/base/core/java/android/view/ViewGroup.java中。


       参数child用来描述当前要测量大小的子视图,参数parentWidthMeasureSpec和parentHeightMeasureSpec用来描述当前子视图可以获得的最大宽度和高度,参数widthUsed和heightUsed用来描述父窗口已经使用了的宽度和高度。ViewGroup类的成员函数measureChildWithMargins必须要综合考虑上述参数,以及当前正在测量的子视图child所设置的大小和Margin值,还有当前视图容器所设置的Padding值,来得到当前正在测量的子视图child的正确宽度childWidthMeasureSpec和高度childHeightMeasureSpec,这是通过调用ViewGroup类的另外一个成员函数getChildMeasureSpec来实现的。

      得到了当前正在测量的子视图child的正确宽度childWidthMeasureSpec和高度childHeightMeasureSpec之后,就可以调用它的成员函数measure来设置它的大小了,即执行前面Step 1的操作。注意,如果当前正在测量的子视图child描述的也是一个视图容器,那么它又会重复执行Step 2和Step 3的操作,直到它的所有子孙视图的大小都测量完成为止。

      至此,我们就分析完成Android应用程序窗口的测量过程了,接下来我们继续分析Android应用程序窗口的布局过程。

      2. Android应用程序窗口的布局过程

      DecorView类的成员函数layout是从父类View继承下来的,因此,我们就从View类的成员函数layout开始分析应用程序窗口的布局过程,如图3所示:

%22

图3 Android应用程序窗口的布局过程

        这个过程可以分为5个步骤,接下来我们就详细地分析每一个步骤。

        Step 1. View.layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public  class  View  implements  Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     ......
                                                                                                                                                                                     
     int  mPrivateFlags;
     ......
     public  final  void  layout( int  l,  int  t,  int  r,  int  b) {
         boolean  changed = setFrame(l, t, r, b);
         if  (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
             ......
             onLayout(changed, l, t, r, b);
             mPrivateFlags &= ~LAYOUT_REQUIRED;
         }
         mPrivateFlags &= ~FORCE_LAYOUT;
     }
     ......
}

       这个函数定义在文件frameworks/base/core/java/android/view/View.java中。

       参数l、t、r和b分别用来描述当前视图的左上右下四条边与其父视图的左上右下四条边的距离,这样当前视图通过这四个参数就可以知道它在父视图中的位置以及大小。

       View类的成员函数layout首先调用另外一个成员函数setFrame来设置当前视图的位置以及大小。设置完成之后,如果当前视图的大小或者位置与上次相比发生了变化,那么View类的成员函数setFrame的返回值changed就会等于true。在这种情况下, View类的成员函数layout就会继续调用另外一个成员函数onLayout重新布局当前视图的子视图。此外,如果此时View类的成员变量mPrivateFlags的LAYOUT_REQUIRED位不等于0,那么也表示当前视图需要重新布局它的子视图,因此,这时候View类的成员函数layout也会调用另外一个成员函数onLayout。

       当前视图的子视图都重新布局完成之后,View类的成员函数layout就可以将成员变量mPrivateFlags的LAYOUT_REQUIRED位设置为0了,因为此时当前视图及其子视图都已经执行了一次布局操作了。

       View类的成员函数layout最后还会将成员变量mPrivateFlags的FORCE_LAYOUT位设置为0,也是因为此时当前视图及其子视图的布局已经是最新的了。

       接下来,我们就继续分析View类的成员函数setFrame和onLayout的实现,以便可以了解当前视图及其子视图是如何执行布局操作的。

       Step 2. View.setFrame

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public  class  View  implements  Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     ......
                                                                                                                                                                                 
     int  mPrivateFlags;
     ......
     int  mViewFlags;
     ......
     protected  int  mLeft;
     ......
     protected  int  mRight;
     ......
     protected  int  mTop;
     ......
     protected  int  mBottom;
     ......
     private  boolean  mBackgroundSizeChanged;
     ......
     protected  boolean  setFrame( int  left,  int  top,  int  right,  int  bottom) {
         boolean  changed =  false ;
         ......
         if  (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
             changed =  true ;
             // Remember our drawn bit
             int  drawn = mPrivateFlags & DRAWN;
             // Invalidate our old position
             invalidate();
             int  oldWidth = mRight - mLeft;
             int  oldHeight = mBottom - mTop;
             mLeft = left;
             mTop = top;
             mRight = right;
             mBottom = bottom;
             mPrivateFlags |= HAS_BOUNDS;
             int  newWidth = right - left;
             int  newHeight = bottom - top;
             if  (newWidth != oldWidth || newHeight != oldHeight) {
                 onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
             }
             if  ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                 // If we are visible, force the DRAWN bit to on so that
                 // this invalidate will go through (at least to our parent).
                 // This is because someone may have invalidated this view
                 // before this call to setFrame came in, therby clearing
                 // the DRAWN bit.
                 mPrivateFlags |= DRAWN;
                 invalidate();
             }
             // Reset drawn bit to original value (invalidate turns it off)
             mPrivateFlags |= drawn;
             mBackgroundSizeChanged =  true ;
         }
         return  changed;
     }
     ......
}

      这个函数定义在文件frameworks/base/core/java/android/view/View.java中。

      View类的成员变量mLeft、mRight、mTop和mBottom分别用来描述当前视图的左右上下四条边与其父视图的左右上下四条边的距离,如果它们的值与参数left、right、top和bottom的值不相等,那么就说明当前视图的大小或者位置发生变化了。这时候View类的成员函数setFrame就需要将参数left、right、top和bottom的值分别记录在成员变量mLeft、mRight、mTop和mBottom中。在记录之前,还会执行两个操作:

      1. 将成员变量mPrivateFlags的DRAWN位记录在变量drawn中,并且调用另外一个成员函数invalidate来检查当前视图上次请求的UI绘制操作是否已经执行。如果已经执行了的话,那么就会再请求执行一个UI绘制操作,以便可以在修改当前视图的大小和位置之前,将当前视图在当前位置按照当前大小显示一次。在接下来的Step 3中,我们再详细分析View类的成员函数invalidate的实现。

      2. 计算当前视图上一次的宽度oldWidth和oldHeight,以便接下来可以检查当前视图的大小是否发生了变化。

      当前视图距离父视图的边距一旦设置好之后,它就是一个具有边界的视图了,因此,View类的成员函数setFrame接着还会将成员变量mPrivateFlags的HAS_BOUNDS设置为1。

      View类的成员函数setFrame再接下来又会计算当前视图新的宽度newWidth和高度newHeight,如果它们与上一次的宽度oldWidth和oldHeight的值不相等,那么就说明当前视图的大小发生了变化,这时候就会调用另外一个成员函数onSizeChanged来让子类有机会处理这个变化事件。

      View类的成员函数setFrame接下来继续判断当前视图是否是可见的,即成员变量mViewFlags的VISIBILITY_MASK位的值是否等于VISIBLE。如果是可见的话,那么就需要将成员变量mPrivateFlags的DRAWN位设置为1,以便接下来可以调用另外一个成员函数invalidate来成功地执行一次UI绘制操作,目的是为了将当前视图马上显示出来。

      View类的成员变量mPrivateFlags的DRAWN位描述的是当前视图上一次请求的UI绘制操作是否已经执行过了。如果它的值等于1,就表示已经执行过了,否则的话,就表示还没在等待执行。前面第一次调用View类的成员函数invalidate来检查当前视图上次请求的UI绘制操作是否已经执行时,如果发现已经执行了,那么就会重新请求执行一次新的UI绘制操作,这时候会导致当前视图的成员变量mPrivateFlags的DRAWN位重置为0。注意,新请求执行的UI绘制只是为了在修改当前视图的大小以及大小之前,先将它在上一次设置的大小以及位置中绘制出来,这样就可以使得当前视图的大小以及位置出现平滑的变换。换句话说,新请求执行的UI绘制只是为了获得一个中间效果,它不应该影响当前视图的绘制状态,即不可以修改当前视图的成员变量mPrivateFlags的DRAWN位。因此,我们就需要在前面第一次调用View类的成员函数invalidate前,先将当前视图的成员变量mPrivateFlags的DRAWN位保存下来,即保存在变量drawn中,然后等到调用之后,再将变量drawn的值恢复到当前视图的成员变量mPrivateFlags的DRAWN位中去。

       另一方面,如果当前视图的大小和位置发生了变化,View类的成员函数setFrame还会将成员变量mBackgroundSizeChanged的值设置为true,以便可以表示当前视图的背景大小发生了变化。

       最后,View类的成员函数setFrame将变量changed的值返回给调用者,以便调用者可以知道当前视图的大小和位置是否发生了变化。

       接下来,我们继续分析View类的成员函数invalidate的实现,以便可以了解当前视图是如何执行一次UI绘制操作的。

       Step 3. View.invalidate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public  class  View  implements  Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     ......
     protected  ViewParent mParent;
     ......
                                                                                                                                                                             
     int  mPrivateFlags;
     ......
     public  void  invalidate() {
         ......
         if  ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
             mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
             final  ViewParent p = mParent;
             final  AttachInfo ai = mAttachInfo;
             if  (p !=  null  && ai !=  null ) {
                 final  Rect r = ai.mTmpInvalRect;
                 r.set( 0 0 , mRight - mLeft, mBottom - mTop);
                 // Don\\\'t call invalidate -- we don\\\'t want to internally scroll
                 // our own bounds
                 p.invalidateChild( this , r);
             }
         }
     }
     ......
}

      这个函数定义在文件frameworks/base/core/java/android/view/View.java中。


      View类的成员函数invalidate首先检查成员变量mPrivateFlags的DRAWN位和HAS_BOUNDS位是否都被设置为1。如果是的话,那么就说明当前视图上一次请求执行的UI绘制操作已经执行完成了,这时候View类的成员函数invalidate才可以请求执行新的UI绘制操作。

       View类的成员函数invalidate在请求新的UI绘制操作之前,会将成员变量mPrivateFlags的DRAWN位和DRAWING_CACHE_VALID位重置为0,其中,后者表示当前视图正在缓存的一些绘图对象已经失效了,这是因为接下来就要重新开始绘制当前视图的UI了。

       请求绘制当前视图的UI是通过调用View类的成员变量mParent所描述的一个ViewParent接口的成员函数invalidateChild来实现的。前面我们假设当前视图是应用程序窗口的顶层视图,即它是一个类型为DecoreView的视图,它的成员变量mParent指向的是与其所关联的一个ViewRoot对象。因此,绘制当前视图的UI的操作实际上是通过调用ViewRoot类的成员函数invalidateChild来实现的。

      注意,在调用ViewRoot类的成员函数invalidateChild的成员函数invalidateChild来绘制当前视图的UI之前,会将当前视图即将要绘制的区域记录在View类的成员变量mAttachInfo所描述的一个AttachInfo对象的成员变量mTmpInvalRect中。

      接下来,我们就继续分析ViewRoot类的成员函数invalidateChild的实现。

      Step 4. ViewRoot.invalidateChild

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
public  final  class  ViewRoot  extends  Handler  implements  ViewParent,
         View.AttachInfo.Callbacks {
     ......
     public  void  invalidateChild(View child, Rect dirty) {
         checkThread();
         ......
         if  (mCurScrollY !=  0  || mTranslator !=  null ) {
             mTempRect.set(dirty);
             dirty = mTempRect;
             if  (mCurScrollY !=  0 ) {
                dirty.offset( 0 , -mCurScrollY);
             }
             if  (mTranslator !=  null ) {
                 mTranslator.translateRectInAppWindowToScreen(dirty);
             }
             if  (mAttachInfo.mScalingRequired) {
                 dirty.inset(- 1 , - 1 );
             }
         }
         mDirty.union(dirty);
         if  (!mWillDrawSoon) {
             scheduleTraversals();
         }
     }
     ......
}

       这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中。





本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/1242900,如需转载请自行联系原作者
目录
相关文章
|
24天前
|
搜索推荐 Android开发 iOS开发
安卓与iOS系统的用户界面设计对比分析
本文通过对安卓和iOS两大操作系统的用户界面设计进行对比分析,探讨它们在设计理念、交互方式、视觉风格等方面的差异及各自特点,旨在帮助读者更好地理解和评估不同系统的用户体验。
18 1
|
2月前
|
Android开发 数据安全/隐私保护 iOS开发
安卓与iOS系统的发展趋势与比较分析
【2月更文挑战第6天】 在移动互联网时代,安卓和iOS系统作为两大主流移动操作系统,各自呈现出不同的发展趋势。本文将从技术角度出发,对安卓和iOS系统的发展方向、特点及未来趋势进行比较分析,以期为读者提供更深入的了解和思考。
35 4
|
3月前
|
监控 Android开发 C语言
深度解读Android崩溃日志案例分析2:tombstone日志
深度解读Android崩溃日志案例分析2:tombstone日志
82 0
|
2月前
|
网络协议 算法 Android开发
安卓逆向 -- 实战某峰窝APP(动态分析)
安卓逆向 -- 实战某峰窝APP(动态分析)
25 4
|
2月前
|
安全 搜索推荐 Android开发
Android 与 iOS 的比较分析
【2月更文挑战第5天】 Android 和 iOS 是目前市场上两种最流行的移动操作系统,它们都拥有自己的特点和优势。本文将会分别从操作系统设计、应用生态、安全性等方面对这两种操作系统进行比较和分析,希望能够帮助读者更好地选择适合自己的移动设备。
|
2月前
|
Android开发
[Android 四大组件] --- Activity
[Android 四大组件] --- Activity
22 1
|
3月前
|
安全 算法 JavaScript
安卓逆向 -- 关键代码定位与分析技术
安卓逆向 -- 关键代码定位与分析技术
39 0
|
3月前
|
算法 安全 Android开发
安卓逆向 -- Frida Hook某车_sign算法分析
安卓逆向 -- Frida Hook某车_sign算法分析
76 0
|
3月前
|
安全 Android开发 数据安全/隐私保护
安卓逆向 -- SO文件逆向分析
安卓逆向 -- SO文件逆向分析
19 0
|
3月前
|
Android开发 数据安全/隐私保护
安卓逆向 -- FridaHook分析3DES加密值
安卓逆向 -- FridaHook分析3DES加密值
17 0
安卓逆向 -- FridaHook分析3DES加密值