转载请注明出处:http://blog.csdn.net/qinjuning

 

 

 

            前言: 本文是我读《Android内核剖析》第13章----View工作原理总结而成的,在此膜拜下作者 。同时真挚地向渴望了解

     Android 框架层的网友,推荐这本书,希望你们能够在Android开发里学到更多的知识 。 

 

         

            整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为

 根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘

 (draw),其框架过程如下:

                                                                                                   步骤其实为host.layout() 

        View 的绘制流程   

 

 

      接下来温习一下整个View树的结构,对每个具体View对象的操作,其实就是个递归的实现。

 

                   View 的绘制流程

 

           关于这个 DecorView 根视图的说明,可以参考我的这篇博客:

               

 

         《Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起》

 

 

 

  流程一:      mesarue()过程

 

        主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:

  mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。

 

     具体的调用链如下:

          ViewRoot根对象地属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调

View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:    

         1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:  

                mMeasuredHeight)和宽(对应属性:mMeasureWidth)   ;

         2 、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。

              

               2.1  对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去

          实现,该方法内部只是简单地调用了View对象的measure()方法。(由于measureChildWithMargins()方法只是一个过渡

          层更简单的做法是直接调用View对象的measure()方法)。

              

     整个measure调用流程就是个树形的递归过程

 

     measure函数原型为 View.java 该函数不能被重载

      

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        //....
	
        //回调onMeasure()方法  
        onMeasure(widthMeasureSpec, heightMeasureSpec);
       
        //more
    }

 

 

 

     为了大家更好的理解,采用“二B程序员”的方式利用伪代码描述该measure流程

 

   //回调View视图里的onMeasure过程
   private void onMeasure(int height , int width){
	   //设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight)
	   //1、该方法必须在onMeasure调用,否者报异常。
	   setMeasuredDimension(h , l) ;
	   
	   //2、如果该View是ViewGroup类型,则对它的每个子View进行measure()过程
	   int childCount = getChildCount() ;
	   
	   for(int i=0 ;i<childCount ;i++){
		   //2.1、获得每个子View对象引用
		   View child = getChildAt(i) ;
		   
		   //整个measure()过程就是个递归过程
		   //该方法只是一个过滤器,最后会调用measure()过程 ;或者 measureChild(child , h, i)方法都
		   measureChildWithMargins(child , h, i) ; 
		   
		   //其实,对于我们自己写的应用来说,最好的办法是去掉框架里的该方法,直接调用view.measure(),如下:
		   //child.measure(h, l)
	   }
   }
   
   //该方法具体实现在ViewGroup.java里 。
   protected  void measureChildWithMargins(View v, int height , int width){
	   v.measure(h,l)   
   }

 

 

 

 

流程二、 layout布局过程:

 

     主要作用 :为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。

 

     具体的调用链如下:

       host.layout()开始View树的布局,继而回调给View/ViewGroup类中的layout()方法。具体流程如下

  

        1 、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)

  接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局) ;

       

       2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。

 

 

          layout函数原型为 ,位于View.java

 

   /* final 标识符 , 不能被重载 , 参数为每个视图位于父视图的坐标轴
    * @param l Left position, relative to parent
    * @param t Top position, relative to parent
    * @param r Right position, relative to parent
    * @param b Bottom position, relative to parent
    */
   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) {
           if (ViewDebug.TRACE_HIERARCHY) {
               ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
           }

           onLayout(changed, l, t, r, b);//回调onLayout函数 ,设置每个子视图的布局
           mPrivateFlags &= ~LAYOUT_REQUIRED;
       }
       mPrivateFlags &= ~FORCE_LAYOUT;
   }


 

 

    同样地, 将上面layout调用流程,用伪代码描述如下: 

 

   // layout()过程  ViewRoot.java
   // 发起layout()的"发号者"在ViewRoot.java里的performTraversals()方法, mView.layout()
   
   private void  performTraversals(){
	   
       //...
   	
       View mView  ;
   	   mView.layout(left,top,right,bottom) ;
   	
       //....
   }
   
   //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现
   private void onLayout(int left , int top , right , bottom){
	 
	   //如果该View不是ViewGroup类型
	   //调用setFrame()方法设置该控件的在父视图上的坐标轴
	   
	   setFrame(l ,t , r ,b) ;
	   
	   //--------------------------
	   
	   //如果该View是ViewGroup类型,则对它的每个子View进行layout()过程
	   int childCount = getChildCount() ;
	   
	   for(int i=0 ;i<childCount ;i++){
		   //2.1、获得每个子View对象引用
		   View child = getChildAt(i) ;
		   //整个layout()过程就是个递归过程
		   child.layout(l, t, r, b) ;
	   }
   }


 

 

 

   流程三、 draw()绘图过程

 

     由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不

  会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该

视图需要重绘时,就会为该View添加该标志位。

 

   调用流程 :

     mView.draw()开始绘制,draw()方法实现的功能如下:

          1 、绘制该View的背景

          2 、为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)          

          3、调用onDraw()方法绘制视图本身   (每个View都需要重载该方法,ViewGroup不需要实现该方法)

          4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)

值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类

  函数实现具体的功能。

 

            4.1 dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个 

地方“需要重绘”的视图才会调用draw()方法)。值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能

实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。

    

     5、绘制滚动条

 

  于是,整个调用链就这样递归下去了。

    

     同样地,使用伪代码描述如下:

    

   // draw()过程     ViewRoot.java
   // 发起draw()的"发号者"在ViewRoot.java里的performTraversals()方法, 该方法会继续调用draw()方法开始绘图
   private void  draw(){
	   
       //...
	   View mView  ;
       mView.draw(canvas) ;  
   	
       //....
   }
   
   //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现
   private void draw(Canvas canvas){
	   //该方法会做如下事情
	   //1 、绘制该View的背景
	   //2、为绘制渐变框做一些准备操作
	   //3、调用onDraw()方法绘制视图本身
	   //4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。
	        // 应用程序程序一般不需要重写该方法,但可以捕获该方法的发生,做一些特别的事情。
	   //5、绘制渐变框	
   }
   
   //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法
   @Override
   protected void dispatchDraw(Canvas canvas) {
	   // 
	   //其实现方法类似如下:
	   int childCount = getChildCount() ;
	   
	   for(int i=0 ;i<childCount ;i++){
		   View child = getChildAt(i) ;
		   //调用drawChild完成
		   drawChild(child,canvas) ;
	   }	   
   }
   //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法
   protected void drawChild(View child,Canvas canvas) {
	   // ....
	   //简单的回调View对象的draw()方法,递归就这么产生了。
	   child.draw(canvas) ;
	   
	   //.........
   }

 

 

 

   关于绘制背景图片详细的过程,请参考我的另外的博客:

           

              <<Android中View(视图)绘制不同状态背景图片原理深入分析以及StateListDrawable使用详解>>

 

 

    强调一点的就是,在这三个流程中,Google已经帮我们把draw()过程框架已经写好了,自定义的ViewGroup只需要实现

 measure()过程和layout()过程即可 。

 

 

     这三种情况,最终会直接或间接调用到三个函数,分别为invalidate(),requsetLaytout()以及requestFocus() ,接着

这三个函数最终会调用到ViewRoot中的schedulTraversale()方法,该函数然后发起一个异步消息,消息处理中调用

performTraverser()方法对整个View进行遍历。

 

 

    invalidate()方法 :

 

   说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”

视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。

 

     一般引起invalidate()操作的函数如下:

            1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。

            2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。

            3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,

                     继而绘制该View。

            4 、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。

 

    requestLayout()方法 :会导致调用measure()过程 和 layout()过程 。

 

           说明:只是对View树重新布局layout过程包括measure()和layout()过程,不会调用draw()过程,但不会重新绘制

任何视图包括该调用者本身。

 

    一般引起invalidate()操作的函数如下:

         1、setVisibility()方法:

             当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。

    同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。

 

    requestFocus()函数说明:

 

          说明:请求View树的draw()过程,但只绘制“需要重绘”的视图。

 

 

    下面写个简单的小Demo吧,主要目的是给大家演示绘图的过程以及每个流程里该做的一些功能。截图如下:

 

                                                View 的绘制流程

 

 

 

 1、    MyViewGroup.java  自定义ViewGroup类型

   

	/**
	 * @author http://http://blog.csdn.net/qinjuning
	 */
	//自定义ViewGroup 对象
	public class MyViewGroup  extends ViewGroup{


		private static String TAG = "MyViewGroup" ;
		private Context mContext ;
		
		public MyViewGroup(Context context) {
			super(context);
			mContext = context ;
			init() ;
		}

		//xml定义的属性,需要该构造函数
	    public MyViewGroup(Context context , AttributeSet attrs){
	    	super(context,attrs) ;
	    	mContext = context ;
	    	init() ;
	    }
		
	    //为MyViewGroup添加三个子View
	    private void init(){
	    	//调用ViewGroup父类addView()方法添加子View
	    	
	    	//child 对象一 : Button
	    	Button btn= new Button(mContext) ;
	    	btn.setText("I am Button") ;
	    	this.addView(btn) ;
	    	
	    	//child 对象二 : ImageView 
	    	ImageView img = new ImageView(mContext) ;
	    	img.setBackgroundResource(R.drawable.icon) ;
	    	this.addView(img) ;
	    	
	    	//child 对象三 : TextView
	    	TextView txt = new TextView(mContext) ;
	    	txt.setText("Only Text") ;
	    	this.addView(txt) ; 
	    	
	    	//child 对象四 : 自定义View
	    	MyView myView = new MyView(mContext) ;
	    	this.addView(myView) ; 
	    }
	    
	    @Override
	    //对每个子View进行measure():设置每子View的大小,即实际宽和高
	    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
	    	//通过init()方法,我们为该ViewGroup对象添加了三个视图 , Button、 ImageView、TextView
	    	int childCount = getChildCount() ;
	    	Log.i(TAG, "the size of this ViewGroup is ----> " + childCount) ;
	    	    	    	
	    	Log.i(TAG, "**** onMeasure start *****") ;
	    	
	    	//获取该ViewGroup的实际长和宽  涉及到MeasureSpec类的使用
	    	int specSize_Widht = MeasureSpec.getSize(widthMeasureSpec) ;
	    	int specSize_Heigth = MeasureSpec.getSize(heightMeasureSpec) ;
	    	
	    	Log.i(TAG, "**** specSize_Widht " + specSize_Widht+ " * specSize_Heigth   *****" + specSize_Heigth) ;
	    	
	    	//设置本ViewGroup的宽高
	    	setMeasuredDimension(specSize_Widht , specSize_Heigth) ;
	    	
	    	
	    	
	    	
	    	for(int i=0 ;i<childCount ; i++){
	    		View child = getChildAt(i) ;   //获得每个对象的引用
	    		child.measure(50, 50) ;   //简单的设置每个子View对象的宽高为 50px , 50px  
	    		//或者可以调用ViewGroup父类方法measureChild()或者measureChildWithMargins()方法
	    	    //this.measureChild(child, widthMeasureSpec, heightMeasureSpec) ;
	    	}
	    	
	    }
	    
		@Override
		//对每个子View视图进行布局
		protected void onLayout(boolean changed, int l, int t, int r, int b) {
			// TODO Auto-generated method stub
	    	//通过init()方法,我们为该ViewGroup对象添加了三个视图 , Button、 ImageView、TextView
	    	int childCount = getChildCount() ;
	    	
	    	int startLeft = 0 ;//设置每个子View的起始横坐标 
	    	int startTop = 10 ; //每个子View距离父视图的位置 , 简单设置为10px吧 。 可以理解为 android:margin=10px ;
	    	
	    	Log.i(TAG, "**** onLayout start ****") ;
	    	for(int i=0 ;i<childCount ; i++){
	    		View child = getChildAt(i) ;   //获得每个对象的引用
	    		child.layout(startLeft, startTop, startLeft+child.getMeasuredWidth(), startTop+child.getMeasuredHeight()) ;
	    		startLeft =startLeft+child.getMeasuredWidth() + 10;  //校准startLeft值,View之间的间距设为10px ;
	    		Log.i(TAG, "**** onLayout startLeft ****" +startLeft) ;
	    	}   	   	
		}
		//绘图过程Android已经为我们封装好了 ,这儿只为了观察方法调用程
		protected void dispatchDraw(Canvas canvas){
			Log.i(TAG, "**** dispatchDraw start ****") ;
			
			super.dispatchDraw(canvas) ;
		}
	    
		protected boolean drawChild(Canvas canvas , View child, long drawingTime){
			Log.i(TAG, "**** drawChild start ****") ;
			
			return super.drawChild(canvas, child, drawingTime) ;
		}
	}

 

   

          2、MyView.java 自定义View类型,重写onDraw()方法 ,

 

//自定义View对象
	public class MyView extends View{

		private Paint paint  = new Paint() ;
		
		public MyView(Context context) {
			super(context);
			// TODO Auto-generated constructor stub
		}
		public MyView(Context context , AttributeSet attrs){
			super(context,attrs);
		}
		
		protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
			//设置该View大小为 80 80
			setMeasuredDimension(50 , 50) ;
		}
		
		
		
		//存在canvas对象,即存在默认的显示区域
		@Override
		public void onDraw(Canvas canvas) {
			// TODO Auto-generated method stub
			super.onDraw(canvas);
			
			Log.i("MyViewGroup", "MyView is onDraw ") ;
			//加粗
			paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
			paint.setColor(Color.RED);
			canvas.drawColor(Color.BLUE) ;
			canvas.drawRect(0, 0, 30, 30, paint);
			canvas.drawText("MyView", 10, 40, paint);
		}
	}


 

 

 

          主Activity只是显示了该xml文件,在此也不罗嗦了。 大家可以查看该ViewGroup的Log仔细分析下View的绘制流程以及

相关方法的使用。第一次启动后捕获的Log如下,网上找了些资料,第一次View树绘制过程会走几遍,具体原因可能是某些

View 发生了改变,请求重新绘制,但这根本不影响我们的界面显示效果 。

 

        总的来说: 整个绘制过程还是十分十分复杂地,每个具体方法的实现都是我辈难以立即的,感到悲剧啊。对Android提

 供的一些ViewGroup对象,比如LinearLayout、RelativeLayout布局对象的实现也很有压力。 本文重在介绍整个View树的绘制

流程,希望大家在此基础上,多接触源代码进行更深入地扩展。

 

 

Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起

本文主要内容是讲解一个视图View或者一个ViewGroup对象是如何添加至应用程序窗口中的。

 

        下文中提到的窗口可泛指我们能看到的界面,包括一个Activity呈现的界面(我们可以将之理解为应用程序窗口),一个Dialog,

   一个Toast,一个Menu菜单等。

 

      首先对相关类的作用进行一下简单介绍:

 

         Window 类   位于 /frameworks/base/core/java/android/view/Window.java

            说明:该类是一个抽象类,提供了绘制窗口的一组通用API。可以将之理解为一个载体,各种View在这个载体上显示。

             源文件(部分)如下:

public abstract class Window {	
	//...
	//指定Activity窗口的风格类型
    public static final int FEATURE_NO_TITLE = 1;
    public static final int FEATURE_INDETERMINATE_PROGRESS = 5;
    
    //设置布局文件
    public abstract void setContentView(int layoutResID);

    public abstract void setContentView(View view);

    //请求指定Activity窗口的风格类型
    public boolean requestFeature(int featureId) {
        final int flag = 1<<featureId;
        mFeatures |= flag;
        mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
        return (mFeatures&flag) != 0;
    }    
    //...
}

       PhoneWindow类  位于/frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindow.java

         说明: 该类继承于Window类,是Window类的具体实现,即我们可以通过该类具体去绘制窗口。并且,该类内部包含了

            一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。 简而言之,PhoneWindow类是

            把一个FrameLayout类即DecorView对象进行一定的包装,将它作为应用窗口的根View,并提供一组通用的窗口操作

            接口。

               源文件(部分)如下:          

public class PhoneWindow extends Window implements MenuBuilder.Callback {
	//...
	// This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;  //该对象是所有应用窗口的根视图 , 是FrameLayout的子类
    
    //该对象是Activity布局文件的父视图,一般来说是一个FrameLayout型的ViewGroup 
    // 同时也是DecorView对象的一个子视图
    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent; 
    
