android-----View工作原理系列(二)

        看过《Android开发艺术探索》View的绘制源码之后,里面在讲解绘制最开始执行的方法是ViewRootImpl里面的performTraversals,觉得有点费解,为什么直接就执行到这个方法呢?这中间一定也存在着执行到performTraversals的过程,本着想要了解清楚的想法,看了看源码,在此分享一下:

        上一篇我介绍了invalidate、postInvalidate以及requestLayout的源码,这篇后面部分会用到里面的知识,如果你对这三个方法不太了解,可以看看上一篇博客;

        我们平常会在Activity的onCreate里面调用setContentView设置Activity的布局文件,那么很自然应该从这个方法开始分析,因为他才是我们主动调用的,调用之后经过一系列过程把我们的布局文件中的内容显示到界面上;

        首先查看Activity的setContentView:

public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initActionBar();
    }
        可以发现他调用的是getWindow()的setContentView方法,那么这里getWindow返回值是什么呢?

        Activity#getWindow

public Window getWindow() {
        return mWindow;
    }
        可以看到返回值是mWindow,他的类型是:

private Window mWindow;
        而Window是一个抽象类,我们需要找到mWindow的赋值语句,如下:

mWindow = PolicyManager.makeNewWindow(this);
        可以看到他的值是PolicyManager的静态方法makeNewWindow的返回值,查看这个方法:

 public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }
        该方法直接调用的是sPolicy的makeNewWindow方法,这里的sPolicy是IPolicy类型对象,而IPolicy是接口,所以我们还需要找到他的具体实现,在PolicyManager类最开始,我们找到如下代码:

private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";

    private static final IPolicy sPolicy;

    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();
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
        } catch (InstantiationException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        }
    }
        可以发现sPolicy实际上是通过反射的方式创建的Policy对象,也就是sPolicy.makeNewWindow实际上调用的是Policy.makeNewWindow,进入Policy里面找到makeNewWindow方法:

  public Window makeNewWindow(Context context) {
        return new PhoneWindow(context);
    }
        终于找到了,其实就是返回了一个PhoneWindow对象而已了,回到我们的Activity#setContentView,getWindow().setContentView(layoutResID);实际上执行的是PhoneWindow的setContentView方法啦,那么我们就去PhoneWindow的setContentView里面看看吧:

@Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
        代码不长,但是内容其实挺多的,首先查看mContentParent是否为空,mContentParent是ViewGroup类型对象,如果你是第一次执行setContentView的话,mContentParent的值当然为null了,为null执行installDecor来初始化一个DecorView对象,不为null的话将mContentParent里面的View全部移除掉,从这里也可以看出来setContentView是可以多次调用的,不过第二次调用会移掉第一次已经添加进去的View而已了,因为我们是第一次执行setContentView,那么就该执行installDecor方法了:

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...............
            ...............
            ...............
        }
    }
        这部分代码比较长,我省略了非关键部分,首先判断mDecor是否为null,mDecor是DecorView类型对象,这里我们第一次调用setContentView,那么mDecor为null,执行第3行代码,通过generateDecor生成一个DecorView对象并且返回:

  protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }
        可以看到这个方法就只是创建一个DecorView对象返回而已;

        接着判断mContentParent为null,执行第11行通过generateLayout生成一个ViewGroup对象返回,这里会将刚刚创建的DecorView对象传递进去,很自然该看看generateLayout的源码了:

        鉴于generateLayout的源码比较长,我截取了对我们有用的部分用伪代码的方式来进行分析:

 protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        //1.获得窗口style属性
        TypedArray a = getWindowStyle();
        
        //根据这些style属性来设置窗口是否显示标题、是否浮动、是都有动画等等
        .............
        .............

        //2.获取feature(风格)属性,来选择不同的窗口修饰布局文件
        int layoutResource;
        int features = getLocalFeatures();
        //接下来就是根据不同的features值来给layoutResource赋不同的布局文件id
        .............
        .............
        //3.加载选定好的布局文件,将其添加至DecorView上面,并且指定contentParent的值
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        .............
        .............
        //4.设置一些background、title之类的属性
 }
        generateLayout具体的执行过程在伪代码里面已经解释的比较清楚了,这里我们补充点知识,在generateLayout里面第2步中,我们通过getLocalFeatures获取到了风格属性,一般来说我们设置窗体风格属性的方式有两种:(1):通过requestFeature()指定,相应的获取方法就是getLocalFeatures了;(2):通过requestWindowFeature或者在Activity的xml配置中设置属性:android:theme=" ",相应的获取方法是getWindowStyle;我们来看下其中一个Activity窗口修饰布局文件是R.layout.screen_title,也就是下面这样子的布局文件:

<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就是我们的标题,第2个FrameLayout是内容存放地方,如果你仔细看的话,会发现generateLayout伪代码里面的第19行ID_ANDROID_CONTENT的值其实就是第二个FrameLayout的id值,这样的话,我们的generateLayout代码分析结束了;

        那么我们回到PhoneWindow里面的setContentView方法里面,发现此时mContentParent的值将等于我们上面布局中的第二个FrameLayout,继续分析PhoneWindow里面的setContentView方法,接下来执行的将是mLayoutInflater.inflate(layoutResID, mContentParent);也就是把我们当前的布局添加到了mContentParent里面,这里也说明了一点我们平常添加的布局文件其实只是Activity总体布局中第2个FrameLayout部分的子布局而已,这点我将会在这篇博客最后面通过实例来进行验证,很自然,这里是通过LayoutInflater的inflate方法将当前布局添加进去的,这也解释了为什么说setContentView其实也是通过inflate来进行布局解析并且添加到界面中了;那么我们就该看看LayoutInflater的inflate的源码了:

        这个方法的具体源码分析大家可以看我的另外一篇博客android------LayoutInflater的inflate方法详解,在这里我只是讲解我们用到的部分:

        LayoutInflater#inflate

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            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
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();
                
                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }

                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");
                    }

                    rInflate(parser, root, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    View temp;
                    if (TAG_1995.equals(name)) {
                        temp = new BlinkLayout(mContext, attrs);
                    } else {
                        temp = createViewFromTag(root, name, attrs);
                    }

                    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 = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }
                    // Inflate all children under temp
                    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) {
                        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;
            }

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);

            return result;
        }
    }

        这里首先解析根布局,在第45行通过createViewFromTag创建出根布局view,具体里面是采用反射实现的,将根布局view赋值给temp,接着在第68行调用rInflate,将根布局View作为参数传递进去,这个方法会循环解析根布局下面的所有子元素,并且将他们添加到根布局中,我们可以看看rInflate这个方法:

        LayoutInflater#rInflate

 void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
            boolean finishInflate) 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)) {
                parseRequestFocus(parser, parent);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else if (TAG_1995.equals(name)) {
                final View view = new BlinkLayout(mContext, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflate(parser, view, attrs, true);
                viewGroup.addView(view, params);                
            } else {
                final View view = createViewFromTag(parent, name, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflate(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (finishInflate) parent.onFinishInflate();
    }
        可以看到第7行是一个while循环,就是用来遍历根布局view下面子元素的,除了在解析的过程中抛出一些异常之外,我们会发现第30行以及第36行都执行了viewGroup.addView(view, params);这句话,这句话的主要作用就是将当前遍历到的子元素view添加到vireGroup中,这里的viewGroup就是我们传入的参数---根布局view,那么我们就该看看addView中做了些什么操作了;

        这个方法位于ViewGroup里面:

        ViewGroup#addView

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
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }
        看到了我们熟悉的requestLayout以及invalidate,在上一篇博客我已经分析了这两个方法是怎么执行到performTraversals的过程,大家可以去上一篇看看;

        到这里的话,我们分析了通过Activity的setContentView执行到了ViewRootImpl里面的performTraversals的过程,接下来从performTraversals开始就是我们View的绘制过程了,也就是measure、layout、draw过程了,这个部分网上源码分析很多的,我只会在下篇从伪代码的角度进行总结;

         接下来我通过实例来验证我在上面说到的平常我们添加的View其实只是添加到DecorView布局里面第2个Fragment部分:

        首先来看下布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/linearLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="验证按钮"/>
</LinearLayout>
        接着就是MainActivity了:

public class MainActivity extends Activity {

	public Button mButton;
	public LinearLayout mLinearLayout;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mLinearLayout = (LinearLayout) findViewById(R.id.linearLayout);
		ViewParent parent = mLinearLayout.getParent();
		System.out.println(parent);
		System.out.println(parent.getParent());
	}
}

         点击按钮查看Logcat输出:

07-05 05:21:26.618: I/System.out(2586): android.widget.FrameLayout{4175cf98 V.E..... ......I. 0,0-0,0 #1020002 android:id/content}
07-05 05:21:26.648: I/System.out(2586): android.widget.LinearLayout{41756830 V.E..... ......I. 0,0-0,0}

        我们获取到了LinearLayout,随后通过getParent获取到他的父布局,同样调用getParent获得父布局的父布局,可以看到第一行输出是FrameLayout,验证了我们上面说的setContentView只是将参数中的布局添加到FrameLayout里面,而第二行输出LinearLayout的原因是我们在Manifest里面设置Activity的theme主题是:

        android:theme="@android:style/Theme"

        接下来以一张图来说明Activity布局格局:

android-----View工作原理系列(二)_第1张图片




你可能感兴趣的:(view,View工作原理,View视图绘制,View绘制过程)