浅谈Android之Activity Decor View创建流程介绍

6 Activity DecorView创建流程介绍

上头已经完整的介绍了Activity的启动流程,Activity是如何绑定Window,Window的décor view是如何通过ViewRootImpl与WMS建立关联的,也就是说,整个框架已经有了,唯一缺的就是Activity如何初始化创建DecorView了。

接下去通过相对不是那么复杂的LinearLayout,来完整的介绍Décor View以及其内部

ContentView的详细创建过程

6.1 DecorView的初始化和布局介绍

首先定义一个Layout文件activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:id="@+id/activity_main"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical">

    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Hello World --- One" />

    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Hello World --- Two" />

</LinearLayout>

这个Layout布局很简单,LinearLayout内置两个TextView,接着在Activity.onCreate的时候将其作为ContentView设置到Activity:

protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

}

接着看setContentView:

//Activity.java

public void setContentView(int layoutResID) {

    getWindow().setContentView(layoutResID);

    initWindowDecorActionBar();

}

最终会把content view设置到Activity关联Window那,接着看PhoneWindow.setContentView:

    public void setContentView(int layoutResID) {

        if (mContentParent == null) {

            installDecor();

        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

            mContentParent.removeAllViews();

        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,

                    getContext());

            transitionTo(newScene);

        } else {

            mLayoutInflater.inflate(layoutResID, mContentParent);

        }

        final Callback cb = getCallback();

        if (cb != null && !isDestroyed()) {

            cb.onContentChanged();

        }

    }

这里ContentParent肯定为null,接着调用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 View在初始状态下肯定为null,所以这里肯定会调用generateDecor创建DecorView:

protected DecorView generateDecor() {

return new DecorView(getContext(), -1);

}

DecorView派生自FrameLayout:

private final class DecorView extends FrameLayout

由于mContentParent在初始状态下也为空,所以接着调用generateLayout创建Content Root

View:

protected ViewGroup generateLayout(DecorView decor) {

        ……

        TypedArray a = getWindowStyle();

        ……

        mDecor.startChanging();

        View in = mLayoutInflater.inflate(layoutResource, null);

        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

        if (contentParent == null) {

            throw new RuntimeException("Window couldn't find content container view");

        }

        ……

mDecor.finishChanging();

        return contentParent;

    }

先根据layoutResource对应的layout文件创建View作为mContentRoot并添加到DecorView,接着从mContentRoot查找名称为ID_ANDROID_CONTENT的child view作为content Parent并最终保存到mContentParent.

installDecor完成后,接着我们回到setContentView, 接着执行的代码为:

mLayoutInflater.inflate(layoutResID, mContentParent);

根据layoutResID指定的layout文件创建View并添加到mContentParent中

setContentView结束后,DecorView创建完成,接下去我们看下DécorView完整的布局结构图:


通过图可以很清晰的看出,DecorView是top level view,Content Root是其子视图,Content Root用于显示内容,主要包含:

1)  Action bar view

2)  Content Parent view

我们在Activity.onCreate中调用setContentView设置的自定义View是添加到Content Parent

View中的

6.2 inflate view介绍

在开发过程中,我们可以通过编写layout文件,然后在代码中使用inflate并传入该layout文件实现对View的创建,比如上文中ContentView的创建:

mLayoutInflater.inflate(layoutResID, mContentParent);

上面代码通过inflate创建layoutResID对应的View并添加到mContentParent中,接下去详细介绍下inflate的实现

为了便于后续分析,先简单介绍下在app工程编译时,layout文件是被怎样处理的,从代码上看,Android应该是支持两种处理方式的

比如原先的Layout内部描述是这样的:

<LinearLayout >

    <TextView />

    <TextView />

</LinearLayout>

第一种处理方式:

将layout内部所有View的tag从类名改成view,原先的tag在处理完后,会被保存到名称为class的属性字段中,编译处理后,会变成:

<view

class=”LinearLayout”>

    <view class=”TextView” />

    <view class=”TextView” />

</view>