    //设置标题
    @Override
    public void setTitle(CharSequence title) {
        if (mTitleView != null) {
            mTitleView.setText(title);
        }
        mTitle = title;
    }
    //设置背景图片
    @Override
    public final void setBackgroundDrawable(Drawable drawable) {
        if (drawable != mBackgroundDrawable || mBackgroundResource != 0) {
            mBackgroundResource = 0;
            mBackgroundDrawable = drawable;
            if (mDecor != null) {
                mDecor.setWindowBackground(drawable);
            }
        }
    }
    //...    
}

       DecorView类    该类是PhoneWindow类的内部类

         说明: 该类是一个FrameLayout的子类,并且是PhoneWindow的子类,该类就是对普通的FrameLayout进行功能的扩展,

            更确切点可以说是修饰(Decor的英文全称是Decoration,即“修饰”的意思),比如说添加TitleBar(标题栏),以及

            TitleBar上的滚动条等 。最重要的一点是,它是所有应用窗口的根View 。

         如下所示 :

   

  View 的绘制流程             View 的绘制流程

           DecorView 根视图结构                                                          DecorView 根视图形式

     

     源文件(部分)如下:

private final class DecorView extends FrameLayout {
	//...
	//触摸事件处理
	@Override
    public boolean onTouchEvent(MotionEvent event) {
        return onInterceptTouchEvent(event);
    }
	//...
}


 

       打个不恰当比喻吧,Window类相当于一幅画(抽象概念,什么画我们未知) ,PhoneWindow为一副齐白石先生的山水画

   (具体概念,我们知道了是谁的、什么性质的画),DecorView则为该山水画的具体内容(有山、有水、有树,各种界面)。

   DecorView呈现在PhoneWindow上。

 

 

 

       当系统(一般是ActivityManagerService)配置好启动一个Activity的相关参数(包括Activity对象和Window对象信息)后,

   就会回调Activity的onCreate()方法,在其中我们通过设置setContentView()方法类设置该Activity的显示界面,整个调用链

   由此铺垫开来。setContentView()的三个构造方法调用流程本质上是一样的,我们就分析setContentView(intresId)方法。

  

    

           

Step 1  、Activity.setContentView(intresId)   该方法在Activity类中

         该方法只是简单的回调Window对象,具体为PhoneWindow对象的setContentView()方法实现 。

 

	public void setContentView(int layoutResID) {
	    getWindow().setContentView(layoutResID);
	}

	public Window getWindow() {
	    return mWindow;   //Window对象,本质上是一个PhoneWindow对象
	}


 Step 2  、PhoneWindow.setContentView()     该方法在PhoneWindow类中 

 

 

	@Override
	public void setContentView(int layoutResID) {
		//是否是第一次调用setContentView方法, 如果是第一次调用,则mDecor和mContentParent对象都为空
	    if (mContentParent == null) {
	        installDecor();
	    } else {
	        mContentParent.removeAllViews();
	    }
	    mLayoutInflater.inflate(layoutResID, mContentParent);
	    final Callback cb = getCallback();
	    if (cb != null) {
	        cb.onContentChanged();
	    }
	}

 

 

 

       该方法根据首先判断是否已经由setContentView()了获取mContentParent即View对象, 即是否是第一次调用该

   PhoneWindow对象setContentView()方法。如果是第一次调用,则调用installDecor()方法,否则,移除该mContentParent内

   所有的所有子View。最后将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中。

      

       PS:因此,在应用程序里,我们可以多次调用setContentView()来显示我们的界面。

 

 Step 3、 PhoneWindow. installDecor()    该方法在PhoneWindow类中

 

 

	private void installDecor() {
	    if (mDecor == null) {
	    	//mDecor为空,则创建一个Decor对象
	        mDecor = generateDecor();
	        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
	        mDecor.setIsRootNamespace(true);
	    }
	    if (mContentParent == null) {
	    	//generateLayout()方法会根据窗口的风格修饰,选择对应的修饰布局文件
	    	//并且将id为content(android:id="@+id/content")的FrameLayout赋值给mContentParent
	        mContentParent = generateLayout(mDecor);
	        
	        //...
	}

 

 

   首先、该方法首先判断mDecor对象是否为空,如果不为空,则调用generateDecor()创建一个DecorView(该类是

           FrameLayout子类,即一个ViewGroup视图) ;

 

      generateDecor()方法原型为:

    protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

 

 

  其次、继续判断mContentParent对象是否为空,如果不为空,则调用generateLayout()方法去创建mContentParent对象。

         generateLayout()方法如下:

 

    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        //...1、根据requestFreature()和Activity节点的android:theme="" 设置好 features值
    	
    	//2 根据设定好的 features值,即特定风格属性,选择不同的窗口修饰布局文件
        int layoutResource;  //窗口修饰布局文件  
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                layoutResource = com.android.internal.R.layout.dialog_title_icons;
            } else {
                layoutResource = com.android.internal.R.layout.screen_title_icons;
            }
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = com.android.internal.R.layout.screen_progress;
            // System.out.println("Progress!");
        } 
        //...
        
        //3 选定了窗口修饰布局文件 ,添加至DecorView对象里,并且指定mcontentParent值
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }
        //...
        return contentParent;
    }


 

 

 

 

 该方法会做如下事情:

   1、根据窗口的风格修饰类型为该窗口选择不同的窗口布局文件(根视图)。这些窗口修饰布局文件指定一个用来存放

         Activity自定义布局文件的ViewGroup视图,一般为FrameLayout 其id 为: android:id="@android:id/content"。

        例如窗口修饰类型包括FullScreen(全屏)、NoTitleBar(不含标题栏)等。选定窗口修饰类型有两种:

           ①、指定requestFeature()指定窗口修饰符,PhoneWindow对象调用getLocalFeature()方法获取值;

           ②、为我们的Activity配置相应属性,即android:theme=“”,PhoneWindow对象调用getWindowStyle()方法

              获取值。

        举例如下,隐藏标题栏有如下方法:requestWindowFeature(Window.FEATURE_NO_TITLE);

                   或者 为Activity配置xml属性:android:theme=”@android:style/Theme.NoTitleBar”。

 

        PS:因此,在Activity中必须在setContentView之前调用requestFeature()方法。

 

 

  确定好窗口风格之后,选定该风格对应的布局文件,这些布局文件位于 frameworks/base/core/res/layout/  ,

        典型的窗口布局文件有:

          R.layout.dialog_titile_icons                          R.layout.screen_title_icons

          R.layout.screen_progress                             R.layout.dialog_custom_title

          R.layout.dialog_title   

          R.layout.screen_title         // 最常用的Activity窗口修饰布局文件

          R.layout.screen_simple    //全屏的Activity窗口布局文件

 

 

 

   分析Activity最常用的一种窗口布局文件,R.layout.screen_title  :

 

    <!--
    This is an optimized layout for a screen, with the minimum set of features
    enabled.
    -->

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:fitsSystemWindows="true">
        <FrameLayout
            android:layout_width="match_parent" 
            android:layout_height="?android:attr/windowTitleSize"
            style="?android:attr/windowTitleBackgroundStyle">
            <TextView android:id="@android:id/title" 
                style="?android:attr/windowTitleStyle"
                android:background="@null"
                android:fadingEdge="horizontal"
                android:gravity="center_vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </FrameLayout>
        <FrameLayout android:id="@android:id/content"
            android:layout_width="match_parent" 
            android:layout_height="0dip"
            android:layout_weight="1"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />
    </LinearLayout>

 

       该布局文件很简单,一个LinearLayout下包含了两个子FrameLayout视图,第一个FrameLayout用来显示标题栏(TitleBar),

  该TextView 视图id为title(android:id="@android:id/title");第二个FrameLayout用来显示我们Activity的布局文件的父视图,

  该FrameLayoutid为content(android:id="@android:id/content") 。

 

 

  全屏的窗口布局文件 R.layout.screen_simple:

 

    <--This is an optimized layout for a screen, with the minimum set of features
    enabled.
    -->

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@android:id/content"
        android:fitsSystemWindows="true"
        android:foregroundInsidePadding="false"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />

 

     

            该布局文件只有一个FrameLayout,用来显示我们Activity的布局文件,该FrameLayoutid为

    android:id="@android:id/content"

 

 

  2、前面一步我们确定窗口修饰布局文件后,mDecor做为根视图将该窗口布局对应的视图添加进去,并且获取id为content

          的View,将其赋值给mContentParent对象,即我们前面中提到的第二个FrameLayout。

 

   At Last、产生了mDecor和mContentParent对象后,就将我们的Activity布局文件直接添加至mContentParent父视图中即可。

      我们再次回到 Step 2 中PhoneWindow.setContentView()      该方法在PhoneWindow类中

 

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null) {
            cb.onContentChanged();
        }
    }


 

 

  整个过程主要是如何把Activity的布局文件添加至窗口里,上面的过程可以概括为:

              1、创建一个DecorView对象,该对象将作为整个应用窗口的根视图

              2、创建不同的窗口修饰布局文件,并且获取Activity的布局文件该存放的地方,由该窗口修饰布局文件内id为content的

                  FrameLayout指定 。

              3、将Activity的布局文件添加至id为content的FrameLayout内。

 

       最后,当AMS(ActivityManagerService)准备resume一个Activity时,会回调该Activity的handleResumeActivity()方法,

  该方法会调用Activity的makeVisible方法 ,显示我们刚才创建的mDecor 视图族。

   

    //系统resume一个Activity时,调用此方法
    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
    	ActivityRecord r = performResumeActivity(token, clearHide);
    	//...
    	 if (r.activity.mVisibleFromClient) {
             r.activity.makeVisible();
         }
    }

 

 

    handleResumeActivity()方法原型如下: 位于ActivityThread类中

    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();   // 获取WindowManager对象
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE); //使其处于显示状况
    }

 

 

     接下来就是,如何把我们已经创建好的窗口通知给WindowManagerService ,以便它能够把这个窗口显示在屏幕上。

关于这方面内容大家可以去看邓凡平老师的这篇博客《Android深入浅出之Surface[1]

Android深入浅出之Surface

一 目的

本节的目的就是为了讲清楚Android中的Surface系统,大家耳熟能详的SurfaceFlinger到底是个什么东西,它的工作流程又是怎样的。当然,鉴于SurfaceFlinger的复杂性,我们依然将采用情景分析的办法,找到合适的切入点。

一个Activity是怎么在屏幕上显示出来的呢?我将首先把这个说清楚。

接着我们把其中的关键调用抽象在Native层,以这些函数调用为切入点来研究SurfaceFlinger。好了,开始我们的征途吧。

二 Activity是如何显示的

最初的想法就是,Activity获得一块显存,然后在上面绘图,最后交给设备去显示。这个道理是没错,但是Android的SurfaceFlinger是在System Server进程中创建的,Activity一般另有线程,这之间是如何...如何挂上关系的呢?我可以先提前告诉大家,这个过程还比较复杂。呵呵。

好吧,我们从Activity最初的启动开始。代码在

framework/base/core/java/android/app/ActivityThread.java中,这里有个函数叫handleLaunchActivity

[---->ActivityThread:: handleLaunchActivity()]

