源码分析:Activity加载并显示View的流程分析(一)

我们在用Activity的时候,一般都是在onCreate()方法中设置setContentView()方法。然后,进去Activity的话,页面就加载出来了。这里就分析下,他是怎么一步步的显示出来的。

我们先分析下Activity::setContentView(layoutId)之后,都做了什么。

Activity::setContentView(layoutId)

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

很简单,就两行代码。
1,首先是获取Window。之前的文章我们分析过,这个Window是在Activity的attach()方法中初始化的,其实就是PhoneWindow。所以,这里调用的就是PhoneWindow的setContentView(layoutResID)。
2,执行 initWindowDecorActionBar()方法,这个跟我们现在的分析无关,先不看。

先看第一个。

PhoneWindow::setContentView(layoutResID)方法

public class PhoneWindow extends Window implements MenuBuilder.Callback {

	...

	@Override
    public void setContentView(int layoutResID) {
		//如果mContentParent是null。第一次调用的时候
        if (mContentParent == null) {
			//加载DecorView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
			//如果设置FEATURE_CONTENT_TRANSITIONS的话,通过Scene启动
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
			//否则,通过LayoutInflater解析成View
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
		//这里是设置各种回调。
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

	...

}

这里方法主要是做了两件事。

  • 1,installDecor();
  • 2,mLayoutInflater.inflate(layoutResID, mContentParent);

先看下installDecor()方法

PhoneWindow的installDecor()方法

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
			//这里创建了DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
			//这里创建了mContentParent
            mContentParent = generateLayout(mDecor);

            ...

        }
    }

这里主要做了2件事:

  • 1,generateDecor(-1)方法创建DecorView
  • 2,generateLayout(mDecor)方法创建mContentParent

先看下PhoneWindow的generateDecor(-1)方法

PhoneWindow的generateDecor(-1)方法
protected DecorView generateDecor(int featureId) {
       
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext().getResources());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
		//这里创建出来了DecorView
        return new DecorView(context, featureId, this, getAttributes());
    }

这里可以看到创建出来了DecorView。
我们进DecorView看下

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
	DecorView(Context context, int featureId, PhoneWindow window,
            WindowManager.LayoutParams params) {
        super(context);
        
		...

        setWindow(window);
	
		...        
    }


}

到这里PhoneWindow的generateDecor(-1)方法就看完了。这个方法创建了一个DecorView。
这个DecorView继承了FrameLayout,并且把里面保存了PhoneWindow。

PhoneWindow的generateLayout(mDecor)方法。

通过上面的差un更加爱你。这里的mDecor就是DecorView。

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

        TypedArray a = getWindowStyle();


       
		//设置NoTitle
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // 设置ActionBar
            requestFeature(FEATURE_ACTION_BAR);
        }

       ...
		//设置其他Feature
		
		//设置全屏
        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }
		//设置状态栏透明
        if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
                false)) {
            setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                    & (~getForcedWindowFlags()));
        }
		
		...  

        // 渲染Window的DecorView
		
		//layout资源
        int layoutResource;
        int features = getLocalFeatures();
		
		//根据features拿到不同的layout资源xml文件
	
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 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 = R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
        }


        mDecor.startChanging();
		//调用DecorView的onResourcesLoaded(LayoutInflater, 根据features获取的layout资源)
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
		//获取DecorView里面的ID_ANDROID_CONTENT来获取ViewGroup
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        
        ...

        mDecor.finishChanging();
		//返回找到的id是ID_ANDROID_CONTENT的ViewGroup
        return contentParent;
    }

这个里面主要做的事情:

  • 1,设置feature(NoTitle等等),flag(全屏等等)。
  • 2,根据设置feature来获取不同的layout资源文件。
  • 3,调用DecorView的onResourcesLoaded()
  • 4,在DecorView里找到id是ID_ANDROID_CONTENT的ViewGroup并返回

到这里我们知道PhoneWindow里的mContentParent,实际上就是DecorView里面id是ID_ANDROID_CONTENT(android.R.id.content)的ViewGroup。

到这里PhoneWindow的installDecor()方法,大致就看完了。
这里我们在看下DecorView的onResourcesLoaded()方法是怎么把layout文件加载到DecorView的。

DecorView的onResourcesLoaded()方法

在看这个方法的之前,先看一个根据feature获取的layout文件。

R.layout.screen_simple.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

这个layout资源有一个action_mode_bar_stub的ViewStub,还有一个id是ID_ANDROID_CONTENT(android.R.id.content)的FrameLayout。所以上面找到的mContentParent,其实就是下面的这个FrameLayout。

看下onResourcesLoaded()方法

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
       
		...

        final View root = inflater.inflate(layoutResource, null);
       
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
       
		...
        
    }

这里做了2件事:

  • 1,inflater.inflate(layoutResource, null);
  • 2,addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

通过LayoutInflater的inflate()方法,渲染出来View。然后,添加到DecorView里面。

先看下inflater.inflate(layoutResource, null)方法

inflater.inflate(layoutResource, null)方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
		//重载
        return inflate(resource, root, root != null);
    }