Tag统一改为view,然后添加class属性用于描述view的class path,如果是自定义视图,这里需要给出全路径,如果是系统定义视图,则给出相对路径即可,inflate在解析的时候,会自动补全android.view.前缀

第二种处理方式:

编译处理后,会变成:

<android.view.LinearLayout”>

    <android.view.TextView />

    <android.view.TextView />

</view>

也就是说,在TAG name中把class path补全了

最新的系统版本应该都用的第二种方式,所以后续我们就基于第二种方式来分析

最后,android打包工具会将layout数据重新格式化并保存到新的数据结构中,然后保存到layout同名文件并最终打包到apk中。

接下去通过代码,一步一步分析inflate的整个过程

先看下LayoutInflater的初始化代码:

mLayoutInflater = LayoutInflater.from(context);

接着看LayoutInflater.from:

public static LayoutInflater from(Context context) {

LayoutInflater LayoutInflater =

                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        ……

        return LayoutInflater;

}

其实就是对context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)调用做下封装,ContextImpl里头最终调用PolicyManager.makeNewLayoutInflater->

Policy. makeNewLayoutInflater创建LayoutInflater:

//Policy.java

public LayoutInflater makeNewLayoutInflater(Context context) {

        return new PhoneLayoutInflater(context);

}

LayoutInflater创建结束,接着看对其inflate函数的调用

PhoneLayoutInflate派生自LayoutInflater,不过它没重新实现啥函数,基本上还是使用LayoutInflate的实现,inflate函数也是:

//LayoutInflater.java

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

        final Resources res = getContext().getResources();

        final XmlResourceParser parser = res.getLayout(resource);

        try {

            return inflate(parser, root, attachToRoot);

        } finally {

            parser.close();

        }

    }

由于root不为空,所以attachToRoot为true

先调用res.getLayout获取resource对应layout文件的配置数据并保存到parser中,接着调用inflate对应的重载函数:

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;

            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 (TAG_MERGE.equals(name)) {

                    ……

                } else {

                    // Temp is the root view that was found in the xml

                    final View temp = createViewFromTag(root, name, attrs, false);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {

                        ……

                        // 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);

                        }

                    }

                    ……

                    // Inflate all children under temp

                    rInflate(parser, temp, attrs, true, true);

                    ……

                    // 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) {

                ……

            } catch (IOException e) {

                ……

            } finally {

                // Don't retain static reference on context.

                mConstructorArgs[0] = lastContext;

                mConstructorArgs[1] = null;

            }

            return result;

        }

}

首先通过parser拿到关联的AttributeSet,接着通过while循环,直到解析到类型为

XmlPullParser.START_TAG为止,由于我们的TAG类型肯定不是TAG_MERGE,所以接着走到else部分代码,这里先调用createViewFromTag创建tag对应的view, 也就是Layout文件中的Top Parent View,接着调用rInflate递归创建其全部childviews

先看createViewFromTag的实现:

//LayoutInflater.java 

View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) {

        if (name.equals("view")) {

            name = attrs.getAttributeValue(null, "class");

        }

        Context viewContext;

        if (parent != null && inheritContext) {

            viewContext = parent.getContext();

        } else {

            viewContext = mContext;

        }

        // Apply a theme wrapper, if requested.

        final TypedArray ta = viewContext.obtainStyledAttributes(attrs, ATTRS_THEME);

        final int themeResId = ta.getResourceId(0, 0);

        if (themeResId != 0) {

            viewContext = new ContextThemeWrapper(viewContext, themeResId);

        }

        ta.recycle();

        if (name.equals(TAG_1995)) {

            // Let's party like it's 1995!

            return new BlinkLayout(viewContext, attrs);

        }

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

        try {

            View view;

            if (mFactory2 != null) {

                view = mFactory2.onCreateView(parent, name, viewContext, attrs);

            } else if (mFactory != null) {

                view = mFactory.onCreateView(name, viewContext, attrs);

            } else {

                view = null;

            }

            if (view == null && mPrivateFactory != null) {

                view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs);

            }

            if (view == null) {

                final Object lastContext = mConstructorArgs[0];

                mConstructorArgs[0] = viewContext;

                try {

                    if (-1 == name.indexOf('.')) {

                        view =onCreateView(parent, name, attrs);

                    } else {

                        view = createView(name, null, attrs);

                    }

                } finally {

                    mConstructorArgs[0] = lastContext;

                }

            }

            return view;

        } catch (InflateException e) {

            ……

        } catch (ClassNotFoundException e) {

           ……

        } catch (Exception e) {

           ……

        }

    }