private final void handleLaunchActivity(ActivityRecord r, Intent customIntent) {

      Activity a = performLaunchActivity(r, customIntent);

 

        if (a != null) {

            r.createdConfig = new Configuration(mConfiguration);

            Bundle oldState = r.state;

            handleResumeActivity(r.token, false, r.isForward);

---->调用handleResumeActivity

}

handleLaunchActivity中会调用handleResumeActivity。

[--->ActivityThread:: handleResumeActivity]

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {

         boolean willBeVisible = !a.mStartedActivity;

          

if (r.window == null && !a.mFinished && willBeVisible) {

                r.window = r.activity.getWindow();

                View decor = r.window.getDecorView();

                decor.setVisibility(View.INVISIBLE);

                ViewManager wm = a.getWindowManager();

                WindowManager.LayoutParams l = r.window.getAttributes();

                a.mDecor = decor;

                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;

                if (a.mVisibleFromClient) {

                    a.mWindowAdded = true;

                    wm.addView(decor, l); //这个很关键。

                }

上面addView那几行非常关键,它关系到咱们在Activity中setContentView后,整个Window到底都包含了些什么。我先告诉大家。所有你创建的View之上,还有一个DecorView,这是一个FrameLayout,另外还有一个PhoneWindow。上面这些东西的代码在

framework/Policies/Base/Phone/com/android/Internal/policy/impl。这些隐藏的View的创建都是由你在Acitivty的onCreate中调用setContentView导致的。

[---->PhoneWindow:: addContentView]

   public void addContentView(View view, ViewGroup.LayoutParams params) {

        if (mContentParent == null) {  //刚创建的时候mContentParent为空

            installDecor();

        }

        mContentParent.addView(view, params);

        final Callback cb = getCallback();

        if (cb != null) {

            cb.onContentChanged();

        }

}

installDecor将创建mDecor和mContentParent。mDecor是DecorView类型,

mContentParent是ViewGroup类型

private void installDecor() {

        if (mDecor == null) {

            mDecor = generateDecor();

            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

            mDecor.setIsRootNamespace(true);

        }

        if (mContentParent == null) {

            mContentParent = generateLayout(mDecor);

那么,ViewManager wm = a.getWindowManager()又返回什么呢?

PhoneWindow从Window中派生,Acitivity创建的时候会调用它的setWindowManager。而这个函数由Window类实现。

代码在framework/base/core/java/android/view/Window.java中

public void setWindowManager(WindowManager wm,IBinder appToken, String appName) {

        mAppToken = appToken;

        mAppName = appName;

        if (wm == null) {

            wm = WindowManagerImpl.getDefault();

        }

        mWindowManager = new LocalWindowManager(wm);

    }

你看见没,分析JAVA代码这个东西真的很复杂。mWindowManager的实现是LocalWindowManager,但由通过Bridge模式把功能交给WindowManagerImpl去实现了。

真的很复杂!

好了,罗里罗嗦的,我们回到wm.addView(decor, l)。最终会由WindowManagerImpl来完成

addView操作,我们直接看它的实现好了。

代码在framework/base/core/java/android/view/WindowManagerImpl.java

[---->addView]

private void addView(View view, ViewGroup.LayoutParams params, boolean nest)

    {

          ViewRoot root; //ViewRoot,我们的主人公终于登场!

         synchronized (this) {

         root = new ViewRoot(view.getContext());

          root.mAddNesting = 1;

          view.setLayoutParams(wparams);

           

            if (mViews == null) {

                index = 1;

                mViews = new View[1];

                mRoots = new ViewRoot[1];

                mParams = new WindowManager.LayoutParams[1];

            } else {

           }

            index--;

            mViews[index] = view;

            mRoots[index] = root;

            mParams[index] = wparams;

        }

       root.setView(view, wparams, panelParentView);

}

ViewRoot是整个显示系统中最为关键的东西,看起来这个东西好像和View有那么点关系,其实它根本和View等UI关系不大,它不过是一个Handler罢了,唯一有关系的就是它其中有一个变量为Surface类型。我们看看它的定义。ViewRoot代码在

framework/base/core/java/android/view/ViewRoot.java中

public final class ViewRoot extends Handler implements ViewParent,

        View.AttachInfo.Callbacks

{

private final Surface mSurface = new Surface();

}

它竟然从handler派生,而ViewParent不过定义了一些接口函数罢了。

看到Surface直觉上感到它和SurfaceFlinger有点关系。要不先去看看?

Surface代码在framework/base/core/java/android/view/Surface.java中,我们调用的是无参构造函数。

public Surface() {

       mCanvas = new CompatibleCanvas(); //就是创建一个Canvas!

}

如果你有兴趣的话,看看Surface其他构造函数,最终都会调用native的实现,而这些native的实现将和SurfaceFlinger建立关系,但我们这里ViewRoot中的mSurface显然还没有到这一步。那它到底是怎么和SurfaceFlinger搞上的呢?这一切待会就会水落石出的。

另外,为什么ViewRoot是主人公呢?因为ViewRoot建立了客户端和SystemServer的关系。我们看看它的构造函数。

public ViewRoot(Context context) {

        super();

       ....

       getWindowSession(context.getMainLooper());

}

getWindowsession将建立和WindowManagerService的关系。

ublic static IWindowSession getWindowSession(Looper mainLooper) {

        synchronized (mStaticInit) {

            if (!mInitialized) {

                try {

                //sWindowSession是通过Binder机制创建的。终于让我们看到点希望了

                    InputMethodManager imm = InputMethodManager.getInstance(mainLooper);

                    sWindowSession = IWindowManager.Stub.asInterface(

                            ServiceManager.getService("window"))

                            .openSession(imm.getClient(), imm.getInputContext());

                    mInitialized = true;

                } catch (RemoteException e) {

                }

            }

            return sWindowSession;

        }

    }

上面跨Binder的进程调用另一端是WindowManagerService,代码在

framework/base/services/java/com/android/server/WindowManagerService.java中。我们先不说这个。

回过头来看看ViewRoot接下来的调用。

[-->ViewRoot::setView()],这个函数很复杂,我们看其中关键几句。

public void setView(View view, WindowManager.LayoutParams attrs,

            View panelParentView) {

        synchronized (this) {

            requestLayout();

                try {

                    res = sWindowSession.add(mWindow, mWindowAttributes,

                            getHostVisibility(), mAttachInfo.mContentInsets);

                }

}

requestLayout实现很简单,就是往handler中发送了一个消息。

public void requestLayout() {

        checkThread();

        mLayoutRequested = true;

        scheduleTraversals(); //发送DO_TRAVERSAL消息

}

public void scheduleTraversals() {

        if (!mTraversalScheduled) {

            mTraversalScheduled = true;

            sendEmptyMessage(DO_TRAVERSAL);

        }

}

我们看看跨进程的那个调用。sWindowSession.add。它的最终实现在WindowManagerService中。

[--->WindowSession::add()]

public int add(IWindow window, WindowManager.LayoutParams attrs,

                int viewVisibility, Rect outContentInsets) {

            return addWindow(this, window, attrs, viewVisibility, outContentInsets);

        }

WindowSession是个内部类,会调用外部类的addWindow

这个函数巨复杂无比,但是我们的核心目标是找到创建显示相关的部分。所以,最后精简的话就简单了。

[--->WindowManagerService:: addWindow]

public int addWindow(Session session, IWindow client,

            WindowManager.LayoutParams attrs, int viewVisibility,

            Rect outContentInsets) {

        //创建一个WindowState,这个又是什么玩意儿呢?

              win = new WindowState(session, client, token,

                    attachedWindow, attrs, viewVisibility);

           win.attach();

           return res;

}

WindowState类中有一个和Surface相关的成员变量,叫SurfaceSession。它会在

attach函数中被创建。SurfaceSession嘛,就和SurfaceFlinger有关系了。我们待会看。

好,我们知道ViewRoot创建及调用add后,我们客户端的View系统就和WindowManagerService建立了牢不可破的关系。

另外,我们知道ViewRoot是一个handler,而且刚才我们调用了requestLayout,所以接下来消息循环下一个将调用的就是ViewRoot的handleMessage。

public void handleMessage(Message msg) {

        switch (msg.what) {

       case DO_TRAVERSAL:

            performTraversals();

performTraversals更加复杂无比,经过我仔细挑选,目标锁定为下面几个函数。当然,后面我们还会回到performTraversals,不过我们现在更感兴趣的是Surface是如何创建的。

private void performTraversals() {

        // cache mView since it is used so much below...

        final View host = mView;

 

         boolean initialized = false;

            boolean contentInsetsChanged = false;

            boolean visibleInsetsChanged;

            try {

//ViewRoot也有一个Surface成员变量,叫mSurface,这个就是代表SurfaceFlinger的客户端

//ViewRoot在这个Surface上作画,最后将由SurfaceFlinger来合成显示。刚才说了mSurface还没有什么内容。

          relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

[---->ViewRoot:: relayoutWindow()]

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,

            boolean insetsPending) throws RemoteException {

      

//relayOut是跨进程调用,mSurface做为参数传进去了,看来离真相越来越近了呀!

        int relayoutResult = sWindowSession.relayout(

                mWindow, params,

                (int) (mView.mMeasuredWidth * appScale + 0.5f),

                (int) (mView.mMeasuredHeight * appScale + 0.5f),

                viewVisibility, insetsPending, mWinFrame,

                mPendingContentInsets, mPendingVisibleInsets,

                mPendingConfiguration, mSurface); mSurface做为参数传进去了。

       }

我们赶紧转到WindowManagerService去看看吧。、

public int relayoutWindow(Session session, IWindow client,

            WindowManager.LayoutParams attrs, int requestedWidth,

            int requestedHeight, int viewVisibility, boolean insetsPending,

            Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,

            Configuration outConfig, Surface outSurface){

               .....

         try {

           //看到这里,我内心一阵狂喜,有戏,太有戏了!

         //其中win是我们最初创建的WindowState!

                    Surface surface = win.createSurfaceLocked();

                    if (surface != null) {

                  //先创建一个本地surface,然后把传入的参数outSurface copyFrom一下

                        outSurface.copyFrom(surface);

                        win.mReportDestroySurface = false;

                        win.mSurfacePendingDestroy = false;

                       } else {

                       outSurface.release();

                    }

                }

}

[--->WindowState::createSurfaceLocked]

Surface createSurfaceLocked() {

         

                try {

                    mSurface = new Surface(

                            mSession.mSurfaceSession, mSession.mPid,

                            mAttrs.getTitle().toString(),

                            0, w, h, mAttrs.format, flags);

                  }

                Surface.openTransaction();

这里使用了Surface的另外一个构造函数。

    public Surface(SurfaceSession s,

            int pid, String name, int display, int w, int h, int format, int flags)

        throws OutOfResourcesException {

        mCanvas = new CompatibleCanvas();

        init(s,pid,name,display,w,h,format,flags); ---->调用了native的init函数。

        mName = name;

}

到这里,不进入JNI是不可能说清楚了。不过我们要先回顾下之前的关键步骤。

l add中,new了一个SurfaceSession

l 创建new了一个Surface

l 调用copyFrom,把本地Surface信息传到outSurface中

JNI层

上面两个类的JNI实现都在framework/base/core/jni/android_view_Surface.cpp中。

 [---->SurfaceSession:: SurfaceSession()]

public class SurfaceSession {

    /** Create a new connection with the surface flinger. */

    public SurfaceSession() {

        init();

}

它的init函数对应为:

[--->SurfaceSession_init]

static void SurfaceSession_init(JNIEnv* env, jobject clazz)

{

   //SurfaceSession对应为SurfaceComposerClient

    sp<SurfaceComposerClient> client = new SurfaceComposerClient;

client->incStrong(clazz);

//Google常用做法,在JAVA对象中保存C++对象的指针。

    env->SetIntField(clazz, sso.client, (int)client.get());

}

Surface的init对应为:

[--->Surface_init]

static void Surface_init(

        JNIEnv* env, jobject clazz,

        jobject session,

        jint pid, jstring jname, jint dpy, jint w, jint h, jint format, jint flags)

{

   SurfaceComposerClient* client =

            (SurfaceComposerClient*)env->GetIntField(session, sso.client);

 

    sp<SurfaceControl> surface;

if (jname == NULL) {

   //client是SurfaceComposerClient,返回的surface是一个SurfaceControl

  //真得很复杂!

        surface = client->createSurface(pid, dpy, w, h, format, flags);

    } else {

        const jchar* str = env->GetStringCritical(jname, 0);

        const String8 name(str, env->GetStringLength(jname));

        env->ReleaseStringCritical(jname, str);

        surface = client->createSurface(pid, name, dpy, w, h, format, flags);

}

  //把surfaceControl信息设置到Surface对象中

    setSurfaceControl(env, clazz, surface);

}

 

static void setSurfaceControl(JNIEnv* env, jobject clazz,

        const sp<SurfaceControl>& surface)

{

    SurfaceControl* const p =

        (SurfaceControl*)env->GetIntField(clazz, so.surfaceControl);

    if (surface.get()) {

        surface->incStrong(clazz);

    }

    if (p) {

        p->decStrong(clazz);

    }

    env->SetIntField(clazz, so.surfaceControl, (int)surface.get());

}

[--->Surface_copyFrom]

static void Surface_copyFrom(

        JNIEnv* env, jobject clazz, jobject other)

{

   const sp<SurfaceControl>& surface = getSurfaceControl(env, clazz);

    const sp<SurfaceControl>& rhs = getSurfaceControl(env, other);

    if (!SurfaceControl::isSameSurface(surface, rhs)) {

        setSurfaceControl(env, clazz, rhs);

//把本地那个surface的surfaceControl对象转移到outSurface上

    }

}

这里仅仅是surfaceControl的转移,但是并没有看到Surface相关的信息。

那么Surface在哪里创建的呢?为了解释这个问题,我使用了终极武器,aidl。

1 终极武器AIDL

aidl可以把XXX.aidl文件转换成对应的java文件。我们刚才调用的是WindowSession的

relayOut函数。如下:

sWindowSession.relayout(

                mWindow, params,

                (int) (mView.mMeasuredWidth * appScale + 0.5f),

                (int) (mView.mMeasuredHeight * appScale + 0.5f),

                viewVisibility, insetsPending, mWinFrame,

                mPendingContentInsets, mPendingVisibleInsets,

                mPendingConfiguration, mSurface);

它的aidl文件在framework/base/core/java/android/view/IWindowSession.aidl中

interface IWindowSession {

    int add(IWindow window, in WindowManager.LayoutParams attrs,

            in int viewVisibility, out Rect outContentInsets);

    void remove(IWindow window);

   //注意喔,这个outSurface前面的是out,表示输出参数,这个类似于C++的引用。

 int relayout(IWindow window, in WindowManager.LayoutParams attrs,

            int requestedWidth, int requestedHeight, int viewVisibility,

            boolean insetsPending, out Rect outFrame, out Rect outContentInsets,

            out Rect outVisibleInsets, out Configuration outConfig,

            out Surface outSurface);

刚才说了,JNI及其JAVA调用只是copyFrom了SurfaceControl对象到outSurface中,但是没看到哪里创建Surface。这其中的奥秘就在aidl文件编译后生成的java文件中。

你在命令行下可以输入:

aidl -Id:/android-2.2-froyo-20100625-source/source/frameworks/base/core/java/ -Id:/android-2.2-froyo-20100625-source/source/frameworks/base/Graphics/java d:/android-2.2-froyo-20100625-source/source/frameworks/base/core/java/android/view/IWindowSession.aidl test.java

以生成test.java文件。-I参数指定include目录,例如aidl有些参数是在别的java文件中指定的,那么这个-I就需要把这些目录包含进来。

先看看ViewRoot这个客户端生成的代码是什么。

public int relayout(

android.view.IWindow window,

android.view.WindowManager.LayoutParams attrs,

 int requestedWidth, int requestedHeight,

int viewVisibility, boolean insetsPending,

android.graphics.Rect outFrame,

android.graphics.Rect outContentInsets,

    android.graphics.Rect outVisibleInsets,

android.content.res.Configuration outConfig,

android.view.Surface outSurface) ---->outSurface是第11个参数

throws android.os.RemoteException

{

android.os.Parcel _data = android.os.Parcel.obtain();

android.os.Parcel _reply = android.os.Parcel.obtain();

int _result;

try {

_data.writeInterfaceToken(DESCRIPTOR);

_data.writeStrongBinder((((window!=null))?(window.asBinder()):(null)));

if ((attrs!=null)) {

_data.writeInt(1);

attrs.writeToParcel(_data, 0);

}

else {

_data.writeInt(0);

}

_data.writeInt(requestedWidth);

_data.writeInt(requestedHeight);

_data.writeInt(viewVisibility);

_data.writeInt(((insetsPending)?(1):(0)));

 //奇怪,outSurface的信息没有写到_data中。那.....

mRemote.transact(Stub.TRANSACTION_relayout, _data, _reply, 0);

_reply.readException();

_result = _reply.readInt();

if ((0!=_reply.readInt())) {

outFrame.readFromParcel(_reply);

}

....

if ((0!=_reply.readInt())) {

outSurface.readFromParcel(_reply); //从Parcel中读取信息来填充outSurface

}

}

finally {

_reply.recycle();

_data.recycle();

}

return _result;

}

真奇怪啊,Binder客户端这头竟然没有把outSurface的信息发过去。我们赶紧看看服务端。

服务端这边处理是在onTranscat函数中。

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException

{

switch (code)

{

case TRANSACTION_relayout:

{

data.enforceInterface(DESCRIPTOR);

android.view.IWindow _arg0;

android.view.Surface _arg10;

//刚才说了,Surface信息并没有传过来,那么我们在relayOut中看到的outSurface是怎么

//出来的呢?看下面这句,原来在服务端这边竟然new了一个新的Surface!!!

_arg10 = new android.view.Surface();

int _result = this.relayout(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6, _arg7, _arg8, _arg9, _arg10);

reply.writeNoException();

reply.writeInt(_result);

//_arg10是copyFrom了,那怎么传到客户端呢?

if ((_arg10!=null)) {

reply.writeInt(1);//调用Surface的writeToParcel,把信息加入reply

_arg10.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);

}

return true;

}

太诡异了!竟然有这么多花花肠子。我相信如果没有aidl的帮助,我无论如何也不会知道这其中的奥妙。

那好,我们的流程明白了。

l 客户端虽然传了一个surface,但其实没传递给服务端

l 服务端调用writeToParcel,把信息写到Parcel中,然后数据传回客户端

l 客户端调用Surface的readFromParcel,获得surface信息。

那就去看看writeToParcel吧。

[---->Surface_writeToParcel]

static void Surface_writeToParcel(

        JNIEnv* env, jobject clazz, jobject argParcel, jint flags)

{

    Parcel* parcel = (Parcel*)env->GetIntField(

            argParcel, no.native_parcel);

  

const sp<SurfaceControl>& control(getSurfaceControl(env, clazz));

//还好,只是把数据序列化到Parcel中

    SurfaceControl::writeSurfaceToParcel(control, parcel);

    if (flags & PARCELABLE_WRITE_RETURN_VALUE) {

        setSurfaceControl(env, clazz, 0);

    }

}

那看看客户端的Surface_readFromParcel吧。

[----->Surface_readFromParcel]

static void Surface_readFromParcel(

        JNIEnv* env, jobject clazz, jobject argParcel)

{

    Parcel* parcel = (Parcel*)env->GetIntField( argParcel, no.native_parcel);

  

   //客户端这边还没有surface呢

 const sp<Surface>& control(getSurface(env, clazz));

 //不过我们看到希望了,根据服务端那边Parcel信息来构造一个新的surface

    sp<Surface> rhs = new Surface(*parcel);

    if (!Surface::isSameSurface(control, rhs)) {

       setSurface(env, clazz, rhs); //把这个新surface赋给客户端。终于我们有了surface!

    }

}

到此,我们终于七拐八绕的得到了surface,这其中经历太多曲折了。下一节,我们将精简这其中复杂的操作,统一归到Native层,以这样为切入点来了解Surface的工作流程和原理。

好,反正你知道ViewRoot调用了relayout后,Surface就真正从WindowManagerService那得到了。继续回到ViewRoot,其中还有一个重要地方是我们知道却不了解的。

private void performTraversals() {

        // cache mView since it is used so much below...

        final View host = mView;

 

         boolean initialized = false;

            boolean contentInsetsChanged = false;

            boolean visibleInsetsChanged;

            try {

         relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

      // relayoutWindow完后,我们得到了一个无比宝贵的Surface

     //那我们画界面的地方在哪里?就在这个函数中,离relayoutWindow不远处。

....

  boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();

 

        if (!cancelDraw && !newSurface) {

            mFullRedrawNeeded = false;

            draw(fullRedrawNeeded); //draw?draw什么呀?

       }

    [--->ViewRoot::draw()]

private void draw(boolean fullRedrawNeeded) {

        Surface surface = mSurface; //嘿嘿,不担心了,surface资源都齐全了

        if (surface == null || !surface.isValid()) {

            return;

        }

        if (mAttachInfo.mViewScrollChanged) {

            mAttachInfo.mViewScrollChanged = false;

            mAttachInfo.mTreeObserver.dispatchOnScrollChanged();

        }

 

        int yoff;

        final boolean scrolling = mScroller != null && mScroller.computeScrollOffset();

        if (scrolling) {

            yoff = mScroller.getCurrY();

        } else {

            yoff = mScrollY;

        }

        if (mCurScrollY != yoff) {

            mCurScrollY = yoff;

            fullRedrawNeeded = true;

        }

        float appScale = mAttachInfo.mApplicationScale;

        boolean scalingRequired = mAttachInfo.mScalingRequired;

 

        Rect dirty = mDirty;

        if (mUseGL) { //我们不用OPENGL

           ...

        }

 

         Canvas canvas;

        try {

            int left = dirty.left;

            int top = dirty.top;

            int right = dirty.right;

            int bottom = dirty.bottom;

          //从Surface中锁定一块区域,这块区域是我们认为的需要重绘的区域

            canvas = surface.lockCanvas(dirty);

            // TODO: Do this in native

            canvas.setDensity(mDensity);

        }

 

        try {

            if (!dirty.isEmpty() || mIsAnimating) {

                long startTime = 0L;

                try {

                    canvas.translate(0, -yoff);

                    if (mTranslator != null) {

                        mTranslator.translateCanvas(canvas);

                    }

                    canvas.setScreenDensity(scalingRequired

                            ? DisplayMetrics.DENSITY_DEVICE : 0);

                 //mView就是之前的decoreView,

                    mView.draw(canvas);

                }

            } finally {

             //我们的图画完了,告诉surface释放这块区域

            surface.unlockCanvasAndPost(canvas);

        }

       if (scrolling) {

            mFullRedrawNeeded = true;

            scheduleTraversals();

        }

}

看起来,这个surface的用法很简单嘛:

l lockSurface,得到一个画布Canvas

l 调用View的draw,让他们在这个Canvas上尽情绘图才。另外,这个View会调用所有它的子View来画图,最终会进入到View的onDraw函数中,在这里我们可以做定制化的界面美化工作。当然,如果你想定制化整个系统画图的话,完全可以把performTranvsal看懂,然后再修改。

l unlockCanvasAndPost,告诉Surface释放这块画布

当然,这几个重要函数调用干了具体的活。这些重要函数,我们最终会精简到Native层的。

2 总结

到这里,你应该知道了一个Activity中,调用setContentView后它如何从系统中获取一块Surface,以及它是如何使用这个Surface的了。不得不说,关于UI这块,Android绝对是够复杂的。难怪2.3把UI这块代码基本重写一遍,希望能够简单精炼点。

 

Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析

在之前一篇博文中<< Android中View绘制流程以及invalidate()等相关方法分析>>,简单的阐述 了Android View 

  绘制流程的三个步骤,即:

 

                      1、  measure过程 --- 测量过程

                      2、 layout 过程     --- 布局过程
                      3、 draw 过程      --- 绘制过程

      要想对Android 中View这块深入理解,对这三个步骤地学习是必不可少的 。
 

      今天,我着重讲解下如下三个内容:

            1、 measure过程

            2、WRAP_CONTENT、MATCH_PARENT/FILL_PARENT属性的原理说明

            3、xml布局文件解析成View树的流程分析。

 

     希望对大家能有帮助。- -  分析版本基于Android 2.3


 

 1、WRAP_CONTENT、MATCH_PARENT/FILL_PARENT 

 

       初入Android殿堂的同学们,对这三个属性一定又爱又恨。爱的是使用起来挺爽地---照葫芦画瓢即可,恨的

  却是时常混淆这几个属性地意义,需要三思而后行。在带着大家重温下这几个属性的用法吧(希望我没有啰嗦)。

 

      这三个属性都用来适应视图的水平或垂直大小,一个以视图的内容或尺寸为基础的布局比精确地指定视图范围

  更加方便。

        ①  fill_parent

                设置一个视图的布局为fill_parent将强制性地使视图扩展至父元素大小。

 

        ② match_parent

               Android 中match_parent和fill_parent意思一样,但match_parent更贴切,于是从2.2开始两个词都可以

          用,但2.3版本后建议使用match_parent。

       ③ wrap_content

              自适应大小,强制性地使视图扩展以便显示其全部内容。以TextView和ImageView控件为例,设置为

         wrap_content将完整显示其内部的文本和图像。布局元素将根据内容更改大小。

       

      可不要重复造轮子,以上摘自<<Android fill_parent、wrap_content和match_parent的区别>>。

 

      当然,我们可以设置View的确切宽高,而不是由以上属性指定。

 

        android:layout_weight="wrap_content"   //自适应大小
        android:layout_weight="match_parent"   //与父视图等高
        android:layout_weight="fill_parent"	   //与父视图等高
        android:layout_weight="100dip"         //精确设置高度值为 100dip

 

 

      接下来,我们需要转换下视角,看看ViewGroup.LayoutParams类及其派生类。

 

 2、ViewGroup.LayoutParams类及其派生类

 

    2.1、  ViewGroup.LayoutParams类说明

            Android API中如下介绍:

                LayoutParams are used by views to tell their parents how they want to be laid out.


     意思大概是说: View通过LayoutParams类告诉其父视图它想要地大小(即,长度和宽度)。

 

    因此,每个View都包含一个ViewGroup.LayoutParams类或者其派生类,View类依赖于ViewGroup.LayoutParams。

        路径:frameworks\base\core\java\android\view\View.java

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
  ...
  /**
   * The layout parameters associated with this view and used by the parent
   * {@link android.view.ViewGroup} to determine how this view should be
   * laid out.
   * {@hide}
   */
  //该View拥有的 LayoutParams属性,父试图添加该View时,会为其赋值,特别注意,其类型为ViewGroup.LayoutParams。
  protected ViewGroup.LayoutParams mLayoutParams;  
  ...
}

 

     2.2、  ViewGroup.LayoutParams源码分析

      路径位于:frameworks\base\core\java\android\view\ViewGroup.java

 

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
	...
	 public static class LayoutParams {
        /**
         * Special value for the height or width requested by a View.
         * FILL_PARENT means that the view wants to be as big as its parent,
         * minus the parent's padding, if any. This value is deprecated
         * starting in API Level 8 and replaced by {@link #MATCH_PARENT}.
         */
        @Deprecated
        public static final int FILL_PARENT = -1;  // 注意值为-1,Android2.2版本不建议使用
        /**
         * Special value for the height or width requested by a View.
         * MATCH_PARENT means that the view wants to be as big as its parent,
         * minus the parent's padding, if any. Introduced in API Level 8.
         */
        public static final int MATCH_PARENT = -1; // 注意值为-1
        /**
         * Special value for the height or width requested by a View.
         * WRAP_CONTENT means that the view wants to be just large enough to fit
         * its own internal content, taking its own padding into account.
         */
        public static final int WRAP_CONTENT = -2; // 注意值为-2
        /**
         * Information about how wide the view wants to be. Can be one of the
         * constants FILL_PARENT (replaced by MATCH_PARENT ,
         * in API Level 8) or WRAP_CONTENT. or an exact size.
         */
        public int width;  //该View的宽度,可以为WRAP_CONTENT/MATCH_PARENT 或者一个具体值
        /**
         * Information about how tall the view wants to be. Can be one of the
         * constants FILL_PARENT (replaced by MATCH_PARENT ,
         * in API Level 8) or WRAP_CONTENT. or an exact size.
         */
        public int height; //该View的高度,可以为WRAP_CONTENT/MATCH_PARENT 或者一个具体值
        /**
         * Used to animate layouts.
         */
        public LayoutAnimationController.AnimationParameters layoutAnimationParameters;
        /**
         * Creates a new set of layout parameters. The values are extracted from
         * the supplied attributes set and context. The XML attributes mapped
         * to this set of layout parameters are:、
         */
        public LayoutParams(Context c, AttributeSet attrs) {
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_Layout_layout_width,
                    R.styleable.ViewGroup_Layout_layout_height);
            a.recycle();
        }

        /**
         * Creates a new set of layout parameters with the specified width
         * and height.
         */
        public LayoutParams(int width, int height) {
            this.width = width;
            this.height = height;
        }
        /**
         * Copy constructor. Clones the width and height values of the source.
         *
         * @param source The layout params to copy from.
         */
        public LayoutParams(LayoutParams source) {
            this.width = source.width;
            this.height = source.height;
        }
        /**
         * Used internally by MarginLayoutParams.
         * @hide
         */
        LayoutParams() {
        }
        /**
         * Extracts the layout parameters from the supplied attributes.
         *
         * @param a the style attributes to extract the parameters from
         * @param widthAttr the identifier of the width attribute
         * @param heightAttr the identifier of the height attribute
         */
        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            width = a.getLayoutDimension(widthAttr, "layout_width");
            height = a.getLayoutDimension(heightAttr, "layout_height");
        }
}

 

       我们发现FILL_PARENT/MATCH_PARENT值为 -1 ,WRAP_CONETENT值为-2,是不是有点诧异? 将值

  设置为负值的目的是为了区别View的具体值(an exact size) 总是大于0的。

 

       ViewGroup子类可以实现自定义LayoutParams,自定义LayoutParams提供了更好地扩展性,例如LinearLayout

 就有LinearLayout. LayoutParams自定义类(见下文)。整个LayoutParams类家族还是挺复杂的。

      ViewGroup.LayoutParams及其常用派生类的类图(部分类图)如下:

                  View 的绘制流程

 

                              该类图是在太庞大了,大家有兴趣的去看看Android API吧。

           

 

      前面我们说过,每个View都包含一个ViewGroup.LayoutParams类或者其派生类,下面我们的疑问是Android框架

 中时如何为View设置其LayoutParams属性的。

 

     有两种方法会设置View的LayoutParams属性:

       1、 直接添加子View时,常见于如下几种方法:ViewGroup.java

//Adds a child view.	
void addView(View child, int index)
//Adds a child view with this ViewGroup's default layout parameters 
//and the specified width and height.
void addView(View child, int width, int height)
//Adds a child view with the specified layout parameters.		
void addView(View child, ViewGroup.LayoutParams params)


         三个重载方法的区别只是添加View时构造LayoutParams对象的方式不同而已,稍后我们探寻一下它们的源码。

      2、 通过xml布局文件指定某个View的属性为:android:layout_heigth=””以及android:layout_weight=”” 时。

    总的来说,这两种方式都会设定View的LayoutParams属性值----指定的或者Default值。
 

  方式1流程分析

     直接添加子View时,比较容易理解,我们先来看看这种方式设置LayoutParams的过程:

         路径:\frameworks\base\core\java\android\view\ViewGroup.java

 

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
	...
    /**
     * Adds a child view. If no layout parameters are already set on the child, the
     * default parameters for this ViewGroup are set on the child.
     *
     * @param child the child view to add
     *
     * @see #generateDefaultLayoutParams()
     */
    public void addView(View child) {
        addView(child, -1);
    }
    /**
     * Adds a child view. If no layout parameters are already set on the child, the
     * default parameters for this ViewGroup are set on the child.
     *
     * @param child the child view to add
     * @param index the position at which to add the child
     *
     * @see #generateDefaultLayoutParams()
     */
    public void addView(View child, int index) {
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams(); //返回默认地LayoutParams类,作为该View的属性值
            if (params == null) {//如果不能获取到LayoutParams对象,则抛出异常。
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }
    /**
     * Adds a child view with this ViewGroup's default layout parameters and the
     * specified width and height.
     *
     * @param child the child view to add
     */
    public void addView(View child, int width, int height) {
    	//返回默认地LayoutParams类,作为该View的属性值
    	final LayoutParams params = generateDefaultLayoutParams(); 
        params.width = width;   //重新设置width值
        params.height = height; //重新设置height值
        addView(child, -1, params); //这儿,我们有指定width、height的大小了。
    }
    /**
     * Adds a child view with the specified layout parameters.
     *
     * @param child the child view to add
     * @param params the layout parameters to set on the child
     */
    public void addView(View child, LayoutParams params) {
        addView(child, -1, params);
    }
    /**
     * Adds a child view with the specified layout parameters.
     *
     * @param child the child view to add
     * @param index the position at which to add the child
     * @param params the layout parameters to set on the child
     */
    public void addView(View child, int index, LayoutParams params) {
        ...
        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
        // therefore, we call requestLayout() on ourselves before, so that the child's request
        // will be blocked at our level
        requestLayout();
        invalidate();
        addViewInner(child, index, params, false);
    }
    /**
     * Returns a set of default layout parameters. These parameters are requested
     * when the View passed to {@link #addView(View)} has no layout parameters
     * already set. If null is returned, an exception is thrown from addView.
     *
     * @return a set of default layout parameters or null
     */
    protected LayoutParams generateDefaultLayoutParams() {
        //width 为 WRAP_CONTENT大小 , height 为WRAP_CONTENT 
    	//ViewGroup的子类可以重写该方法,达到其特定要求。稍后会以LinearLayout类为例说明。
    	return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }
    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        if (!checkLayoutParams(params)) { //params对象是否为null
            params = generateLayoutParams(params); //如果params对象是为null,重新构造个LayoutParams对象
        }
        //preventRequestLayout值为false
        if (preventRequestLayout) {  
            child.mLayoutParams = params; //为View的mLayoutParams属性赋值
        } else {
            child.setLayoutParams(params);//为View的mLayoutParams属性赋值,但会调用requestLayout()请求重新布局
        }
        //if else 语句会设置View为mLayoutParams属性赋值
        ...
    }
	...
}

 

 

      主要功能就是在添加子View时为其构建了一个LayoutParams对象。但更重要的是,ViewGroup的子类可以重载

 上面的几个方法,返回特定的LayoutParams对象,例如:对于LinearLayout而言,则是LinearLayout.LayoutParams

 对象。这么做地目的是,能在其他需要它的地方,可以将其强制转换成LinearLayout.LayoutParams对象。

 

      LinearLayout重写函数地实现为:

 

public class LinearLayout extends ViewGroup {
	...
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LinearLayout.LayoutParams(getContext(), attrs);
    }
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        //该LinearLayout是水平方向还是垂直方向
    	if (mOrientation == HORIZONTAL) { 
            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        } else if (mOrientation == VERTICAL) {
            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        }
        return null;
    }
    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }
	/**
     * Per-child layout information associated with ViewLinearLayout.
     * 
     * @attr ref android.R.styleable#LinearLayout_Layout_layout_weight
     * @attr ref android.R.styleable#LinearLayout_Layout_layout_gravity
     */ //自定义的LayoutParams类
    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        /**
         * Indicates how much of the extra space in the LinearLayout will be
         * allocated to the view associated with these LayoutParams. Specify
         * 0 if the view should not be stretched. Otherwise the extra pixels
         * will be pro-rated among all views whose weight is greater than 0.
         */
        @ViewDebug.ExportedProperty(category = "layout")
        public float weight;      //  见于属性,android:layout_weight=""  ;
        /**
         * Gravity for the view associated with these LayoutParams.
         *
         * @see android.view.Gravity
         */
        public int gravity = -1;  // 见于属性, android:layout_gravity=""  ; 
        /**
         * {@inheritDoc}
         */
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a =c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
            weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
            gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);

            a.recycle();
        }
        /**
         * {@inheritDoc}
         */
        public LayoutParams(int width, int height) {
            super(width, height);
            weight = 0;
        }
        /**
         * Creates a new set of layout parameters with the specified width, height
         * and weight.
         *
         * @param width the width, either {@link #MATCH_PARENT},
         *        {@link #WRAP_CONTENT} or a fixed size in pixels
         * @param height the height, either {@link #MATCH_PARENT},
         *        {@link #WRAP_CONTENT} or a fixed size in pixels
         * @param weight the weight
         */
        public LayoutParams(int width, int height, float weight) {
            super(width, height);
            this.weight = weight;
        }
        public LayoutParams(ViewGroup.LayoutParams p) {
            super(p);
        }
        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }
    }
	...
}

 

 

       LinearLayout.LayoutParams类继承至ViewGroup.MarginLayoutParams类,添加了对android:layout_weight以及

   android:layout_gravity这两个属性的获取和保存。而且它的重写函数返回的都是LinearLayout.LayoutParams

   类型。这样,我们可以再对子View进行其他操作时,可以将将其强制转换成LinearLayout.LayoutParams对象进行

   使用。

         例如,LinearLayout进行measure过程,使用了LinearLayout.LayoutParam对象,有如下代码:

public class LinearLayout extends ViewGroup {
	...
	@Override  //onMeasure方法。
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	    //判断是垂直方向还是水平方向,这儿我们假设是VERTICAL垂直方向,
		if (mOrientation == VERTICAL) {
	        measureVertical(widthMeasureSpec, heightMeasureSpec);
	    } else {
	        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
	    }
	}
	 /**
     * Measures the children when the orientation of this LinearLayout is set
     * to {@link #VERTICAL}.
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
     * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
     *
     * @see #getOrientation()
     * @see #setOrientation(int)
     * @see #onMeasure(int, int)
     */
	  void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
	        mTotalLength = 0;
	        ...
	        // See how tall everyone is. Also remember max width.
	        for (int i = 0; i < count; ++i) {
	            final View child = getVirtualChildAt(i); //获得索引处为i的子VIew   
                ...
                //注意,我们将类型为 ViewGroup.LayoutParams的实例对象强制转换为了LinearLayout.LayoutParams,
                //即父对象转换为了子对象,能这样做的原因就是LinearLayout的所有子View的LayoutParams类型都为
                //LinearLayout.LayoutParams
	            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
                ...
	    }
	...
}

 

        超类ViewGroup.LayoutParams强制转换为了子类LinearLayout.LayoutParams,因为LinearLayout的每个

  ”直接“子View的LayoutParams属性都是LinearLayout.LayoutParams类型,因此可以安全转换。

 

 

       PS : Android 2.3源码Launcher2中也实现了自定义的LayoutParams类,在IDLE界面的每个View至少包含如下

  信息:所在X方向的单元格索引和高度、所在Y方向的单元格索引和高度等。

            路径: packages\apps\Launcher2\src\com\android\launcher2\CellLayout.java

 

public class CellLayout extends ViewGroup {
	... 
	public static class LayoutParams extends ViewGroup.MarginLayoutParams {
	        /**
	         * Horizontal location of the item in the grid.
	         */
	        public int cellX;   //X方向的单元格索引
	        /**
	         * Vertical location of the item in the grid.
	         */
	        public int cellY;   //Y方向的单元格索引
	        /**
	         * Number of cells spanned horizontally by the item.
	         */
	        public int cellHSpan;  //水平方向所占高度
	        /**
	         * Number of cells spanned vertically by the item.
	         */
	        public int cellVSpan;  //垂直方向所占高度
            ...
	        public LayoutParams(Context c, AttributeSet attrs) {
	            super(c, attrs);
	            cellHSpan = 1;  //默认为高度 1
	            cellVSpan = 1;
	        }

	        public LayoutParams(ViewGroup.LayoutParams source) {
	            super(source); //默认为高度 1
	            cellHSpan = 1;
	            cellVSpan = 1;
	        }
	        
	        public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
	            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
	            this.cellX = cellX;
	            this.cellY = cellY;
	            this.cellHSpan = cellHSpan;
	            this.cellVSpan = cellVSpan;
	        }
	        ...
	    }
	...
}

 

 

     对该自定义CellLayout.LayoutParams类的使用可以参考LinearLayout.LayoutParams类,我也不再赘述了。

 

 方法2流程分析

        使用属性android:layout_heigth=””以及android:layout_weight=”” 时,为某个View设置LayoutParams值。

 

       其实这种赋值方法其实也如同前面那种,只不过它需要一个前期孵化过程---需要利用XML解析将布局文件

  解析成一个完整的View树,可别小看它了,所有Xxx.xml的布局文件都需要解析成一个完整的View树。下面,

  我们就来仔细走这个过程,重点关注如下两个方面

         ①、xml布局是如何解析成View树的 ;

         ②、android:layout_heigth=””和android:layout_weight=””的解析。

 

        PS: 一直以来,我都想当然android:layout_heigth以及android:layout_weight这两个属性的解析过程是在

   View.java内部完成的,但当我真正去找寻时,却一直没有在View.java类或者ViewGroup.java类找到。直到一位

   网友的一次提问,才发现它们的藏身之地。

 

3、布局文件解析流程分析

 

       解析布局文件时,使用的类为LayoutInflater。 关于该类的使用请参考如下博客:

                            <android中LayoutInflater的使用 >>

      主要有如下API方法:

        public View inflate (XmlPullParser parser, ViewGroup root, boolean attachToRoot)

          public View inflate (int resource, ViewGroup root)

          public View inflate (int resource, ViewGroup root, boolean attachToRoot)

     这三个类主要迷惑之处在于地三个参数attachToRoot,即是否将该View树添加到root中去。具体可看这篇博客:

                                       <<关于inflate的第3个参数>>

    当然还有LayoutInflater的inflate()的其他重载方法,大家可以自行了解下。

     

    我利用下面的例子给大家走走这个流程 :

 

public class MainActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //1、该方法最终也会调用到 LayoutInflater的inflate()方法中去解析。
        setContentView(R.layout.main);
        
        //2、使用常见的API方法去解析xml布局文件,
        LayoutInflater layoutInflater = (LayoutInflater)getSystemService();
        View root = layoutInflater.inflate(R.layout.main, null);
    }
}

 

 

   Step 1、获得LayoutInflater的引用。

         路径:\frameworks\base\core\java\android\app\ContextImpl.java

 

/**
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 */
class ContextImpl extends Context {
	if (WINDOW_SERVICE.equals(name)) {
        return WindowManagerImpl.getDefault();
    } else if (LAYOUT_INFLATER_SERVICE.equals(name)) {
        synchronized (mSync) {
            LayoutInflater inflater = mLayoutInflater;
            //是否已经赋值,如果是,直接返回引用
            if (inflater != null) {
                return inflater;
            }
            //返回一个LayoutInflater对象,getOuterContext()指的是我们的Activity、Service或者Application引用
            mLayoutInflater = inflater = PolicyManager.makeNewLayoutInflater(getOuterContext());
            return inflater;
        }
    } else if (ACTIVITY_SERVICE.equals(name)) {
        return getActivityManager();
    }...
}

 

 

         继续去PolicyManager查询对应函数,看看内部实现。    

           路径:frameworks\base\core\java\com\android\internal\policy\PolicyManager.java

 

public final class PolicyManager {
	private static final String POLICY_IMPL_CLASS_NAME = "com.android.internal.policy.impl.Policy";
	private static final IPolicy sPolicy;   // 这可不是Binder机制额,这只是是一个接口,别想多啦
	static {
        // Pull in the actual implementation of the policy at run-time
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        }
        ...
	}
	...
	public static LayoutInflater makeNewLayoutInflater(Context context) {
        return sPolicy.makeNewLayoutInflater(context); //继续去实现类中去查找
    }
}

 

    IPolicy接口的实现对为Policy类。路径:/frameworks/base/policy/src/com/android/internal/policy/impl/Policy.java

 

