FrameWork之View绘制过程


       我们已经知道View的绘制过程包括计算视图大小(measure)、为视图分配位置(layout)以及把视图绘制到屏幕上(draw)三个步骤,下面详细了解如何完成这三个步骤。

       我们假设WmS已经将窗口创建完毕,现在我们要在一个Activity的onCreate()方法中调用setContentView(R.layout.chat_mesg);来绘制一个Activity的界面。下面跟着源码一步步解析其过程。

       首先,我们通常使用的setContentView()方法是Activity类中定义的方法,Activity的setContentView源码:

    

     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     * 
     * 为一个Activity设置内容(视图),该内容是从我们的layout目录下的xml文件提供。
     * 这个资源会被填充到当前窗口,并将最上面的层级视图添加到此Activity。
     * 
     * @param layoutResID Resource ID to be inflated.
     * 
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(int layoutResID) {
    	//这里获取到一个Window对象,实际上Window类是一个抽象类,它只有一个PhoneWindow类,因此这里实际上是调用PhoneWindow类的setCOntentView(int sourcve)方法
        getWindow().setContentView(layoutResID);
        //创建一个新的ActionBar
        initActionBar();
    }

     可以看到,Activity的setContentView方法中首先获取一个Window对象,这个Window类是一个抽象类,它里面的setContentView()方法并没有具体实现,而是他唯一的一个实现类PhoneWindow来实现的,因此,这里getWindow()实际上是获取PhoneWindow的对象。那么这个PhoneWindow对象是怎么来的呢?我们看下getWindow()方法里面是什么:

    

Retrieve the current android.view.Window for the activity. This can be used to directly access parts of the Window API that are not available through Activity/Screen. 