由于这里name本身就对应类的class path,所以equals(“view”)肯定为false,在默认情况下,

mFactory2,mFactory以及mPrivateFactory都是未设置的,类名肯定是包含”.”的,接着调用

createView(name, null, attrs):

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 {

                ……

            }

            Object[] args = mConstructorArgs;

            args[1] = attrs;

            constructor.setAccessible(true);

            final View view = constructor.newInstance(args);

            if (view instanceof ViewStub) {

                // Use the same context when inflating ViewStub later.

                final ViewStub viewStub = (ViewStub) view;

                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));

            }

            return view;

        }

        ……

}

sConstructorMap是缓存map,如果已经load过的class会被缓存在其中,后续就可直接通过name从map中拿到对应的class数据,由于咱们是第一次创建,这里constructor肯定为空,接着调用loadclass加载name对应的class,然后保存到sConstructorMap中

最后调用view对应的构造函数并传入attrs,就这样,view被创建成功了,由于在构造的时候传入了AttributeSet对象,所以,View必须在构造时通过attrs读取并保存对应的属性数据以完成View的初始化。

createViewFromTag介绍完后,在inflate内部,接着rinflate函数被调用用以递归创建当前元素的所有child views:

void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,

            boolean finishInflate, boolean inheritContext) 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)) {

                ……

            } else if (TAG_TAG.equals(name)) {

                ……

            } else if (TAG_INCLUDE.equals(name)) {

                ……

            } else if (TAG_MERGE.equals(name)) {

                ……

            } else {

                final View view = createViewFromTag(parent, name, attrs, inheritContext);

                final ViewGroup viewGroup = (ViewGroup) parent;

                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);

                rInflate(parser, view, attrs, true, true);

                viewGroup.addView(view, params);

            }

        }

        if (finishInflate) parent.onFinishInflate();

    }

看while循环的条件,只有两种情况while循环才会退出或者不进入:

1)      Xml标签类型为XmlPullParser.END_TAG并且parser.getDepth() <=depth

2)      Xml标签类型为XmlPullParser.END_DOCUMENT

再加上循环内部:

if (type != XmlPullParser.START_TAG) {

     continue;

}

代码的存在,也就是说,循环只会处理XML START_TAG标签类型

整个逻辑总结如下:

1)  根据当前元素的START_TAG标签来触发调用createViewFromTag创建对应view,接着将这个View作为parent view参数在rinflate被调用时传入,这时通过rinflate的while函数的判断条件,就会存在两种情况

2)  第一种是,这个元素没有子元素了,那parser.next获取的必定是当前元素的END_TAG,并且由于深度相同,即parser.getDepth() == depth,while循环不会进入,表明元素已经创建完成,接着调用if(finishInflate) parent.onFinishInflate(),然后结束本次rinflate调用

3)  第二种是,这个元素存在子元素,rinflate的while循环会遍历其下一层的所有子元素,每个子元素的处理流程回到(1)步骤,子元素通过rinflate递归调用结束后,再将创建的子元素view通过viewGroup.addView(view,params)添加到当前元素对应的viewgroup中,

最后parser.next碰到当前元素的END_TAG标签,并且parser.getDepth() == depth,当前元素全部子元素创建结束,接着调用if (finishInflate) parent.onFinishInflate(),然后结束本次rinflate调用

感觉递归在处理这种内部层层嵌套的数据结构解析,真的非常好用

通过上头还可以看出,View.onFinishInflate这个回调,是在View被构造结束并且完成对其child view的创建添加后才被调用的,也就是说,咱们在这个回调函数里,能够读取View的默认参数和child views,仅此而已.

发布了46 篇原创文章 · 获赞 25 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/zhejiang9/article/details/55096589