//Simple implementation of the policy interface that spawns the right
//set of objects
public class Policy implements IPolicy{
	...
    public PhoneLayoutInflater makeNewLayoutInflater(Context context) {
        //实际上返回的是PhoneLayoutInflater类。
		return new PhoneLayoutInflater(context);
    }
}
//PhoneLayoutInflater继承至LayoutInflater类
public class PhoneLayoutInflater extends LayoutInflater {
	...
	/**
     * Instead of instantiating directly, you should retrieve an instance
     * through {@link Context#getSystemService}
     * 
     * @param context The Context in which in which to find resources and other
     *                application-specific things.
     * 
     * @see Context#getSystemService
     */
    public PhoneLayoutInflater(Context context) {
        super(context);
    }
	...
}

 

       LayoutInflater是个抽象类,实际上我们返回的是PhoneLayoutInflater类,但解析过程的操作基本上是在

  LayoutInflater中完成地。



   Step 2、调用inflate()方法去解析布局文件。

public abstract class LayoutInflater {
    ...
    public View inflate(int resource, ViewGroup root) {
    	//继续看下个函数,注意root为null
        return inflate(resource, root, root != null); 
    }
    
    public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
    	//获取一个XmlResourceParser来解析XML文件---布局文件。
    	//XmlResourceParser类以及xml是如何解析的,大家自己有兴趣找找。
        XmlResourceParser parser = getContext().getResources().getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
}
/**
 * The XML parsing interface returned for an XML resource.  This is a standard
 * XmlPullParser interface, as well as an extended AttributeSet interface and
 * an additional close() method on this interface for the client to indicate
 * when it is done reading the resource.
 */