Returns:
Window The current window, or null if the activity is not visual.807 
    
 public Window getWindow() {
         return mWindow;
     }
      如上所示,getWindow()方法(参数太多省略。。)仅仅是返回一个mWindow对象,此对象是Activity的一个私有的成员变量,那么这个对象是什么时候被赋值的呢?既然mWindow对象是在Activity中,那么我们在Activity中搜索下就知道了,找啊找啊找,终于发现了,在一个叫attach()的方法中发现了这一行代码:

       mWindow = PolicyManager.makeNewWindow(this);

      由此可见mWindow是在attach()方法中被创建,实际上在attach()法中还创建了N多东西,比如mUiThread线程,ActivityThread线程、Application实例等等,看下attach()的源码吧!:

     

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config) {
        attachBaseContext(context);

        mFragments.attachActivity(this);
        //这里生产一个Window对象
        mWindow = PolicyManager.makeNewWindow(this);
        //注册回滚监听,
        mWindow.setCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        //UI线程,直接就是获取当前线程作为UI线程
        mUiThread = Thread.currentThread();
       //主线程,实际上就是ActivityThread
        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        //获取Application实例
        mApplication = application;
        mIntent = intent;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;

        mWindow.setWindowManager(null, mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }<span style="font-size:18px;">
</span>
       可以看到,attach()方法做了很多有用的工作,那么attach()方法是在什么时候被执行呢?这个attach()方法是在ActivityThread的performLaunchActivity()中被调用(实际上在ActivityThread中有一整套对应于Activity生命周期的performXXXX()方法,这些方法又对应于Instruction类的一整套Activity生命周期的callActivityOnCreate()等等方法,之后我们会详细介绍。),也就是启动Activity之前会调用这个attach()方法,attach()调用之后才会调用onCreate()等等之类的方法。好了,上面说了半天也只是说mWindow是由PolicyManager.makeNewWindow(this);创建的,那么我们去看看这个类吧:
 // The static methods to spawn new policy-specific objects

    public static Window  makeNewWindow(Context context) {
        
        return sPolicy.makeNewWindow(context);

    }

         如上所示,是PolicyManager类的makeNewWindow()方法,里面是通过sPolicy调用的makeNewWindow()来生产一个Window对象,sPolicy是一个IPolicy接口对象,sPolicy实际上是IPolicy的一个实现类Policy的对象,我们去看看Policy的makeNewWindow()方法:

 public Window  [More ...] makeNewWindow(Context context) {

        return new PhoneWindow(context);
} 
       看出来了吗,在Policy中的makeNewWindow()方法中创建了一个PhoneWindow对象。OK,现在我们知道了Activity中的mWindow对象是一个PhoneWindow对象,每一个Activity都有一个mWindow对象,也就是每一个Activity都对应于一个Window窗口对象。其生产过程是:ActivityThread--->-performLaunchActivity()---->Activity----attach()----->PolicyManager---->Policy---makeNewWindow()----new PhoneWindo()。

       回过头来看setContentView(int  resId),这个方法吧,上面说到,这个方法又调用到了PhoneWindow的setContentView(int  resId),那么我们来看看PhoneWindow吧:

 @Override
    public void setContentView(int layoutResID) {
        
        //如果mContentParent是空,说明是第一次创建布局视图,通俗点将就是一个Activity被创建即onCreate()执行的时候,
        if (mContentParent == null) {
           //初始化mDecorView
           installDecor();
        } else {
           //如果不是就将mContentParent的子视图全部移除,因为mContentParent只有一个
           mContentParent.removeAllViews();
        }
        //解析我们的xml布局文件
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
      在上面的PhoneWindow的setContentView()方法中,首先会判断mContentParent对象是否为空,如果是空就调用installDecor()初始化一个DecorView、mContentParent等操作,否则就将mContentParent的子视图全部移除,将我们的布局文件添加上去,这里注意,mContentParent是一个ViewGroup对象,它是我们布局文件的根节点,通常我们编写的布局文件的实际根节点就是它了。那么它是什么时候被创建的呢?我们看上面的代码,当mContentParent为null时执行的installDecor()方法吧:

    

 private void installDecor() {
       //判断mDecor是否为空,如果为空就创建一个
       if (mDecor == null) { 
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
     //判断mContentParent是否为空,如果为空就创建一个
     if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    View titleContainer = findViewById(com.android.internal.R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    if (mContentParent instanceof FrameLayout) {
                        ((FrameLayout)mContentParent).setForeground(null);
                    }
                } else {
                    mTitleView.setText(mTitle);
                }
            } else {
                mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
                if (mActionBar != null) {
                    mActionBar.setWindowCallback(getCallback());
                    if (mActionBar.getTitle() == null) {
                        mActionBar.setWindowTitle(mTitle);
                    }
                    final int localFeatures = getLocalFeatures();
                    if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) {
                        mActionBar.initProgress();
                    }
                    if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
                        mActionBar.initIndeterminateProgress();
                    }

                    boolean splitActionBar = false;
                    final boolean splitWhenNarrow =
                            (mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0;
                    if (splitWhenNarrow) {
                        splitActionBar = getContext().getResources().getBoolean(
                                com.android.internal.R.bool.split_action_bar_is_narrow);
                    } else {
                        splitActionBar = getWindowStyle().getBoolean(
                                com.android.internal.R.styleable.Window_windowSplitActionBar, false);
                    }
                    final ActionBarContainer splitView = (ActionBarContainer) findViewById(
                            com.android.internal.R.id.split_action_bar);
                    if (splitView != null) {
                        mActionBar.setSplitView(splitView);
                        mActionBar.setSplitActionBar(splitActionBar);
                        mActionBar.setSplitWhenNarrow(splitWhenNarrow);

                        final ActionBarContextView cab = (ActionBarContextView) findViewById(
                                com.android.internal.R.id.action_context_bar);
                        cab.setSplitView(splitView);
                        cab.setSplitActionBar(splitActionBar);
                        cab.setSplitWhenNarrow(splitWhenNarrow);
                    } else if (splitActionBar) {
                        Log.e(TAG, "Requested split action bar with " +
                                "incompatible window decor! Ignoring request.");
                    }

                    // Post the panel invalidate for later; avoid application onCreateOptionsMenu
                    // being called in the middle of onCreate or similar.
                    mDecor.post(new Runnable() {
                        public void run() {
                            // Invalidate if the panel menu hasn't been created before this.
                            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
                            if (!isDestroyed() && (st == null || st.menu == null)) {
                                invalidatePanelMenu(FEATURE_ACTION_BAR);
                            }
                        }
                    });
                }
            }
        }
    }
         installDecor()方法的代码比较多,比较重要的操作就是创建了一个mDecor对象和mContentParent对象,mDecor是一个DecorView类,它是PhoneWindow的内部类,mDecor就是一个窗口的根视图,它继承自FrameLayout,generateDecor()方法很简单,就是直接new了一个DecorView对象。在创建mContentParent时使用了generateLayout(DecorView decor)方法,注意,在使用该方法时将mDecor传递过去了,generateLayout()方法代码略多,我们截取最关键的代码如下:
<span style="font-size:14px;">   </span>
      ....
  
     //前面的代码是经过一系列判断根据根据系统默认的布局产生一个layoutResouce的变量,他就是系统的一个默认的布局
     mDecor.startChanging();
     //根据系统默认的布局inflate一个视图
     View in = mLayoutInflater.inflate(layoutResource, null);
    //使用mDecor将视图添加进来,注意,此时mDecor表示的是一个窗口的根节点,同时这个填充的视图是充满屏幕的
       decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
     //根据id查找一个叫做ID_ANDROID_CONTENT的ViewGroup,此常亮是在Window中定义的
      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
     //下面会将 contentParent返回,它就是mContentParent了,我们所有的xml布局文件将作为它的直接子视图。
     

         好了,通过上面的两个步骤,我们已经创建了mDecor和mContentParent视图了,创建顺序是:先创建mDecor(FrameLayout)作为窗口的根节点,然后创建一个mContentParent(ViewGroup)作为我们所有xml布局文件的父节点,再然后将mContentParent添加到mDecor中去。再回过头看setContentView()方法,此时mDecor和mContentParent已经创建完毕,并且开始执行

 mLayoutInflater.inflate(layoutResID, mContentParent);
方法了,这个方法我们都很熟悉,他就会将我们的xml布局layoutResID解析成一个相应的View并添加到mContentParent上去,此时一颗View Tree创建完毕。

            注意mDecor和mContentParent对象的区别,前者是应用窗口的根视图,后者是我们布局文件的根视图,也是mDecor的子视图。下面再看LayoutInflate类的inflate()方法:

     * Inflate a new view hierarchy from the specified xml resource. Throws
     * {@link InflateException} if there is an error.
     */
    public View inflate(int resource, ViewGroup root) {
        return inflate(resource, root, root != null);
    }
       该方法调用了本类的重载方法如下:
     /* Inflate a new view hierarchy from the specified xml resource. Throws
     * {@link InflateException} if there is an error.
     * 
     */
    public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
      if (DEBUG) System.out.println("INFLATING from resource: " + resource);
        XmlResourceParser parser = getContext().getResources().getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
      注意上面代码中是先从Context中获取一个Resource对象,然后获取Resource类中的XmlResourceParser对象,这里是用于解析xml布局文件(layout  xml)所以是调用getLayout()获取一个XmlResourceParser对象,我们知道Android项目中有很多种不同类型的xml,比如color xml、value xml、anim xml等等,都是不同的XmlResourceParser对象完成的,在Resources中有对象的getAnimation()、getLayout()和getXml()来获取不同的xml‘解析器。进一步查看Resource类的getLayout()方法:

    

 public XmlResourceParser getLayout(int id) throws NotFoundException {
         //该方法返回,注意这里传递了一个"layout"参数,表示是布局xml文件
          return loadXmlResourceParser(id, "layout");
    }
       进一步查看loadXmlResourceParesr()方法
              /**
               * 
               * @param id  资源id,这个id就是我们应用程序中R.layout.xxx的xml文件id
               * @param type  表示需要解析的xml文件类型,比如type的值为anim、xml等,上面方法传入的一个"layout"参数
               * 
               * @return
               * @throws NotFoundException
               */
    /*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
            throws NotFoundException {
    	   //安全加锁
        synchronized (mTmpValue) {
            TypedValue value = mTmpValue;
            getValue(id, value, true);
            if (value.type == TypedValue.TYPE_STRING) {
                return loadXmlResourceParser(value.string.toString(), id,
                        value.assetCookie, type);
            }
            throw new NotFoundException(
                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
                    + Integer.toHexString(value.type) + " is not valid");
        }
    }
       上述方法中加锁的对象是一个动态类型数据值的容器,主要用于Resource类所持有的资源值,其源码解释是:
/**
 * Container for a dynamically typed data value.  Primarily used with
 * {@link android.content.res.Resources} for holding resource values.
 */
