美文网首页
activtiy中setContentView布局加载

activtiy中setContentView布局加载

作者: 刘孙猫咪 | 来源:发表于2017-07-07 10:47 被阅读0次

在activity中调用setContentView,传入xml文件,就会有相应的ui呈现出来,是不是感觉很简单,调用setContentView一个方法就轻松实现了,其实setContentView底层是做了很多处理,接下来就一层一层点进去,看下setContentView的源码;


setContentView.png

点进activity的源码找到setContentView()方法,

    /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the 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(@LayoutRes int layoutResID) {
       //调用Window中的setContentView
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

getWindow()获取到的是一个Window对象,调用的是Window中的setContentView()方法,

public abstract void setContentView(@LayoutRes int layoutResID);

会看到Window中的setContentView()方法是一个抽象方法,应该找Window的子类中的setContentView();方法,PhoneWindow是Window子类,找到PhoneWindow中的setContentView();方法

  @Override
    public void setContentView(int layoutResID) {    
        // mContentParent 就是一个ViewGroup 首次加载的时候mContentParent 肯定是空的,空的话就会调用 installDecor()方法,可以获取到一个DecorView对象
        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 {
            //通过inflate解析加载布局资源
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

mContentParent是一个ViewGroup,在首次创建activity时,mContentParent是为空的,就会调用installDecor();方法,

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
           //获取到一个DecorView 首次加载的时候DecorView对象是空的,调用generateDecor()方法进行初始化
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            //DecorView不为null时,通过DecorView设置Window
            mDecor.setWindow(this);
        }
        //给mContentParent赋值
        if (mContentParent == null) {
           //这里的mContentParent对象还是空的,根据上面初始化获取的DecorView对象并调用generateLayout()方法,获取ViewGroup
            mContentParent = generateLayout(mDecor);
            ...
            } else {
               ...
            }
            if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
                mDecor.setBackgroundFallback(mBackgroundFallbackResource);
            }
            if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
                ....
            }
        }
    }

这里的mContentParent对象还是空的,根据上面初始化获取的DecorView对象并调用generateLayout()方法,获取ViewGroup

protected ViewGroup generateLayout(DecorView decor) {
        ...
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
       //下面这些是系统的布局
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } 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;
            // System.out.println("Simple!");
        }
        mDecor.startChanging();
        //调用DecorView中的onResourcesLoaded(),利用LayoutInflater去加载布局
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        //通过findViewById获取对应的ViewGroup,并将ViewGroup返回,到这里mContentParent就已经初始化好了,
        //ID_ANDROID_CONTENT这个id其实就是com.android.internal.R.id.content
       ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...
        return contentParent;
    }

根据上面获取到的layoutResource布局资源和LayoutInflater对象调用DecorView中的onResourcesLoaded()方法从而加载布局,这里需要注意的是layoutResource是系统的布局资源id,并不是在activity中setContentView时设置的布局资源id

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ...
        mDecorCaptionView = createDecorCaptionView(inflater);
        //利用LayoutInflater对象中的inflate方法进行布局文件的加载,这里加载的是一个系统资源布局,并返回一个View对象
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            //DecorCaptionView不为空的话,就调用DecorCaptionView中的addView()方法将解析的View添加进去
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
          //如果DecorCaptionView对象为空的话,就调用ViewGroup中的addView()方法,将解析的View添加进去
            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

同过上面这些就将系统资源布局添加进去了,在这里是采用LayoutInflater来解析获取到的布局文件并返回一个View对象;在fragment中或者很多时候要加载布局的时候,都会用到LayoutInflater,经常使用的有三种加载布局的方式;

View.inflate(Context context, @LayoutRes int resource, ViewGroup root);
LayoutInflater.from(Context mContext).inflate(@LayoutRes int resource, @Nullable ViewGroup root);
LayoutInflater.from(Context mContext).inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot);

先看下这个三种加载方式各自的源码;
View.inflate:

public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
    }

LayoutInflater.from:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

在用后面两种方式的时候,首先要调用LayoutInflater中的from方法,返回的是一个LayoutInflater

    /**
     * Obtains the LayoutInflater from the given context.
     */
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

点击去发现Context中的getSystemService又是一个抽象方法,

public abstract Object getSystemService(@ServiceName @NonNull String name);

那就只能去找Context的子类ContextImpl了,找到ContextImpl中的getSystemService();

  @Override
    public String getSystemServiceName(Class<?> serviceClass) {
        return SystemServiceRegistry.getSystemServiceName(serviceClass);
    }

到这里又涉及到SystemServiceRegistry,这类里面注册了很多服务;第一种和第二种方式往里面看的时候其实调用的都是第三种方式,

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

在这里可以获取到一个XmlResourceParser解析器去解析xml文件,

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...
            try {
                ...             
                if (DEBUG) {
                    ...
                }
                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
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
                    ...
                }
            } catch (XmlPullParserException e) {
               ...
            } catch (Exception e) {
                ...
            } finally {
                ...
            }
            return result;
        }
    }

找到createViewFromTag点进去,

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        ...
        try {
            View 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 {
                    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) {
            ...
        }
    }

对xml布局文件解析完成后会继续走onResourcesLoaded(LayoutInflater inflater, int layoutResource)方法中的代码,会根据DecorCaptionView对象是否为空,来addView,如果DecorCaptionView对象为空,调用的是ViewGroup中的addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT))方法,

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

        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }

        // 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进行布局摆放
        requestLayout();
        //调用invalidate进行view绘制渲染
        invalidate(true);
        addViewInner(child, index, params, false);
    }

系统布局解析完成后,将会将解析得到的view通过addView方法添加到DecorView布局中,在addView中就会去调用requestLayout进行摆放和invalidate进行绘制和渲染,在调用requestLayout时就会去调用View中的requestLayout方法;

public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            //获取ViewRootImpl实例,View的绘制流程大部分都是在ViewRootImpl类中完成的,
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            //ViewParent是一个接口,ViewGroup和ViewRootImpl是它的实现类,就要去看ViewRootImpl类中的requestLayout方法
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

通过一步步调用就到了ViewRootImpl中的requestLayout方法中,在该方法中首先会进行线程的检查,接着会去绘制渲染view;具体可以看invalidate和postInvalidate使用场景及源码解析

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //检查当前线程是否是ui线程,如果不是ui线程会抛异常
            checkThread();
            mLayoutRequested = true;
            //进行页面的绘制和渲染
            scheduleTraversals();
        }
    }

到这里的话,其实还只是将系统的布局资源添加到DecorView中,并没有将activity中setContentView的布局资源添加到系统中;接着看PhoneWindow中的setContentView方法;

@Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        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);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

上面走了installDecor()方法实例化了一个DecorView,对mContentParent进行了赋值,并将mContentParent添加到初始化好的DecorView中,在mLayoutInflater.inflate(layoutResID, mContentParent);这句代码执行后才将setContentView的资源布局添加到mContentParent系统布局中。

总结:在activity中调用setContentView后,会去调用PhoneWindow中的setContentView方法,在setContentView方法中通过installDecor()方法实例化一个DecorView,对mContentParent进行赋值并添加到实例化好的DecorView中,接着通过mLayoutInflater.inflate将setContentView设置的布局资源添加到mContentParent系统布局中。

相关文章

网友评论

      本文标题:activtiy中setContentView布局加载

      本文链接:https://www.haomeiwen.com/subject/fzklhxtx.html