public interface XmlResourceParser extends XmlPullParser, AttributeSet {
    /**
     * Close this interface to the resource.  Calls on the interface are no
     * longer value after this call.
     */
    public void close();
}


 

      我们获得了一个当前应用程序环境的XmlResourceParser对象,该对象的主要作用就是来解析xml布局文件的。

  XmlResourceParser类是个接口类,更多关于XML解析的,大家可以参考下面博客:

                              <<android之XmlResourceParser类使用实例>>

                                    <<android解析xml文件的方式(其一)>>

                                    <<android解析xml文件的方式(其二)>>

                                    <<android解析xml文件的方式(其三)>>

 

 

   Step 3 、真正地开始解析工作 。

public abstract class LayoutInflater {
    ...
    /**
     * Inflate a new view hierarchy from the specified XML node. Throws
     * {@link InflateException} if there is an error.
     */
    //我们传递过来的参数如下: root 为null , attachToRoot为false 。
    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context)mConstructorArgs[0];
            mConstructorArgs[0] = mContext;  //该mConstructorArgs属性最后会作为参数传递给View的构造函数
            View result = root;  //根View

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                ...
                final String name = parser.getName();  //节点名,即API中的控件或者自定义View完整限定名。
                if (TAG_MERGE.equals(name)) { // 处理<merge />标签
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    //将<merge />标签的View树添加至root中,该函数稍后讲到。
                    rInflate(parser, root, attrs);
                } else {
                    // Temp is the root view that was found in the xml
                	//创建该xml布局文件所对应的根View。
                    View temp = createViewFromTag(name, attrs); 

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        // Create layout params that match root, if supplied
                    	//根据AttributeSet属性获得一个LayoutParams实例,记住调用者为root。
                        params = root.generateLayoutParams(attrs); 
                        if (!attachToRoot) { //重新设置temp的LayoutParams
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }
                    // Inflate all children under temp
                    //添加所有其子节点,即添加所有字View
                    rInflate(parser, temp, attrs);
                    
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            } 
            ...
            return result;
        }
    }
    
    /*
     * default visibility so the BridgeInflater can override it.
     */
    View createViewFromTag(String name, AttributeSet attrs) {
    	//节点是否为View,如果是将其重新赋值,形如 <View class="com.qin.xxxView"></View>
        if (name.equals("view")) {  
            name = attrs.getAttributeValue(null, "class");
        }
        try {
            View view = (mFactory == null) ? null : mFactory.onCreateView(name,
                    mContext, attrs);  //没有设置工厂方法

            if (view == null) {
                //通过这个判断是Android API的View,还是自定义View
            	if (-1 == name.indexOf('.')) {
                    view = onCreateView(name, attrs); //创建Android API的View实例
                } else {
                    view = createView(name, null, attrs);//创建一个自定义View实例
                }
            }
            return view;
        } 
        ...
    }
    //获得具体视图的实例对象
    public final View createView(String name, String prefix, AttributeSet attrs) {
		Constructor constructor = sConstructorMap.get(name);
		Class clazz = null;
		//以下功能主要是获取如下三个类对象:
		//1、类加载器  ClassLoader
		//2、Class对象
		//3、类的构造方法句柄 Constructor
		try {
		    if (constructor == null) {
		    // Class not found in the cache, see if it's real, and try to add it
		    clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name);
		    ...
		    constructor = clazz.getConstructor(mConstructorSignature);
		    sConstructorMap.put(name, constructor);
		} else {
		    // If we have a filter, apply it to cached constructor
		    if (mFilter != null) {
		        ...   
		    }
		}
		    //传递参数获得该View实例对象
		    Object[] args = mConstructorArgs;
		    args[1] = attrs;
		    return (View) constructor.newInstance(args);
		} 
		...
	}

}

 

     这段代码的作用是获取xml布局文件的root View,做了如下两件事情

          1、获取xml布局的View实例,通过createViewFromTag()方法获取,该方法会判断节点名是API 控件

            还是自定义控件,继而调用合适的方法去实例化View。

          2、判断root以及attachToRoot参数,重新设置root View值以及temp变量的LayoutParams值。

 

        如果仔细看着段代码,不知大家心里有没有疑惑:当root为null时,我们的temp变量的LayoutParams值是为

  null的,即它不会被赋值?有个View的LayoutParams值为空,那么,在系统中不会报异常吗?见下面部分

  代码:

    //我们传递过来的参数如下: root 为null , attachToRoot为false 。
    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...
            try {
                
                ...
                if (TAG_MERGE.equals(name)) { // 处理<merge />标签
                    ...
                } else {
                    // Temp is the root view that was found in the xml
                	//创建该xml布局文件所对应的根View。
                    View temp = createViewFromTag(name, attrs); 
                    ViewGroup.LayoutParams params = null;

                    //注意!!! root为null时,temp变量的LayoutParams属性不会被赋值的。
                    if (root != null) {
                        // Create layout params that match root, if supplied
                    	//根据AttributeSet属性获得一个LayoutParams实例,记住调用者为root。
                        params = root.generateLayoutParams(attrs); 
                        if (!attachToRoot) { //重新设置temp的LayoutParams
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }
                    ...
                }
            } 
            ...
        }
    }

 

 

        关于这个问题的详细答案,我会在后面讲到。这儿我简单说下,任何View树的顶层View被添加至窗口时,

  一般调用WindowManager.addView()添加至窗口时,在这个方法中去做进一步处理。即使,LayoutParams

  值为空,UI框架每次measure()时都忽略该View的LayoutParams值,而是直接传递MeasureSpec值至View树。

 

       接下来,我们关注另外一个函数,rInflate(),该方法会递归调用每个View下的子节点,以当前View作为根View

 形成一个View树。

 

    /**
     * Recursive method used to descend down the xml hierarchy and instantiate
     * views, instantiate their children, and then call onFinishInflate().
     */
    //递归调用每个字节点
    private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)
            throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            final String name = parser.getName();
            
            if (TAG_REQUEST_FOCUS.equals(name)) { //处理<requestFocus />标签
                parseRequestFocus(parser, parent);
            } else if (TAG_INCLUDE.equals(name)) { //处理<include />标签
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, parent, attrs);//解析<include />节点
            } else if (TAG_MERGE.equals(name)) { //处理<merge />标签
                throw new InflateException("<merge /> must be the root element");
            } else {
            	//根据节点名构建一个View实例对象
                final View view = createViewFromTag(name, attrs); 
                final ViewGroup viewGroup = (ViewGroup) parent;
                //调用generateLayoutParams()方法返回一个LayoutParams实例对象,
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflate(parser, view, attrs); //继续递归调用
                viewGroup.addView(view, params); //OK,将该View以特定LayoutParams值添加至父View中
            }
        }
        parent.onFinishInflate();  //完成了解析过程,通知....
    }

 

 

          值得注意的是,每次addView前都调用了viewGroup.generateLayoutParams(attrs)去构建一个LayoutParams

  实例,然后在addView()方法中为其赋值。参见如下代码:ViewGroup.java

  

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
	...
	
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }
	public static class LayoutParams {
        ... //会调用这个构造函数
        public LayoutParams(Context c, AttributeSet attrs) {
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_Layout_layout_width,
                    R.styleable.ViewGroup_Layout_layout_height);
            a.recycle();
        }
        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            width = a.getLayoutDimension(widthAttr, "layout_width");
            height = a.getLayoutDimension(heightAttr, "layout_height");
        }
	
}

 

 

    好吧 ~~ 我们还是探寻根底,去TypeArray类的getLayoutDimension()看看。

         路径:/frameworks/base/core/java/android/content/res/TypedArray.java

 

public class TypedArray {
	...
	/**
     * Special version of {@link #getDimensionPixelSize} for retrieving
     * {@link android.view.ViewGroup}'s layout_width and layout_height
     * attributes.  This is only here for performance reasons; applications
     * should use {@link #getDimensionPixelSize}.
     * 
     * @param index Index of the attribute to retrieve.
     * @param name Textual name of attribute for error reporting.
     * 
     * @return Attribute dimension value multiplied by the appropriate 
     * metric and truncated to integer pixels.
     */
    public int getLayoutDimension(int index, String name) {
        index *= AssetManager.STYLE_NUM_ENTRIES;
        final int[] data = mData;
        //获得属性对应的标识符 , Identifies,目前还没有仔细研究相关类。
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type >= TypedValue.TYPE_FIRST_INT
                && type <= TypedValue.TYPE_LAST_INT) {
            return data[index+AssetManager.STYLE_DATA];
        } else if (type == TypedValue.TYPE_DIMENSION) { //类型为dimension类型
            return TypedValue.complexToDimensionPixelSize(
                data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
        }
        //没有提供layout_weight和layout_height会来到此处 ,这儿会报异常!
        //因此布局文件中的View包括自定义View必须加上属性layout_weight和layout_height。
        throw new RuntimeException(getPositionDescription()
                + ": You must supply a " + name + " attribute.");
    }
	...
}


         从上面得知,  我们将View的AttributeSet属性传递给generateLayoutParams()方法,让其构建合适地

 

   LayoutParams对象,并且初始化属性值weight和height。同时我们也得知 布局文件中的View包括自定义View

   必须加上属性layout_weight和layout_height,否则会报异常。

 

    Step 3 主要做了如下事情:
       首先,获得了了布局文件地root View,即布局文件中最顶层的View。

       其次,通过递归调用,我们形成了整个View树以及设置了每个View的LayoutParams对象。

 

    

    总结:通过对布局文件的解析流程的学习,也就是转换为View树的过程,我们明白了解析过程的个中奥妙,以及

设置ViewLayoutParams对象的过程。但是,我们这儿只是简单的浮光掠影,更深层次的内容希望大家能深入学习。



 

      本来是准备接下去往下写的,但无奈贴出来的代码太多,文章有点长而且自己也有点凌乱了,因此决定做两篇

  博客发表吧。下篇内容包括如下方面:

        1、MeasureSpec类说明 ;

        2、measure过程中如何正确设置每个View的长宽 ;

        3、UI框架正确设置顶层View的LayoutParams对象,对Activity而言,顶层View则是DecorView,

   其他的皆是普通View了。

 