public class TypedValue {
       OK,上述跟踪到此为止,就不再往下了,我们只需要知道,Android在解析不同的xml资源文件时是使用了不同的解析器的。回过头来看LayoutInflate中的inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)方法,在上面一步中已经根据我们的资源ID生成一个xml解析器了,现在要做的就是解析这个xml文件,也就是下面要做的,这个方法开始真正解析布局文件并且转化为view后添加到其父视图中去,源码如下:
   /**
     * Inflate a new view hierarchy from the specified XML node. Throws
     * {@link InflateException} if there is an error.
     */
    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        
    	//mConstructorArgs,构造器参数数组,大小为2
    	//final Object[] mConstructorArgs = new Object[2];
    	synchronized (mConstructorArgs) {
    		
    		//好吧,要想弄懂xml怎么转换成View的,AttributeSet这个接口还需要了解
    		//这个接口是所有xml布局文件所有tag的属性的集合,谷歌工程师并不建议我们直接使用该接口。
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context)mConstructorArgs[0];
            mConstructorArgs[0] = mContext;
            View result = root;

            try {
                // Look for the root node.
            	//查找根节点
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                	//从开始到结束啥也不干
                }
                //解析到xml文件开头部分,如果没有就会抛出异常
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }
                //获取标记xml文件属性名字
                final String name = parser.getName();
                //调试状态下会打印
                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }
                //判断xml布局文件中是否使用了merge标签
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    //如果有,而且满足条件,那么就调用另一个方法继续解析,此处我不在跟踪下去了~囧~,伤不起。
                    //不管怎样,使用都是使用pull解析,规则是一样的,无非就是会判断标签然后作不同的处理,
                    //如果大家对pull解析不熟悉可以网上搜下相关文章
                    rInflate(parser, root, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    View temp;
                    //TAG_1995对应一个叫blink的标签,这个还真没有用过...低调飘过。。
                    if (TAG_1995.equals(name)) {
                        temp = new BlinkLayout(mContext, attrs);
                    } else {
                    	//通过对前面xml文件以下列的判断,调用这个方法真正的视图View!
                        //根据该方法名字也知道是从xml的标签创建一个View,
                    	//注意此处传入的几个参数,root是我们在应用程序中调用inflate(int source,ViewGroup root)方法时传递的父容器,通常为null
                    	//But!这里由于是使用setContentView()方法,所以默认的父容器即为mContentParent对象。
                                 //name参数就是获取到的xml标签名字
                    	//attrs就是xml文件所有属性的集合了
                    	temp = createViewFromTag(root, name, attrs);
                    }
                    //请务必注意LayoutParams类,这行代码已经表明,该类是ViewGroup类,
                    //我们都知道ViewGroup类是所有容器类的父类,比如LinearLayout、TableLayout、RealtiveLayout等、
                    //那么这些类都有一个叫做LayoutParams的内部类,而且这些LayoutParams类都是继承自ViewGroup类的内部类MarginLayoutParams
                    //而ViewGroup的MarginLayoutParams是继承于同样是内部类的LayoutParams,好吧,就这样终于继承完了。。。
                    //等等,好像跑题了。。。
                    //那么这些类是干嘛的呢?
                    //这个就是接下来要说的View绘制的三个过程(measure、layout、draw)。
                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        //创建一个params对象,调用的ViewGroup的generateLayoutParams(Attribute attrs)方法来创建的
                        //这些参数就是根据我们传入的attrs属性集合来创建的
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                           //设置该视图布局参数
                            //这一步很重要,因为之后需要将该是视图显示到窗口,需要根据这些参数来measure
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }
                    // Inflate all children under temp
                    //将所有的子视图对象放到temp视图下,此temp就是我们setCOntentView的xml布局文件视图
                    rInflate(parser, temp, attrs, true);
                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                    	//OK,终于完成了。。。
                    	//到最后发现还是调用了ViewGroup的addView()方法对不对,是不是有种兜兜转转又回到原地的赶脚
                    	//此方法我们也可以在程序中直接调用相关的View类来创建一个View然后调用addView方法添加
                    	//所以,这里可以看出来,所有的xml布局文件实际上最后经过一系列的转换变为一个View对象,完了之后
                    	//完了之后调用addView()完成添加,即将View控件添加到View容器中 - -、至此,完毕。
                    	//之后再回过头来看下LayoutParams类的具体细节 --、
                        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;
                    }
                }

            } catch (XmlPullParserException e) {
                InflateException ex = new InflateException(e.getMessage());
                ex.initCause(e);
                throw ex;
            } catch (IOException e) {
                InflateException ex = new InflateException(
                        parser.getPositionDescription()
                        + ": " + e.getMessage());
                ex.initCause(e);
                throw ex;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
            }

            return result;
        }
    }
      在上面代码中,具体的解释已经在代码中说明了,这个方法做了很重要的两件事,第一就是生产一个真正意义上的View,第二就是生产默认的LayoutParams。首先生产View是调用了createViewFromTag(root, name, attrs)方法,而首次生产默认的LayoutParams参数是root.generateLayoutParams(attrs);完成的,我们先来看生产View的createFromTag()方法,这个方法就是根据TAG来生产View,源码如下:

      

    /**
     *  default visibility so the BridgeInflater can override it.
     *   默认该视图是可见的,所以BridgeInflater可以重写他
     * @param parent 父容器视图
     * @param name   名字
     * @param attrs   属性
     * @return
     */
    View createViewFromTag(View parent, String name, AttributeSet attrs) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        if (DEBUG) System.out.println("******** Creating view: " + name);

        try {
            View view;
//          //Factor2是本类中的一个接口,onCreateView也是一个接口提供的方法
            //Factor2是继承Factor接口,一样的功能
//          //FactoryMerger类是实现了Factory接口的类,mFactor2也是FactoryMerger的实例,
                //因此,这里调用的onCreate()方法均是FactoryMerger类重写的。
            //总之经过一系列的onCreateView的调用,一个View算是创建好了
            //此类名叫LayoutInflate,顾名思义,是布局填充类,要想完全掌握,需要对此类以及相关的类仔细研究
            //这里又调用了onCreateView()方法,此方法是最终生成一个View对象的方法,源码随后给出
            if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs);
            else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs);
            else view = null;

            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);
            }
            
            if (view == null) {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            }

            if (DEBUG) System.out.println("Created view is: " + view);
            return view;

        } catch (InflateException e) {
            throw e;

        } catch (ClassNotFoundException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;

        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;
        }
 }

           上面说到,调用onCreateView()方法生成了一个View对象,源码如下:

 public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        Class<? extends View> clazz = null;

        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).asSubclass(View.class);
                
                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                sConstructorMap.put(name, constructor);
            } else {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);
                        
                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }

            Object[] args = mConstructorArgs;
            args[1] = attrs;

            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                // always use ourselves when inflating ViewStub later
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(this);
            }
            return view;

        } catch (NoSuchMethodException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;

        } catch (ClassCastException e) {
            // If loaded class is not a View subclass
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Class is not a View "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;
        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (clazz == null ? "<unknown>" : clazz.getName()));
            ie.initCause(e);
            throw ie;
        }
    }

          可以看到该方法利用name参数最终生成一个View对象,比如xml解析到name是TextView就反射一个TextVIew类对象,如果是EditText就反射以价格EditText对象。至此,以上我们分析了一个xml布局文件是怎么通过我们setCOntentVIew()来完成View的创建的,那么问题又来了,View虽然是被创建出来了,但是它还不具备一些属性,比如布局的位置,尺寸大小等。这正是我们现在要说的,回到inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)方法去看,当View创建完毕后,创建LayoutParams是由root.generateLayoutParams(attrs);来完成的,root对象是一个ViewGroup对象,那么我们就去ViewGroup中去看看generateLayoutParams()方法去看看:

/**
         * 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:
         *
         * <ul>
         *   <li><code>layout_width</code>: the width, either an exact value,
         *   {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
         *   {@link #MATCH_PARENT} in API Level 8)</li>
         *   <li><code>layout_height</code>: the height, either an exact value,
         *   {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
         *   {@link #MATCH_PARENT} in API Level 8)</li>
         * </ul>
         *
         * @param c the application environment
         * @param attrs the set of attributes from which to extract the layout
         *              parameters' values
         */
        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.
         *
         * @param width the width, either {@link #WRAP_CONTENT},
         *        {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
         *        API Level 8), or a fixed size in pixels
         * @param height the height, either {@link #WRAP_CONTENT},
         *        {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
         *        API Level 8), or a fixed size in pixels
         */
        public LayoutParams(int width, int height) {
            this.width = width;
            this.height = height;
        }
         由于generateLayoutParams()方法中是直接new一个LayoutParams对象,因此我直接把LayoutParams构造器和一个设置宽和高的方法贴出来,实际上LayoutParams类描述的是View告诉其父容器,它需要布局多大。也就是我们xml中用到的layout_width和layout_height属性。这里我们需要注意并且理解的是,LayoutParams只存在ViewGroup极其拓展子类中,并不存在View和一般控件中,因为LayoutParams子视图告诉父视图它想要多大,LayoutParams是由ViewGroup在绘制子视图时调用的。容器本身是无限大小的,即一个ViewGroup本身是无限大的,但是我们在布局xml文件中无论是容器类LinearLayout还是控件类TextView都必须指定一个layout_width和layout_height属性用于告诉父容器它需要多大的绘制空间。而在View中有一个MeasureSpec类,这个类是由父视图传递给子视图的绘制要求,即父视图告诉你该绘制成多大你就得绘制多大。

       OK!我们已经从setContentView()方法出发大概了解了一个xml文件布局文件是如何转换为一个view视图并最终添加到视图容器中,这里作一下总结:

       首先我们调用setContentView()方法传递一个xml布局文件,该方法是Activity类的方法,接着,Activity类中的setContentView()会先获取一个PhoneWindow对象然后调用其setContentView()方法,在该方法中首先判断是否是第一次调用,如果是第一次调用就会创建mDecor和mContentParent对象,如果不是第一次就调用,就移除mContentParent的所有子视图,因为mContentParent只有一个。然后完成两个对象创建后就调用LayoutInflate类的inflate()方法,该方法我们也经常在程序中用来根据一个xml布局文件来动态创建一个View。inflate()方法会先获取一个XmlResourceParser对象,之后调用Xml.asAttributeSet(parser);将XmlResourceParser对象当参数传递并返回一个AttributeSet对象,该对象就包含了xml文件所有我们定义的属性了,之后再调用createViewFromTag(root, name, attrs)方法正真获取一个View视图对象,该方法会经过一些列的onCreateView()方法的调用....。在将xml文件转换为一个View后,就开始创建LayoutParams参数,这个值的确定对之后View的measure过程产生直接的影响。params也是经过AttributeSet对象的确定:params = root.generateLayoutParams(attrs);最后通过生产的View调用方法完成布局参数的设置:temp.setLayoutParams(params)。至此,xml文件到View视图转化过程完毕。


        上面说了半天的xml文件到View对象的转换过程,事实上在View对象转换后并调用addView()方法添加到ViewGroup时就已经开始了测量、布局与绘制过程。我们就接着上面的addView()方法往下走:

 
 {
    	//获取该视图的布局参数
        LayoutParams params = child.getLayoutParams();
        //如果没有布局参数,就获取系统默认的参数
        if (params == null) {
            params = generateDefaultLayoutParams();
            //如果还是没有系统的参数,我擦,抛出异常吧....
            if (params == null) {
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        //从这里正儿八经的添加一个视图
        addView(child, index, params);
    }

      注意,这里还会先判断一下是否有LayoutParams参数,如果没有就生产一个默认的LayoutParams,接下来调用重载的方法,源码如下:

      

 public void addView(View child, int index, LayoutParams params) {
        if (DBG) {
            System.out.println(this + " addView");
        }

        // 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
        //首先请求布局,此方法是View类的
        requestLayout();
        //这个方法也是View类的方法,此方法里会先进行判断是否有必要进行重绘,如果有必要就重绘
        //没有必要就算了
        invalidate(true);
        //将子视图添加进来
        addViewInner(child, index, params, false);
    }
      接下来继续看最重要的invalidate()方法,该方法在View类里面:

     

 public void invalidate(boolean invalidateCache) {
        //如果不需要重新绘制就直接跳过
        if (skipInvalidate()) {
            return;
        }
        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
                (invalidateCache && (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) ||
                (mPrivateFlags & INVALIDATED) != INVALIDATED || isOpaque() != mLastIsOpaque) {
            mLastIsOpaque = isOpaque();
            mPrivateFlags &= ~DRAWN;
            mPrivateFlags |= DIRTY;
            if (invalidateCache) {
                mPrivateFlags |= INVALIDATED;
                mPrivateFlags &= ~DRAWING_CACHE_VALID;
            }
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            //noinspection PointlessBooleanExpression,ConstantConditions
            if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
                if (p != null && ai != null && ai.mHardwareAccelerated) {
                    // fast-track for GL-enabled applications; just invalidate the whole hierarchy
                    // with a null dirty rect, which tells the ViewAncestor to redraw everything
                    //经过一系列的判断终于来到这里了,此方法是一个重绘入口~
                    //这个方法会在之前已经存在的视图的基础上重绘,因此不再提供一个矩形对象
                    p.invalidateChild(this, null);
                    return;
                }
            }

            if (p != null && ai != null) {
                final Rect r = ai.mTmpInvalRect;
                r.set(0, 0, mRight - mLeft, mBottom - mTop);
                // Don't call invalidate -- we don't want to internally scroll
                // our own bounds
                //首先得说明,Rect类是一个矩形类,它拥有四个属性即:left、top、right、bottom
                //这个方法会把当前视图绘制在此矩形内
                //注意p是一个ViewParent接口对象,该接口是由ViewGroup实现,而所有的布局容器类都继承自ViewGroup类
                //自然的,ViewGroup就重写了invalidateChild()方法,用于实现View在ViewGroup中的层级的
                //该方法最终还是在
                p.invalidateChild(this, r);
            }
        }
    }
    

      OK,我们已经理清了一个layout  xml文件到View的过程,接下来我们开始详细了解View的绘制过程~


======================================== 华丽分隔线 ======================================

     

上面一部分我们介绍了一个布局xml文件到View的转变,那么,这里提出一个根本问题:一个View是在什么时候开始被测量的呢?其实我也不知道,那从哪里入手去寻找答案呢?我也不知道,所以我们先不管这个问题,先看看主线程ActivityThread类吧,这里面有一个main()方法,如下:

public static void main(String[] args) {
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        Process.setArgV0("<pre-initialized>");
        //准备主Looper
        Looper.prepareMainLooper();
        //创建一个ActivityThread实例
        ActivityThread thread = new ActivityThread();
        //调用attach方法,注意这里传递了一个false参数
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        //对AsyncTask进行初始化
        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        //开始消息循环
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
        如上在main方法中除了创建了Looper和一个Handler对象,我们先看看attach(false)做了哪些事情:

     

   private void attach(boolean system) {
        sThreadLocal.set(this);
        //是否是系统线程,刚才也看到了在main()方法中传递过来的是一个false值,表明他不是一个系统线程
        mSystemThread = system;
        if (!system) {
         //ViewRootImpl首次亮相~,它是一个什么东西呢,
         ViewRootImpl.addFirstDrawHandler(new Runnable() {
                public void run() {
                    ensureJitEnabled();
                }
            });
            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                    UserHandle.myUserId());
           //注意mAppThread是一个ApplicationThread类,通过asBinder()方法获取一个IBinder对象,也就是mRemote实例!
           RuntimeInit.setApplicationObject(mAppThread.asBinder());
           //获取一个IActivityManager实例
           IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
              //mAppThread是直接被定义成final的直接new的一个ApplicationThread实例
              mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }
        } else {
            // Don't set application object here -- if the system crashes,
            // we can't display an alert, we just want to die die die.
            android.ddm.DdmHandleAppName.setAppName("system_process",
                                                    UserHandle.myUserId());
            try {
                mInstrumentation = new Instrumentation();
                
                ContextImpl context = new ContextImpl();
                context.init(getSystemContext().mPackageInfo, null, this);
                Application app = Instrumentation.newApplication(Application.class, context);
                mAllApplications.add(app);
                mInitialApplication = app;
                app.onCreate();
            } catch (Exception e) {
                throw new RuntimeException(
                        "Unable to instantiate Application():" + e.toString(), e);
            }
        }

        // add dropbox logging to libcore
        DropBox.setReporter(new DropBoxReporter());

        ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {
            public void onConfigurationChanged(Configuration newConfig) {
                synchronized (mPackages) {
                    // We need to apply this change to the resources
                    // immediately, because upon returning the view
                    // hierarchy will be informed about it.
                    if (applyConfigurationToResourcesLocked(newConfig, null)) {
                        // This actually changed the resources!  Tell
                        // everyone about it.
                        if (mPendingConfiguration == null ||
                                mPendingConfiguration.isOtherSeqNewer(newConfig)) {
                            mPendingConfiguration = newConfig;
                            
                            queueOrSendMessage(H.CONFIGURATION_CHANGED, newConfig);
                        }
                    }
                }
            }
            public void onLowMemory() {
            }
            public void onTrimMemory(int level) {
            }
        });
    }
       在上面attach()中包含了很多操作,主要的有创建IBinder对象(即mRemote),IActivityManager实例,ApplicationThread实例,最关键的是:当Activity启动时都是通过这个ActivityThread中的H类来完成的,H是Handler的子类。上面还出现了一个身影,那就是ViewRootImpl类(在3.0以前是ViewRoot类),这个类控制着DecorView,也就是mDecor实例,可以说是ViewRootImpl控制着View的绘制、刷新等操作,那么我们就从ViewRootImpl开始说起。这篇文章貌似有点长了,还是另外开一篇将View的绘制过程吧!


      

       
 


     

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值