setContentView源码解读

setContentView方法是我们Activity里面必须要用的方法,那么当我们调用这个方法的时候系统到底做了什么呢?现在来一探究竟

首先查看Activity.java

 public void setContentView(@LayoutRes int layoutResID) {

        getWindow().setContentView(layoutResID);

        initWindowDecorActionBar();

}

里面其实是把view设置到getWindow()对象上面,getWindow()返回的是mWindowmWindow就是一个Window对象的引用,点进WindowsetContent方法,发现Window是一个抽象类.mWindow是什么时候赋值的呢,并且实际类型是什么呢?

    public Window getWindow() {

        return mWindow;

    }

attach方法里面我们看到其实mWindow实际是一个PhoneWindow

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, String referrer, IVoiceInteractor voiceInteractor,

            Window window, ActivityConfigCallback activityConfigCallback) {

       ....省略部分代码

        mWindow = new PhoneWindow(this, window, activityConfigCallback);

        mWindow.setWindowControllerCallback(this);

        mWindow.setCallback(this);

        mWindow.setOnWindowDismissedCallback(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);

        }

        mUiThread = Thread.currentThread();

        mMainThread = aThread;

        mInstrumentation = instr;

        mToken = token;

        mIdent = ident;

        mApplication = application;

       ....省略部分代码

//疑问1

 mWindow.setWindowManager(

                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),

                mToken, mComponent.flattenToString(),

                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

    }

于是我们去PhoneWindow里面查找setContent方法

PhoneWindow.java

 @Override

    public void setContentView(int layoutResID) {

        if (mContentParent == null) {

            installDecor();

        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

            mContentParent.removeAllViews();

        }

     ..省略部分代码

    }

第一次调用setContentView方法时,mContentParent为空,走installDecor方法,点到installDecor方法里面去看

private void installDecor() {

        mForceDecorInstall = false;

        if (mDecor == null) {

            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 = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.

            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(

                    R.id.decor_content_parent);

            if (decorContentParent != null) {

                mDecorContentParent = decorContentParent;

                mDecorContentParent.setWindowCallback(getCallback());

                if (mDecorContentParent.getTitle() == null) {

                    mDecorContentParent.setWindowTitle(mTitle);

                }

            }

        }

}

同样的mDecor 也是空,调用generateDecor方法生成一个DecorView,DecorView是个啥类呢,DecorView是一个继承了FrameLayout ViewGroup。而且该类是被hide的类,也就是说只有系统可以用。

 protected DecorView generateDecor(int featureId) {

        // activity.

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

        }

        return new DecorView(context, featureId, this, getAttributes());

    }

继续查看installDecor方法,mDecor 已经生成。mContentParent 前面我们知道也为空,这个时候调用generateLayout方法返回的

private void installDecor() {

            if (mDecor == null) {

            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 = generateLayout(mDecor);

..//省略后面代码

}

于是ctrl右击到generateLayout方法

 protected ViewGroup generateLayout(DecorView decor) {

        

        TypedArray a = getWindowStyle();

      ...//省略部分代码

       

        // Inflate the window 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;

            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;

            // System.out.println("Simple!");

        }

        mDecor.startChanging();

//layoutResoure资源文件的view加载,并且添加到mDecor

        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

//拿到资源idID_ANDROID_CONTENTViewGroup

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

       ..//省略部分代码

        return contentParent;

    }

看主要的代码,这个根据features的值加载相应的布局文件,由于我们并没有设置features,于是默认选择了R.layout.screen_simple布局载入.当然我们可以通过ActivitygetWindow().requestFeature(features)方法去设置,前提是这个方法要在setContent方法前执行。

Window.java requestFeature方法的注释已经说明了。

/**

     * Enable extended screen features.  This must be called before

     * setContentView().  May be called as many times as desired as long as it

     * is before setContentView().  If not called, no extended features

     * will be available.  You can not turn off a feature once it is requested.

     * You canot use other title features with {@link #FEATURE_CUSTOM_TITLE}.

     *

     * @param featureId The desired features, defined as constants by Window.

     * @return The features that are now set.

     */

    public boolean requestFeature(int featureId) {

        final int flag = 1<<featureId;

        mFeatures |= flag;

        mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;

        return (mFeatures&flag) != 0;

    }

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);方法就是把layoutResource资源文件加载进来,并且添加到mDecor。里面也是我们平常用的LayoutInflater.inflate方法。现在资源文件加载进来后,然后去查找了一个idID_ANDROID_CONTENTViewGroup.那这个ViewGroup是个啥布局呢?

ID_ANDROID_CONTENT我们同样在Window里面查找

Window.java

/**

     * The ID that the main layout in the XML layout file should have.

     */

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

在系统源码frameworks\base\core\res\res\layout的资源文件R.layout.simple.xml里面,R.id.content就是一个FrameLayout布局,contentParent就是它

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

<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>

可以看到installDecor()方法主要是生成一个DecorView 类并复赋值mDecor,加载系统的布局文件并且添加到mDecor,将一个R.id.content的容器控件赋值给mContentParent,继续回到PhoneWindow.setContent方法

@Override

    public void setContentView(int layoutResID) {

        if (mContentParent == null) {

            installDecor();

        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

            mContentParent.removeAllViews();

        }

//没有设置featureFEATURE_CONTENT_TRANSITIONS故走else

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

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

                    getContext());

            transitionTo(newScene);

        } else {

//我们自己acitivity的资源文件加载到mContentParent上面

            mLayoutInflater.inflate(layoutResID, mContentParent);

        }

        mContentParent.requestApplyInsets();

        final Callback cb = getCallback();

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

            cb.onContentChanged();

        }

        mContentParentExplicitlySet = true;

    }

由上面的代码得知,我们自己的布局是加载到一个叫R.id.content容器控件上面。

通过上面的源码分析得知:一个Acitivity里面包含了一个PhoneWindow对象,一个PhonWindow对象里面有个DecorView容器控件,这个控件是根控件。这个根控件里面包含一个子容器控件,子容器控件的idR.Id.Content,该容器控件包含了我们自己写的布局文件。

从网上扣了一张图可以明确控件的关系如下

 

查看Dialog 的源码

 Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {

        if (createContextThemeWrapper) {

            if (themeResId == 0) {

                final TypedValue outValue = new TypedValue();

                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);

                themeResId = outValue.resourceId;

            }

            mContext = new ContextThemeWrapper(context, themeResId);

        } else {

            mContext = context;

        }

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        final Window w = new PhoneWindow(mContext);

        mWindow = w;

        w.setCallback(this);

        w.setOnWindowDismissedCallback(this);

        w.setWindowManager(mWindowManager, null, null);

        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);

    }

Dialog里面也是这样的层次关系。Dialog包含了一个PhoneWindow

疑问1

activity.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, String referrer, IVoiceInteractor voiceInteractor,

            Window window, ActivityConfigCallback activityConfigCallback) {

       ....省略部分代码

        mWindow = new PhoneWindow(this, window, activityConfigCallback);

        

       ....省略部分代码

//疑问1

 mWindow.setWindowManager(

                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),

                mToken, mComponent.flattenToString(),

                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

为什么这行代码会有疑问呢?这行代码看起来就是设置windowwindowManager。而我们的第一直觉是应该就是获取系统的windowManager然后设置进去,是不是这样我们继续查看Window的里面的这个方法

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,

            boolean hardwareAccelerated) {

        mAppToken = appToken;

        mAppName = appName;

        mHardwareAccelerated = hardwareAccelerated

                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);

        if (wm == null) {

            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);

        }

        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);

    }

咦?不是直觉mWindowManager=wm,而是又调用了WindowManagerImplcreateLocalWindowManager方法,继续去WindowManagerImpl类里面查看createLocalWindowManager方法。

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {

        return new WindowManagerImpl(mContext, parentWindow);

}

直接创建了一个新的WindowManagerImpl对象.

虽然WindowManagerImpl里面的主要方法是调用WindowManagerGlobal的,而WindowManagerGlobal类是个单例。要是WindowManagerImpl直接是单例不是更好?

疑问2 上面我们分析了setContent是把我们的布局添加到R.id.content布局上。并且DecorView是个根布局。那么DecorView又是如何最终显示在我们手机界面上的呢?

这两个疑问以后深入其他源码方法时或许会有答案,暂且放下。

猜你喜欢

转载自blog.csdn.net/mr_lu_/article/details/79949089