主要知识点如下:
                 1、MeasureSpc类说明
                 2、measure过程详解(揭秘其细节);
                 3、root View被添加至窗口时,UI框架是如何设置其LayoutParams值得。
 

       在讲解measure过程前,我们非常有必要理解MeasureSpc类的使用,否则理解起来也只能算是囫囵吞枣。

 

 

 1、MeasureSpc类说明

 

   1.1  SDK 说明如下

              A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec

 

         represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and

         a mode.

        即:
             MeasureSpc类封装了父View传递给子View的布局(layout)要求。每个MeasureSpc实例代表宽度或者高度

   (只能是其一)要求。 它有三种模式:

            ①、UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;

            ②、EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;

            ③、AT_MOST(至多),子元素至多达到指定大小的值。

 

   常用的三个函数:

  static int getMode(int measureSpec)  :  根据提供的测量值(格式)提取模式(上述三个模式之一)

     static int getSize(int measureSpec)  : 根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)

     static int makeMeasureSpec(int size,int mode)  :  根据提供的大小值和模式创建一个测量值(格式)


             以上摘取自:  <<MeasureSpec介绍及使用详解>>
 

   1.2   MeasureSpc类源码分析   其为View.java类的内部类,路径:\frameworks\base\core\java\android\view\View.java

public class View implements ... {
	 ...
	 public static class MeasureSpec {
        private static final int MODE_SHIFT = 30; //移位位数为30
        //int类型占32位,向右移位30位,该属性表示掩码值,用来与size和mode进行"&"运算,获取对应值。
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        //向右移位30位,其值为00 + (30位0)  , 即 0x0000(16进制表示)
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        //向右移位30位,其值为01 + (30位0)  , 即0x1000(16进制表示)
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        //向右移位30位,其值为02 + (30位0)  , 即0x2000(16进制表示)
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        //创建一个整形值,其高两位代表mode类型,其余30位代表长或宽的实际值。可以是WRAP_CONTENT、MATCH_PARENT或具体大小exactly size
        public static int makeMeasureSpec(int size, int mode) {
            return size + mode;
        }
        //获取模式  ,与运算
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
        //获取长或宽的实际值 ,与运算
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

    }
	...
}


    MeasureSpec类的处理思路是:

 

      ①、右移运算,使int 类型的高两位表示模式的实际值,其余30位表示其余30位代表长或宽的实际值----可以是

         WRAP_CONTENT、MATCH_PARENT或具体大小exactly size。


      ②、通过掩码MODE_MASK进行与运算 “&”,取得模式(mode)以及长或宽(value)的实际值。
 

 

 2、measure过程详解

 

 
   2.1  measure过程深入分析

 

       之前的一篇博文<< Android中View绘制流程以及invalidate()等相关方法分析>>,我们从”二B程序员”的角度简单    解了measure过程的调用过程。过了这么多,我们也该升级了,- - 。现在请开始从”普通程序员”角度去理解这个

 过程。我们重点查看measure过程中地相关方法。

     我们说过,当UI框架开始绘制时,皆是从ViewRoot.java类开始绘制的。


      ViewRoot类简要说明: 任何显示在设备中的窗口,例如:Activity、Dialog等,都包含一个ViewRoot实例,该

  类主要用来与远端 WindowManagerService交互以及控制(开始/销毁)绘制。


     Step 1、 开始UI绘制 , 具体绘制方法则是:

路径:\frameworks\base\core\java\android\view\ViewRoot.java
public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks {
    ...
    //mView对象指添加至窗口的root View ,对Activity窗口而言,则是DecorView对象。
    View mView;    
    
    //开始View绘制流程
    private void performTraversals(){
    	...
    	//这两个值我们在后面讨论时,在回过头来看看是怎么赋值的。现在只需要记住其值MeasureSpec.makeMeasureSpec()构建的。
    	int childWidthMeasureSpec; //其值由MeasureSpec类构建 , makeMeasureSpec
        int childHeightMeasureSpec;//其值由MeasureSpec类构建 , makeMeasureSpec
        

        // Ask host how big it wants to be
        host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    	...
    }
    ...
}

 

   

      这儿,我并没有说出childWidthMeasureSpec和childHeightMeasureSpec类的来由(为了避免额外地开销,等到

 第三部分时我们在来攻克它,现在只需要记住其值MeasureSpec.makeMeasureSpec()构建的。

 

 

    Step 2 、调用measure()方法去做一些前期准备

       measure()方法原型定义在View.java类中,final修饰符修饰,其不能被重载:

    

public class View implements ... {
   	...
    /**
     * This is called to find out how big a view should be. The parent
     * supplies constraint information in the width and height parameters.
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the
     *        parent
     * @param heightMeasureSpec Vertical space requirements as imposed by the
     *        parent
     * @see #onMeasure(int, int)
     */
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
   		//判断是否为强制布局,即带有“FORCE_LAYOUT”标记 以及 widthMeasureSpec或heightMeasureSpec发生了改变
        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
        	//清除MEASURED_DIMENSION_SET标记   ,该标记会在onMeasure()方法后被设置
            mPrivateFlags &= ~MEASURED_DIMENSION_SET; 

            // measure ourselves, this should set the measured dimension flag back
            // 1、 测量该View本身的大小 ; 2 、 设置MEASURED_DIMENSION_SET标记,否则接写来会报异常。
            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;  //下一步是layout了,添加LAYOUT_REQUIRED标记
        }

        mOldWidthMeasureSpec = widthMeasureSpec;   //保存值
        mOldHeightMeasureSpec = heightMeasureSpec; //保存值
    }
   	...
}

 

 

      参数widthMeasureSpec和heightMeasureSpec 由父View构建,表示父View给子View的测量要求。其值地构建

 会在下面步骤中详解。  

   measure()方法显示判断是否需要重新调用设置改View大小,即调用onMeasure()方法,然后操作两个标识符:

            ①、重置MEASURED_DIMENSION_SET   : onMeasure()方法中,需要添加该标识符,否则,会报异常;    

       ②、添加LAYOUT_REQUIRED : 表示需要进行layout操作。

 

    最后,保存当前的widthMeasureSpec和heightMeasureSpec值。

 

   Step 3 、调用onMeasure()方法去真正设置View的长宽值,其默认实现为:

 

 /**
    * Measure the view and its content to determine the measured width and the
    * measured height. This method is invoked by {@link #measure(int, int)} and
    * should be overriden by subclasses to provide accurate and efficient
    * measurement of their contents.
    * 
    * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
    *                         The requirements are encoded with
    * @param heightMeasureSpec vertical space requirements as imposed by the parent.
    *                         The requirements are encoded with
    */
   //设置该View本身地大小
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
               getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
   }
   
   /**
    * Utility to return a default size. Uses the supplied size if the
    * MeasureSpec imposed no contraints. Will get larger if allowed
    * by the MeasureSpec.
    *
    * @param size Default size for this view
    * @param measureSpec Constraints imposed by the parent
    * @return The size this view should be.
    */
   //@param size参数一般表示设置了android:minHeight属性或者该View背景图片的大小值
   public static int getDefaultSize(int size, int measureSpec) {
       int result = size;  
       int specMode = MeasureSpec.getMode(measureSpec);
       int specSize =  MeasureSpec.getSize(measureSpec);

       //根据不同的mode值,取得宽和高的实际值。
       switch (specMode) {
       case MeasureSpec.UNSPECIFIED:  //表示该View的大小父视图未定,设置为默认值
           result = size;
           break;
       case MeasureSpec.AT_MOST:      //表示该View的大小由父视图指定了
       case MeasureSpec.EXACTLY:
           result = specSize;
           break;
       }
       return result;
   }
   //获得设置了android:minHeight属性或者该View背景图片的大小值, 最为该View的参考值
   protected int getSuggestedMinimumWidth() {
       int suggestedMinWidth = mMinWidth;  //  android:minHeight

       if (mBGDrawable != null) { // 背景图片对应地Width。
           final int bgMinWidth = mBGDrawable.getMinimumWidth();
           if (suggestedMinWidth < bgMinWidth) {
               suggestedMinWidth = bgMinWidth;
           }
       }

       return suggestedMinWidth;
   }
   //设置View在measure过程中宽和高
   protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
       mMeasuredWidth = measuredWidth;
       mMeasuredHeight = measuredHeight;

       mPrivateFlags |= MEASURED_DIMENSION_SET;  //设置了MEASURED_DIMENSION_SET标记
   }

 

 

       主要功能就是根据该View属性(android:minWidth和背景图片大小)和父View对该子View的"测量要求",设置该      View的 mMeasuredWidth 和 mMeasuredHeight 值。

 

       这儿只是一般的View类型地实现方法。一般来说,父View,也就是ViewGroup类型,都需要在重写onMeasure()   方法,遍历所有子View,设置每个子View的大小。基本思想如下:遍历所有子View,设置每个子View的大小。伪

  代码表示为:

 

   //某个ViewGroup类型的视图
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	    //必须调用super.ononMeasure()或者直接调用setMeasuredDimension()方法设置该View大小,否则会报异常。
	    super.onMeasure(widthMeasureSpec , heightMeasureSpec)
        //setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
        //        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        
	    //遍历每个子View
	    for(int i = 0 ; i < getChildCount() ; i++){
	    	View child = getChildAt(i);
	    	//调用子View的onMeasure,设置他们的大小。childWidthMeasureSpec , childHeightMeasureSpec ?
	    	child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
	    }
   }


 

 

      Step 2、Step 3 代码也比较好理解,但问题是我们示例代码中widthMeasureSpec、heightMeasureSpec是如何

 确定的呢?父View是如何设定其值的?

  

      要想回答这个问题,我们看是去源代码里找找答案吧。在ViewGroup.java类中,为我们提供了三个方法,去设置

每个子View的大小,基本思想也如同我们之前描述的思想:遍历所有子View,设置每个子View的大小。

     主要有如下方法:

/**
 * Ask all of the children of this view to measure themselves, taking into
 * account both the MeasureSpec requirements for this view and its padding.
 * We skip children that are in the GONE state The heavy lifting is done in
 * getChildMeasureSpec.
 */
//widthMeasureSpec 和  heightMeasureSpec 表示该父View的布局要求
//遍历每个子View,然后调用measureChild()方法去实现每个子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) { // 不处于 “GONE” 状态
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}
   
/**
 * Ask one of the children of this view to measure itself, taking into
 * account both the MeasureSpec requirements for this view and its padding.
 * The heavy lifting is done in getChildMeasureSpec.
 *
 * @param child The child to measure
 * @param parentWidthMeasureSpec The width requirements for this view
 * @param parentHeightMeasureSpec The height requirements for this view
 */
//测量每个子View高宽时,清楚了该View本身的边距大小,即android:padding属性 或android:paddingLeft等属性标记
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams(); // LayoutParams属性
    //设置子View的childWidthMeasureSpec属性,去除了该父View的边距值  mPaddingLeft + mPaddingRight
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    //设置子View的childHeightMeasureSpec属性,去除了该父View的边距值  mPaddingTop + mPaddingBottom
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

  

 

     measureChildren()方法:遍历所有子View,调用measureChild()方法去设置该子View的属性值。

     measureChild()  方法   : 获取特定子View的widthMeasureSpec、heightMeasureSpec,调用measure()方法

 设置子View的实际宽高值。

    getChildMeasureSpec()就是获取子View的widthMeasureSpec、heightMeasureSpec值。

  

/**
 * Does the hard part of measureChildren: figuring out the MeasureSpec to
 * pass to a particular child. This method figures out the right MeasureSpec
 * for one dimension (height or width) of one child view.
 *
 * The goal is to combine information from our MeasureSpec with the
 * LayoutParams of the child to get the best possible results.
 */
// spec参数                                    表示该父View本身所占的widthMeasureSpec 或  heightMeasureSpec值
// padding参数                          表示该父View的边距大小,见于android:padding属性 或android:paddingLeft等属性标记
// childDimension参数  表示该子View内部LayoutParams属性的值,可以是wrap_content、match_parent、一个精确指(an exactly size),
//           例如:由android:width指定等。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);  //获得父View的mode
    int specSize = MeasureSpec.getSize(spec);  //获得父View的实际值

    int size = Math.max(0, specSize - padding); //父View为子View设定的大小,减去边距值,

    int resultSize = 0;    //子View对应地 size 实际值 ,由下面的逻辑条件赋值
    int resultMode = 0;    //子View对应地 mode 值 , 由下面的逻辑条件赋值

    switch (specMode) {
    // Parent has imposed an exact size on us
    //1、父View是EXACTLY的 !
    case MeasureSpec.EXACTLY: 
    	//1.1、子View的width或height是个精确值 (an exactly size)
        if (childDimension >= 0) {          
            resultSize = childDimension;         //size为精确值
            resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。
        } 
        //1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT 
        else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size. So be it.
            resultSize = size;                   //size为父视图大小
            resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。
        } 
        //1.3、子View的width或height为 WRAP_CONTENT
        else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;                   //size为父视图大小
            resultMode = MeasureSpec.AT_MOST;    //mode为AT_MOST 。
        }
        break;

    // Parent has imposed a maximum size on us
    //2、父View是AT_MOST的 !    
    case MeasureSpec.AT_MOST:
    	//2.1、子View的width或height是个精确值 (an exactly size)
        if (childDimension >= 0) {
            // Child wants a specific size... so be it
            resultSize = childDimension;        //size为精确值
            resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY 。
        }
        //2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
        else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size, but our size is not fixed.
            // Constrain child to not be bigger than us.
            resultSize = size;                  //size为父视图大小
            resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST
        }
        //2.3、子View的width或height为 WRAP_CONTENT
        else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;                  //size为父视图大小
            resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST
        }
        break;

    // Parent asked to see how big we want to be
    //3、父View是UNSPECIFIED的 !
    case MeasureSpec.UNSPECIFIED:
    	//3.1、子View的width或height是个精确值 (an exactly size)
        if (childDimension >= 0) {
            // Child wants a specific size... let him have it
            resultSize = childDimension;        //size为精确值
            resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY
        }
        //3.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
        else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size... find out how big it should
            // be
            resultSize = 0;                        //size为0! ,其值未定
            resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED
        } 
        //3.3、子View的width或height为 WRAP_CONTENT
        else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = 0;                        //size为0! ,其值未定
            resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED
        }
        break;
    }
    //根据上面逻辑条件获取的mode和size构建MeasureSpec对象。
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

 

       为了便于分析,我将上面的逻辑判断语句使用列表项进行了说明.

 

  getChildMeasureSpec()方法的主要功能如下:


        根据父View的measureSpec值(widthMeasureSpec,heightMeasureSpec)值以及子View的子View内部

  LayoutParams属性值,共同决定子View的measureSpec值的大小。主要判断条件主要为MeasureSpec的mode

 类型以及LayoutParams的宽高实际值(lp.width,lp.height),见于以上所贴代码中的列表项: 1、 1.1 ; 1.2 ; 1.3 ; 

  2、2.1等。

 

        例如,分析列表3:假设当父View为MeasureSpec.UNSPECIFIED类型,即未定义时,只有当子View的width

 或height指定时,其mode才为MeasureSpec.EXACTLY,否者该View size为 0 ,mode为MeasureSpec.UNSPECIFIED时

 ,即处于未指定状态。

      由此可以得出, 每个View大小的设定都事由其父View以及该View共同决定的。但这只是一个期望的大小,每个

 View在测量时最终大小的设定是由setMeasuredDimension()最终决定的。因此,最终确定一个View的“测量长宽“是

 由以下几个方面影响:

        1、父View的MeasureSpec属性;

        2、子View的LayoutParams属性 ;

        3、setMeasuredDimension()或者其它类似设定 mMeasuredWidth 和 mMeasuredHeight 值的方法。

                setMeasuredDimension()原型:

   //设置View在measure过程中宽和高
   protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
       mMeasuredWidth = measuredWidth;
       mMeasuredHeight = measuredHeight;

       mPrivateFlags |= MEASURED_DIMENSION_SET;  //设置了MEASURED_DIMENSION_SET标记
   }

 

  将上面列表项转换为表格为:

                              View 的绘制流程

   这张表格更能帮助我们分析View的MeasureSpec的确定条件关系。

 

   为了帮助大家理解,下面我们分析某个窗口使用地xml布局文件,我们弄清楚该xml布局文件中每个View的