...

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
				
           		//如果是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");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
					//创建临时的root view
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

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

                    
                    //渲染所有的子view
                    rInflateChildren(parser, temp, attrs, true);

					//把创建的临时view添加到root上
					//如果root不是null 并且 attachToRoot(添加到root上),是true的话
					if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // 返回创建的临时root view
					//如果root等于空 或 attachToRoot(添加到root上),是false的话
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

           ...

            return result;
        }
    }

这个主要做了:
1,通过createViewFromTag(root, name,…)这个方法创建临时的名字是name的 view
2,通过rInflateChildren()方法,创建下面的所有的子View

在看下这两个方法的具体实现。
先看下1,通过createViewFromTag(root, name,…)这个方法创建临时的root view

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
		//如果标签是view开头的<view ...>
        if (name.equals("view")) {
			//名字就是通过这个标签的class属性来获取
            name = attrs.getAttributeValue(null, "class");
        }

        ...
		
		//如果名字是"blink"
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
			//直接返回BlinkLayout
            return new BlinkLayout(context, attrs);
        }

        	//获取view
            View view;

			//通过LayoutInflater.Factory2获取View
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
					//判断标签的名字上是否有点(support,自定义View)
					//系统的view
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
						//标签有点,非系统View
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;

			...
    }

这里主要是根据条件,通过不同的方式来生成view。

看下onCreateView()方法。
如果是系统的View的话,中间的

protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
		//重载。自己添加前缀。下面看
        return createView(name, "android.view.", attrs);
    }
...

public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
		//通过名字,先从缓存获取
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;

			//如果缓存没有
            if (constructor == null) {
                // 尝试通过拼接后获取"android.view.TextView"
                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
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
				//放到缓存里面
                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 lastContext = mConstructorArgs[0];
            if (mConstructorArgs[0] == null) {
                // Fill in the context if not already within inflation.
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;
			//通过constructor跟参数。来拿到View
            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]));
            }
            mConstructorArgs[0] = lastContext;
            return view;
    }


非系统的View初始化。createView()方法,在上面也分析了,前缀的prefix前缀传的null。所以,在尝试拼接的时候,直接用的标签的名字。

2,通过rInflateChildren()方法,创建下面的所有的子View。

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

	...

	void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;
		//循环遍历
        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的标签
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
				//如果标签是tag
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
				//如果标签是include
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
				//如果标签是merge
                throw new InflateException("<merge /> must be the root element");
            } else {
				//获取到name的View
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
				//这里调用了rInflateChildren()
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }
		
		...	
	
    }
	
...

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
		//这里调用了rInflateView()方法。
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
这里通过调用rInflateChildren(),调用rInflate()方法. 这个方法就是不断的遍历里面的标签。然后,通过createViewFromTag()方法创建出View。 如果这个View是ViewGroup的话,再次调用rInflateChildren()方法,调用rInflate()方法。遍历这个ViewGroup的子View。

通过上面分析,我们知道这个mContentParent就是内容区域的根View(android.R.id.content)。
layoutResID就是我们设置的layout名字(R.layout.xx)。

这个方法的作用就是,首先,把layout资源文件转换成View。
其次,只针对这个两个参数的方法。mContentParent不是空的话。就把layout转换的View添加到这个mContentParent里面,最后返回这个转换的View。如果 mContentParent是空的话,不添加,返回这个转换的View。

上面我们已经知道了mContentParent不是null。所以,我们设置的layout资源转换的View会被添加到mCotentParent里面。到这里,整个的DecorView的内容就添加完成了。


到这里,我们整个的Activity加载View中的setContentView()方法就分析完了。 这里主要是:创建PhoneWindow中的DecorView和DecorView中的mContentParent(android.R.id.content)。 然后,把我们设置的layout资源文件,转化成View,并添加到mContentParent里面。

但是,这里,我们知道了layout文件添加到了DecorView里面。并没有测量,绘制等等。

下一篇,来分析,DecorView是怎么经过测量,绘制等,显示出来的。
源码分析:Activity加载并显示View的流程分析(二)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要将一个Activity转换成一个View显示,可以按以下步骤操作: 1. 在Activity中,创建一个布局文件,用来显示界面上的各个控件和视图。 2. 在Activity中重写onCreateView()方法,在该方法中加载布局文件,生成View对象并返回。 3. 在Activity中获取FragmentManager对象,并通过调用FragmentManager.beginTransaction()方法创建一个FragmentTransaction对象。 4. 在FragmentTransaction对象中,使用add()方法将生成的View对象添加到要显示它的布局容器中。 5. 调用commit()方法提交事务,完成将Activity转换成View显示的操作。 以下是一个简单的示例代码: ``` public class MyActivity extends Activity { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // 加载布局文件 View view = inflater.inflate(R.layout.my_layout, container, false); // 返回生成的View对象 return view; } public void showAsView(ViewGroup container) { // 获取FragmentManager对象 FragmentManager fragmentManager = getFragmentManager(); // 创建FragmentTransaction对象 FragmentTransaction transaction = fragmentManager.beginTransaction(); // 将生成的View对象添加到要显示它的布局容器中 transaction.add(container.getId(), this); // 提交事务,完成将Activity转换成View显示的操作 transaction.commit(); } } ``` 在代码中,MyActivity继承自Activity类,并重写了onCreateView()方法,用来加载布局文件并生成View对象。同时,还提供了一个showAsView()方法,用来将该Activity转换成View显示在指定的布局容器中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值