MeasureSpec值的组成。

    

   <?xml version="1.0" encoding="utf-8"?>
   <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:id="@+id/llayout"
   	   android:orientation="vertical" 
       android:layout_width="match_parent"
   	   android:layout_height="match_parent">
   	
   	
   	<TextView android:id="@+id/tv" 
   	    android:layout_width="match_parent"
   		android:layout_height="wrap_content"
   	    android:text="@string/hello" />

   </LinearLayout>   

 

 

     该布局文件共有两个View:  ①、id为llayout的LinearLayout布局控件 ;

                                                   ②、id为tv的TextView控件。


      假设LinearLayout的父View对应地widthSpec和heightSpec值皆为MeasureSpec.EXACTLY类型(Activity窗口

  的父View为DecorView,具体原因见第三部分说明)。

 

       对LinearLayout而言比较简单,由于 android:layout_width="match_parent",因此其width对应地widthSpec 

  mode值为MeasureSpec.EXACTLY , size由父视图大小指定 ;  由于android:layout_height = "match_parent",

  因此其height对应地heightSpec mode值为MeasureSpec.EXACTLY,size由父视图大小指定 ;


       对TextView而言 ,其父View为LinearLayout的widthSpec和heightSpec值皆为MeasureSpec.EXACTLY类型,

 由于android:layout_width="match_parent" , 因此其width对应地widthSpec mode值为MeasureSpec.EXACTLY,

 size由父视图大小指定 ;  由于android:layout_width="wrap_content" , 因此其height对应地widthSpec mode值为

 MeasureSpec.AT_MOST,size由父视图大小指定 。

 

    我们继续窥测下LinearLayout类是如何进行measure过程的:

 

   public class LinearLayout extends ViewGroup {
		...
		@Override  //onMeasure方法。
		protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		    //判断是垂直方向还是水平方向,这儿我们假设是VERTICAL垂直方向,
			if (mOrientation == VERTICAL) {
		        measureVertical(widthMeasureSpec, heightMeasureSpec);
		    } else {
		        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
		    }
		}
		//垂直方向布局
	    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
	        mTotalLength = 0;         //该LinearLayout测量子View时的总高度。
	    	float totalWeight = 0;    //所有子View的权重和 , android:layout_weight
	    	int maxWidth = 0;         //保存子View中最大width值
	        ...
	        final int count = getVirtualChildCount();  //子View的个数
	        
	        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
	        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            ...
	        // See how tall everyone is. Also remember max width.
	        for (int i = 0; i < count; ++i) {
	            final View child = getVirtualChildAt(i);
                ...
	            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

	            totalWeight += lp.weight;  
	            //满足该条件地View会在该LinearLayout有剩余高度时,才真正调用measure()
	            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
	                ...
	            } else {
	                int oldHeight = Integer.MIN_VALUE;
	                //如果View的hight值为0,并且设置了android:layout_weight属性,重新纠正其height值为WRAP_CONTENT
	                if (lp.height == 0 && lp.weight > 0) {
	                    oldHeight = 0;
	                    lp.height = LayoutParams.WRAP_CONTENT;
	                }
	                // Determine how big this child would like to be. If this or
	                // previous children have given a weight, then we allow it to
	                // use all available space (and we will shrink things later
	                // if needed).
	                //对每个子View调用measure()方法
	                measureChildBeforeLayout(
	                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
	                       totalWeight == 0 ? mTotalLength : 0);
	                
	                //这三行代码做了如下两件事情:
	                //1、获得该View的measuredHeight值,每个View都会根据他们地属性正确设置值  > 0 ;
	                //2、更新mTotalLength值:取当前高度mTotalLength值与mTotalLength + childHeight 的最大值
	                // 于是对于android:layout_height="wrap_height"属性地LinearLayout控件也就知道了它的确切高度值了。
	                final int childHeight = child.getMeasuredHeight();
	                final int totalLength = mTotalLength;
	                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
	                       lp.bottomMargin + getNextLocationOffset(child));
	                ...
	            }
	            final int margin = lp.leftMargin + lp.rightMargin;
	            final int measuredWidth = child.getMeasuredWidth() + margin;
	            maxWidth = Math.max(maxWidth, measuredWidth);
	            ...
	        }
            //后续还有很多处理,包括继续measure()某些符合条件地子View
	        ...
	    }
	    void measureChildBeforeLayout(View child, int childIndex,
	            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
	            int totalHeight) {
	    	//调用measureChildWithMargins()方法去设置子View大小
	        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
	                heightMeasureSpec, totalHeight);
	    }
		...
	}


          

        继续看看measureChildWithMargins()方法,该方法定义在ViewGroup.java内,基本流程同于measureChild()方法,但添加了对子View Margin的处理,即:android:margin属性或者android:marginLeft等属性的处理。

      [email protected] 

 

	/**
	 * Ask one of the children of this view to measure itself, taking into
	 * account both the MeasureSpec requirements for this view and its padding
	 * and margins. The child must have MarginLayoutParams The heavy lifting is
	 * done in getChildMeasureSpec.
	 */
	//基本流程同于measureChild()方法,但添加了对子View Margin的处理,即:android:margin属性或者android:marginLeft等属性的处理
	//widthUsed参数  表示该父View已经使用的宽度
	//heightUsed参数  表示该父View已经使用的高度
	protected void measureChildWithMargins(View child,
	        int parentWidthMeasureSpec, int widthUsed,
	        int parentHeightMeasureSpec, int heightUsed) {
	    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

	    //获得子View的childWidthMeasureSpec和childHeightMeasureSpec值
	    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);
	}

 

 

    measure()过程时,LinearLayout类做了如下事情 :

            1、遍历每个子View,对其调用measure()方法;

            2、子View measure()完成后,需要取得该子View地宽高实际值,继而做处理(例如:LinearLayout属性为

       android:widht="wrap_content"时,LinearLayout的实际width值则是每个子View的width值的累加值)。

     

  2.2 WRAP_CONTENT、MATCH_PARENT以及measure动机揭秘

 

        子View地宽高实际值 ,即child.getMeasuredWidth()值得返回最终会是一个确定值?  难道WRAP_CONTENT(

其值为-2) 、MATCH_PARENT(值为-1)或者说一个具体值(an exactly size > 0)。前面我们说过,View最终“测量”值的

确定是有三个部分组成地:

 

         ①、父View的MeasureSpec属性;

         ②、子View的LayoutParams属性 ;

         ③、setMeasuredDimension()或者其它类似设定 mMeasuredWidth 和 mMeasuredHeight 值的方法。

   因此,一个View必须以某种合适地方法确定它地最终大小。例如,如下自定义View:

 

//自定义View	
public Class MyView extends View {
	
	 //针对不同地mode值,设置本View地大小
	 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
		 //获得父View传递给我们地测量需求
		 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
	     int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		 
	     int width = 0 ;
	     int height = 0 ;
	     //对UNSPECIFIED 则抛出异常
	     if(widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED)
	         throw new RuntimeException("widthMode or heightMode cannot be UNSPECIFIED");
	    
	     //精确指定
	     if(widthMode == MeasureSpec.EXACTLY){
	    	 width = 100 ;
	     }
	     //模糊指定
	     else if(widthMode == MeasureSpec.AT_MOST )
	    	 width = 50 ; 
	     
	      //精确指定
	     if(heightMode == MeasureSpec.EXACTLY){
	    	 height = 100 ;
	     }
	     //模糊指定
	     else if(heightMode == MeasureSpec.AT_MOST )
	    	 height = 50 ;
	     
	     setMeasuredDimension(width , height) ;
	 }
}

 

 

         该自定义View重写了onMeasure()方法,根据传递过来的widthMeasureSpec和heightMeasureSpec简单设置了

 

 该View的mMeasuredWidth 和 mMeasuredHeight值。

      对于TextView而言,如果它地mode不是Exactly类型 , 它会根据一些属性,例如:android:textStyle

  、android:textSizeandroid:typeface等去确定TextView类地需要占用地长和宽。

   

     因此,如果你地自定义View必须手动对不同mode做出处理。否则,则是mode对你而言是无效的。

   

      Android框架中提供地一系列View/ViewGroup都需要去进行这个measure()过程地 ,因为在layout()过程中,父

  View需要调用getMeasuredWidth()或getMeasuredHeight()去为每个子View设置他们地布局坐标,只有确定布局

  坐标后,才能真正地将该View 绘制(draw)出来,否则该View的layout大小为0,得不到期望效果。我们继续看看

  LinearLayout的layout布局过程:

public class LinearLayout extends ViewGroup {
	...
    @Override  //layout 过程
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
		//假定是垂直方向布局
        if (mOrientation == VERTICAL) {
            layoutVertical();
        } else {
            layoutHorizontal();
        }
    }
	//对每个子View调用layout过程
    void layoutVertical() {
        ...
        final int count = getVirtualChildCount();
        ...
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {  //一般为非null
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
            	//获得子View测量时的实际宽高值,
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                
                ...
                //  封装了child.layout()方法,见如下
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight); 
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }
    //width = getMeasuredWidth() ; height = childHeight(); View的大小就是测量大小
    private void setChildFrame(View child, int left, int top, int width, int height) {
    	
        child.layout(left, top, left + width, top + height);
    }
	...
}	

 

 

 

      对一个View进行measure操作地主要目的就是为了确定该View地布局大小,见上面所示代码。但measure操作

 通常是耗时的,因此对自定义ViewGroup而言,我们可以自由控制measure、layout过程,如果我们知道如何layout

 一个View,我们可以跳过该ViewGroup地measure操作(onMeasure()方法中measure所有子View地),直接去layout

 

      在前面一篇博客<<Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用说明>>中,我们自定义了一个     ViewGroup,  并且重写了onMeasure()和onLayout()方法去分别操作每个View。就该ViewGroup而言,我们只需要

  重写onLayout()操作即可,因为我们知道如何layout每个子View。如下代码所示:

 

//自定义ViewGroup , 包含了三个LinearLayout控件,存放在不同的布局位置
public class MultiViewGroup extends ViewGroup {
	private void init() {
		// 初始化3个 LinearLayout控件
		LinearLayout oneLL = new LinearLayout(mContext);
		oneLL.setBackgroundColor(Color.RED);
        addView(oneLL);
        ...
	}
	@Override
	// 我们知晓每个子View的layout布局大小,因此我们不需要为每个子View进行measure()操作了。
//	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//		setMeasuredDimension(width, height);
//		// 设置该ViewGroup的大小
//		int width = MeasureSpec.getSize(widthMeasureSpec);
//		int height = MeasureSpec.getSize(heightMeasureSpec);
//		int childCount = getChildCount();
//		for (int i = 0; i < childCount; i++) {
//			View child = getChildAt(i);
//			// 设置每个子视图的大小 , 即全屏
//			child.measure(MultiScreenActivity.screenWidth, MultiScreenActivity.scrrenHeight);
//		}
	}

	// layout过程
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		// TODO Auto-generated method stub
		Log.i(TAG, "--- start onLayout --");
		int startLeft = 0; // 每个子视图的起始布局坐标
		int startTop = 10; // 间距设置为10px 相当于 android:marginTop= "10px"
		int childCount = getChildCount();
		Log.i(TAG, "--- onLayout childCount is -->" + childCount);
		for (int i = 0; i < childCount; i++) {
			View child = getChildAt(i);
			child.layout(startLeft, startTop, 
					startLeft + MultiScreenActivity.screenWidth, 
					startTop + MultiScreenActivity.scrrenHeight);
			startLeft = startLeft + MultiScreenActivity.screenWidth ; //校准每个子View的起始布局位置
			//三个子视图的在屏幕中的分布如下 [0 , 320] / [320,640] / [640,960]
		}
	}
}  

 

 

     更多关于自定义ViewGroup无须重写measure动作的,可以参考 Android API :

               <<Optimizing the View >>

     中文翻译见于:<< Android中View绘制优化之三---- 优化View>>

 

 

 

 

 3、root View被添加至窗口时,UI框架是如何设置其LayoutParams值

 

         老子道德经有言:“道生一,一生二,二生三,三生万物。”  UI绘制也就是个递归过程。理解其基本架构后,

 也就“掌握了一个中心点”了。在第一节中,我们没有说明开始UI绘制时 ,没有说明mView.measure()参数地由来,

 参数也就是我们本节需要弄懂的“道” --- root View的 widthMeasureSpec和heightMeasureSpec 是如何确定的。

 

   对于如下布局文件: main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
</LinearLayout>

  

    当使用LayoutInflater类解析成View时 ,LinearLayout对象的LayoutParams参数为null 。具体原因请参考上篇博文

 

    任何一个View被添加至窗口时,都需要利用WindowManager类去操作。例如,如下代码:

 

//显示一个悬浮窗吧 , just so so 
public void showView()
{
    //解析布局文件
	LayoutInflater layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    //rootView对应地LayoutParams属性值为null,将会在UI绘制时设定其值
	View rootView = layoutInflater.inflate(R.layout.main, null);
	
	WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
	//设置WindowManager.LayoutParams参数值,作为该窗口的各种属性
	WindowManager.LayoutParams winparams = WindowManager.LayoutParams();
     // 以屏幕左上角为原点,设置x、y初始值
	winparams.x = 0;
	winparams.y = 0;

    //设置悬浮窗口长宽数据
	winparams.width = WindowManager.LayoutParams.WRAP_CONTENT;;
	winparams.height = WindowManager.LayoutParams.WRAP_CONTENT;;
     
	windowManager.addView(rootView, winparams);
}

 

 

       关于WindowManager的使用请看如下博客 :

                               <<android学习---- WindowManager 接口 >>

                              <<在Android中使用WindowManager实现悬浮窗口>>

      关于WindowManager.LayoutParams类说明请看如下博客: 

                              << android学习---- WindowManager.LayoutParams>>

       下面,我们从获得WindowManager对象引用开始,一步步观察addView()做了一些什么事情。

   Step 1 、获得WindowManager对象服务 ,具体实现类在ContextImpl.java内中

           路径: /frameworks/base/core/java/android/app/ContextImpl.java         


   @Override
   public Object getSystemService(String name) {
       if (WINDOW_SERVICE.equals(name)) {
           return WindowManagerImpl.getDefault();
       }
       ...
   }

        WindowManager是个接口,具体返回对象则是WindowManagerImpl的单例对象。

 

 Step 2 、 获得WindowManagerImpl的单例对象,以及部分源码分析

           路径: /frameworks/base/core/java/android/view/WindowManagerImpl.java 

public class WindowManagerImpl implements WindowManager{
	   
   public static WindowManagerImpl getDefault()
   {
       return mWindowManager;
   }
   //以特定Window属性添加一个窗口
   public void addView(View view, ViewGroup.LayoutParams params)
   {
       addView(view, params, false);
   }
   //参数nest表示该窗口是不是一个字窗口
   private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
   {   ...
       final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
       
       ViewRoot root;
       View panelParentView = null;    //该子窗口对应地父窗口View
       
       synchronized (this) {
          
    	   ...//需要对传递过来地参数进行检测...
    	   
           //对每个窗口皆构建一个ViewRoot对象
           root = new ViewRoot(view.getContext());
           root.mAddNesting = 1;
           //设置root View 的LayoutParams为wparams,即WindowManager.LayoutParams类型
           view.setLayoutParams(wparams);
           ...//对参数检测,以及拷贝原有数组...
           
           //将窗口对应地view、root、wparams保存在属性集合中
           mViews[index] = view;
           mRoots[index] = root;
           mParams[index] = wparams;
       }
       // do this last because it fires off messages to start doing things
       // 调用ViewRoot对象去通知系统添加一个窗口
       root.setView(view, wparams, panelParentView);
   }
   ...
   //这三个数组分别保存了一个窗口对应地属性
   private View[] mViews;         //root View对象 , View类型
   private ViewRoot[] mRoots;     //ViewRoot类型 , 与WMS通信
   private WindowManager.LayoutParams[] mParams;  //窗口属性
   
   //WindowManagerImpl实现了单例模式
   private static WindowManagerImpl mWindowManager = new WindowManagerImpl();
}

 


      WindowManagerImpl类的三个数组集合保存了每个窗口相关属性,这样我们可以通过这些属性去操作特定的

 窗口(例如,可以根据View去更新/销毁该窗口)。当参数检查成功时,构建一个ViewRoot对象,并且设置设置root

 View 的LayoutParams为wparams,即WindowManager.LayoutParams类型。最后调用root.setView()方法去通知

 系统需要创建该窗口。我们接下来往下看看ViewRoot类相关操作。

  

    Step 3、

   

public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks {
	   
	View mView;	  //所有窗口地root View   
	final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();  

	...
	 /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs,
            View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                mWindowAttributes.copyFrom(attrs); //保存WindowManager.LayoutParams属性值
                attrs = mWindowAttributes;
                ...
                
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // 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();   //请求UI开始绘制。
                mInputChannel = new InputChannel();  //创建一个InputChannel对象,接受消息
                try {
                    //通知WindowManagerService添加一个窗口
                	res = sWindowSession.add(mWindow, mWindowAttributes,
                            getHostVisibility(), mAttachInfo.mContentInsets,
                            mInputChannel);
                } 
                ...
                view.assignParent(this);  //将root View的父View设置为该ViewRoot对象(实现了ViewParent接口)
                ...
            }
        }
    }
}

           说明:ViewRoot类继承了Handler,实现了ViewParent接口

 

  setView()方法地主要功能如下:
        1、保存相关属性值,例如:mView、mWindowAttributes等;
        2、调用requestLayout()方法请求UI绘制,由于ViewRoot是个Handler对象,异步请求;
        3、通知WindowManagerService添加一个窗口;
        4、注册一个事件监听管道,用来监听:按键(KeyEvent)和触摸(MotionEvent)事件。

  我们这儿重点关注 requestLayout()方法请求UI绘制地流程。

 

  Step 4、异步调用请求UI绘制

   

   /**
    * {@inheritDoc}
    */
   public void requestLayout() {
       checkThread();        //检查是不是UI线程调用,如果不是UI线程,会报异常
       mLayoutRequested = true;   //置为真,表示需要进行measure和layout过程
       scheduleTraversals();  
   }
   //开始UI绘制流程
   public void scheduleTraversals() {
       if (!mTraversalScheduled) {
           mTraversalScheduled = true;       //防止多次调用
           sendEmptyMessage(DO_TRAVERSAL);   //异步请求UI绘制
       }
   }
   @Override
   public void handleMessage(Message msg) {
	   switch (msg.what) {
           case DO_TRAVERSAL:
                performTraversals();  //开始UI绘制
                break;
	   }
   }

   

          由于performTraversals()方法比较复杂,我们侧重于第一次设置root View的widhtSpecSize以及    

  heightSpecSize值。

   private void performTraversals() {
       // cache mView since it is used so much below...
       final View host = mView;

       mTraversalScheduled = false;         
       boolean surfaceChanged = false;
       WindowManager.LayoutParams lp = mWindowAttributes;  

       int desiredWindowWidth;              //表示该窗口期望width值
       int desiredWindowHeight;             //表示该窗口期望width值
       int childWidthMeasureSpec;           //保存root View的widthMeasureSpec
       int childHeightMeasureSpec;          //保存root View的heightMeasureSpec

       final View.AttachInfo attachInfo = mAttachInfo;

       final int viewVisibility = getHostVisibility();
       boolean viewVisibilityChanged = mViewVisibility != viewVisibility
               || mNewSurfaceNeeded;

       float appScale = mAttachInfo.mApplicationScale;

       WindowManager.LayoutParams params = null;
       if (mWindowAttributesChanged) {
           mWindowAttributesChanged = false;
           surfaceChanged = true;
           params = lp;
       }
       Rect frame = mWinFrame;
       if (mFirst) {   //mFirst表示是否是第一次绘制该Window
           fullRedrawNeeded = true;
           mLayoutRequested = true;

           DisplayMetrics packageMetrics =
               mView.getContext().getResources().getDisplayMetrics();
           //第一次绘制时desiredWindowWidth,desiredWindowHeight 值大小为屏幕大小
           desiredWindowWidth = packageMetrics.widthPixels;
           desiredWindowHeight = packageMetrics.heightPixels;
           ...
       } else {   //不是第一次绘制,则desiredWindowWidth值为frame保存大小,frame值会由WMS填充
           desiredWindowWidth = frame.width();
           desiredWindowHeight = frame.height();
           ...
       }
       ...
       boolean insetsChanged = false;

       if (mLayoutRequested) {
           ...//获得root View的widthMeasureSpec 和 heightMeasureSpec值
           childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
           childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
           //开始measure过程
           host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
       }
       ...
       final boolean didLayout = mLayoutRequested;
       
       boolean triggerGlobalLayoutListener = didLayout
               || attachInfo.mRecomputeGlobalAttributes;
       if (didLayout) {
           ... //layout过程
    	   host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
           ...
       }
       ...
       if (!cancelDraw && !newSurface) {
           mFullRedrawNeeded = false;
           draw(fullRedrawNeeded);
           ...
   }
  /**
    * @param windowSize  The available width or height of the window
    *
    * @param rootDimension The layout params for one dimension (width or height) of the window.
   */
   private int getRootMeasureSpec(int windowSize, int rootDimension) {
       int measureSpec;
       switch (rootDimension) {
       case ViewGroup.LayoutParams.MATCH_PARENT:
           // Window can't resize. Force root view to be windowSize.
           measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
           break;
       case ViewGroup.LayoutParams.WRAP_CONTENT:
           // Window can resize. Set max size for root view.
           measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
           break;
       default:
           // Window wants to be an exact size. Force root view to be that size.
           measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
           break;
       }
       return measureSpec;
   }       

 

      调用root View的measure()方法时,其参数是由getRootMeasureSpec()设置的,基本思路同我们前面描述的

  差不多。贴出来的代码只是简简单单列出了measure 、layout 、 draw 过程的调用点,里面有很多逻辑处理,

  阅读起来比较费劲,我也只能算是个囫囵吞枣水平。大家有兴趣地可以看看源码,加深理解。

 

    

 

    最后,由于小子理解水平有限,可能很多地方让大家“丈二和尚--摸不着头脑”,给大家两个小建议吧:

            1、仔细钻研源码  ;

            2、想认真系统性研读UI绘制原理的话,建议详细阅读<<Android内核剖析>>第十三章 <UI绘制原理>

    

Android中将布局文件/View添加至窗口过程分析

 

本文主要内容是讲解一个视图View或者一个ViewGroup对象是如何添加至应用程序窗口中的。

 

        下文中提到的窗口可泛指我们能看到的界面,包括一个Activity呈现的界面(我们可以将之理解为应用程序窗口),一个Dialog,

   一个Toast,一个Menu菜单等。

 

      首先对相关类的作用进行一下简单介绍:

 

         Window 类   位于 /frameworks/base/core/java/android/view/Window.java

            说明:该类是一个抽象类,提供了绘制窗口的一组通用API。可以将之理解为一个载体,各种View在这个载体上显示。

             源文件(部分)如下:

public abstract class Window {	
	//...
	//指定Activity窗口的风格类型
    public static final int FEATURE_NO_TITLE = 1;
    public static final int FEATURE_INDETERMINATE_PROGRESS = 5;
    
    //设置布局文件
    public abstract void setContentView(int layoutResID);

    public abstract void setContentView(View view);

    //请求指定Activity窗口的风格类型
    public boolean requestFeature(int featureId) {
        final int flag = 1<<featureId;
        mFeatures |= flag;
        mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
        return (mFeatures&flag) != 0;
    }    
    //...
}

       PhoneWindow类  位于/frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindow.java

         说明: 该类继承于Window类,是Window类的具体实现,即我们可以通过该类具体去绘制窗口。并且,该类内部包含了

            一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。 简而言之,PhoneWindow类是

            把一个FrameLayout类即DecorView对象进行一定的包装,将它作为应用窗口的根View,并提供一组通用的窗口操作

            接口。

               源文件(部分)如下:          

public class PhoneWindow extends Window implements MenuBuilder.Callback {
	//...
	// This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;  //该对象是所有应用窗口的根视图 , 是FrameLayout的子类
    
    //该对象是Activity布局文件的父视图,一般来说是一个FrameLayout型的ViewGroup 
    // 同时也是DecorView对象的一个子视图
    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent; 
    
    //设置标题
    @Override
    public void setTitle(CharSequence title) {
        if (mTitleView != null) {
            mTitleView.setText(title);
        }
        mTitle = title;
    }
    //设置背景图片
    @Override
    public final void setBackgroundDrawable(Drawable drawable) {
        if (drawable != mBackgroundDrawable || mBackgroundResource != 0) {
            mBackgroundResource = 0;
            mBackgroundDrawable = drawable;
            if (mDecor != null) {
                mDecor.setWindowBackground(drawable);
            }
        }
    }
    //...    
}

       DecorView类    该类是PhoneWindow类的内部类

         说明: 该类是一个FrameLayout的子类,并且是PhoneWindow的子类,该类就是对普通的FrameLayout进行功能的扩展,

            更确切点可以说是修饰(Decor的英文全称是Decoration,即“修饰”的意思),比如说添加TitleBar(标题栏),以及

            TitleBar上的滚动条等 。最重要的一点是,它是所有应用窗口的根View 。

         如下所示 :

   

  View 的绘制流程             View 的绘制流程

           DecorView 根视图结构                                                          DecorView 根视图形式

     

     源文件(部分)如下:

private final class DecorView extends FrameLayout {
	//...
	//触摸事件处理
	@Override
    public boolean onTouchEvent(MotionEvent event) {
        return onInterceptTouchEvent(event);
    }
	//...
}


 

       打个不恰当比喻吧,Window类相当于一幅画(抽象概念,什么画我们未知) ,PhoneWindow为一副齐白石先生的山水画

   (具体概念,我们知道了是谁的、什么性质的画),DecorView则为该山水画的具体内容(有山、有水、有树,各种界面)。

   DecorView呈现在PhoneWindow上。

 

 

 

       当系统(一般是ActivityManagerService)配置好启动一个Activity的相关参数(包括Activity对象和Window对象信息)后,

   就会回调Activity的onCreate()方法,在其中我们通过设置setContentView()方法类设置该Activity的显示界面,整个调用链

   由此铺垫开来。setContentView()的三个构造方法调用流程本质上是一样的,我们就分析setContentView(intresId)方法。

  

    

           

Step 1  、Activity.setContentView(intresId)   该方法在Activity类中

         该方法只是简单的回调Window对象,具体为PhoneWindow对象的setContentView()方法实现 。

 

	public void setContentView(int layoutResID) {
	    getWindow().setContentView(layoutResID);
	}

	public Window getWindow() {
	    return mWindow;   //Window对象,本质上是一个PhoneWindow对象
	}


 Step 2  、PhoneWindow.setContentView()     该方法在PhoneWindow类中 

 

 

	@Override
	public void setContentView(int layoutResID) {
		//是否是第一次调用setContentView方法, 如果是第一次调用,则mDecor和mContentParent对象都为空
	    if (mContentParent == null) {
	        installDecor();
	    } else {
	        mContentParent.removeAllViews();
	    }
	    mLayoutInflater.inflate(layoutResID, mContentParent);
	    final Callback cb = getCallback();
	    if (cb != null) {
	        cb.onContentChanged();
	    }
	}

 

 

 

       该方法根据首先判断是否已经由setContentView()了获取mContentParent即View对象, 即是否是第一次调用该

   PhoneWindow对象setContentView()方法。如果是第一次调用,则调用installDecor()方法,否则,移除该mContentParent内

   所有的所有子View。最后将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中。

      

       PS:因此,在应用程序里,我们可以多次调用setContentView()来显示我们的界面。

 

 Step 3、 PhoneWindow. installDecor()    该方法在PhoneWindow类中

 

 

	private void installDecor() {
	    if (mDecor == null) {
	    	//mDecor为空,则创建一个Decor对象
	        mDecor = generateDecor();
	        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
	        mDecor.setIsRootNamespace(true);
	    }
	    if (mContentParent == null) {
	    	//generateLayout()方法会根据窗口的风格修饰,选择对应的修饰布局文件
	    	//并且将id为content(android:id="@+id/content")的FrameLayout赋值给mContentParent
	        mContentParent = generateLayout(mDecor);
	        
	        //...
	}

 

 

   首先、该方法首先判断mDecor对象是否为空,如果不为空,则调用generateDecor()创建一个DecorView(该类是

           FrameLayout子类,即一个ViewGroup视图) ;

 

      generateDecor()方法原型为:

    protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

 

 

  其次、继续判断mContentParent对象是否为空,如果不为空,则调用generateLayout()方法去创建mContentParent对象。

         generateLayout()方法如下:

 

    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        //...1、根据requestFreature()和Activity节点的android:theme="" 设置好 features值
    	
    	//2 根据设定好的 features值,即特定风格属性,选择不同的窗口修饰布局文件
        int layoutResource;  //窗口修饰布局文件  
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                layoutResource = com.android.internal.R.layout.dialog_title_icons;
            } else {
                layoutResource = com.android.internal.R.layout.screen_title_icons;
            }
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = com.android.internal.R.layout.screen_progress;
            // System.out.println("Progress!");
        } 
        //...
        
        //3 选定了窗口修饰布局文件 ,添加至DecorView对象里,并且指定mcontentParent值
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }
        //...
        return contentParent;
    }


 

 

 

 

 该方法会做如下事情:

   1、根据窗口的风格修饰类型为该窗口选择不同的窗口布局文件(根视图)。这些窗口修饰布局文件指定一个用来存放

         Activity自定义布局文件的ViewGroup视图,一般为FrameLayout 其id 为: android:id="@android:id/content"。

        例如窗口修饰类型包括FullScreen(全屏)、NoTitleBar(不含标题栏)等。选定窗口修饰类型有两种:

           ①、指定requestFeature()指定窗口修饰符,PhoneWindow对象调用getLocalFeature()方法获取值;

           ②、为我们的Activity配置相应属性,即android:theme=“”,PhoneWindow对象调用getWindowStyle()方法

              获取值。

        举例如下,隐藏标题栏有如下方法:requestWindowFeature(Window.FEATURE_NO_TITLE);

                   或者 为Activity配置xml属性:android:theme=”@android:style/Theme.NoTitleBar”。

 

        PS:因此,在Activity中必须在setContentView之前调用requestFeature()方法。

 

 

  确定好窗口风格之后,选定该风格对应的布局文件,这些布局文件位于 frameworks/base/core/res/layout/  ,

        典型的窗口布局文件有:

          R.layout.dialog_titile_icons                          R.layout.screen_title_icons

          R.layout.screen_progress                             R.layout.dialog_custom_title

          R.layout.dialog_title   

          R.layout.screen_title         // 最常用的Activity窗口修饰布局文件

          R.layout.screen_simple    //全屏的Activity窗口布局文件

 

 

 

   分析Activity最常用的一种窗口布局文件,R.layout.screen_title  :

 

    <!--
    This is an optimized layout for a screen, with the minimum set of features
    enabled.
    -->

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:fitsSystemWindows="true">
        <FrameLayout
            android:layout_width="match_parent" 
            android:layout_height="?android:attr/windowTitleSize"
            style="?android:attr/windowTitleBackgroundStyle">
            <TextView android:id="@android:id/title" 
                style="?android:attr/windowTitleStyle"
                android:background="@null"
                android:fadingEdge="horizontal"
                android:gravity="center_vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </FrameLayout>
        <FrameLayout android:id="@android:id/content"
            android:layout_width="match_parent" 
            android:layout_height="0dip"
            android:layout_weight="1"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />
    </LinearLayout>

 

       该布局文件很简单,一个LinearLayout下包含了两个子FrameLayout视图,第一个FrameLayout用来显示标题栏(TitleBar),

  该TextView 视图id为title(android:id="@android:id/title");第二个FrameLayout用来显示我们Activity的布局文件的父视图,

  该FrameLayoutid为content(android:id="@android:id/content") 。

 

 

  全屏的窗口布局文件 R.layout.screen_simple:

 

    <--This is an optimized layout for a screen, with the minimum set of features
    enabled.
    -->

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@android:id/content"
        android:fitsSystemWindows="true"
        android:foregroundInsidePadding="false"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />

 

     

            该布局文件只有一个FrameLayout,用来显示我们Activity的布局文件,该FrameLayoutid为

    android:id="@android:id/content"

 

 

  2、前面一步我们确定窗口修饰布局文件后,mDecor做为根视图将该窗口布局对应的视图添加进去,并且获取id为content

          的View,将其赋值给mContentParent对象,即我们前面中提到的第二个FrameLayout。

 

   At Last、产生了mDecor和mContentParent对象后,就将我们的Activity布局文件直接添加至mContentParent父视图中即可。

      我们再次回到 Step 2 中PhoneWindow.setContentView()      该方法在PhoneWindow类中

 

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null) {
            cb.onContentChanged();
        }
    }


 

 

  整个过程主要是如何把Activity的布局文件添加至窗口里,上面的过程可以概括为:

              1、创建一个DecorView对象,该对象将作为整个应用窗口的根视图

              2、创建不同的窗口修饰布局文件,并且获取Activity的布局文件该存放的地方,由该窗口修饰布局文件内id为content的

                  FrameLayout指定 。

              3、将Activity的布局文件添加至id为content的FrameLayout内。

 

       最后,当AMS(ActivityManagerService)准备resume一个Activity时,会回调该Activity的handleResumeActivity()方法,

  该方法会调用Activity的makeVisible方法 ,显示我们刚才创建的mDecor 视图族。

   

    //系统resume一个Activity时,调用此方法
    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
    	ActivityRecord r = performResumeActivity(token, clearHide);
    	//...
    	 if (r.activity.mVisibleFromClient) {
             r.activity.makeVisible();
         }
    }

 

 

    handleResumeActivity()方法原型如下: 位于ActivityThread类中

    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();   // 获取WindowManager对象
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE); //使其处于显示状况
    }

 

相关文章: