美文网首页
setContentView源码解读2

setContentView源码解读2

作者: 陆元伟 | 来源:发表于2019-07-16 17:14 被阅读0次

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

首先查看ActivitysetContentView方法

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

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

public Window getWindow() {
    return mWindow;
}

attach方法里面我们看到有赋值,其实mWindow实际是一个PhoneWindow,那么attach方法什么时候执行的呢?肯定是在onCreate执行前,系统会调用这个方法。后面我们分析startActivity的时候会发现,Activity被系统实例化后会调用此方法。

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

@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;
    //同样第一次调用,mDecor也为null.走if里面
    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 = 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是一个继承了FrameLayoutViewGroup。而且该类是被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 已经生成,类型是个继承FrameLayoutDecorView类。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));
    //根据不一样的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);
    //拿到资源id为ID_ANDROID_CONTENT的ViewGroup
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

   ..//省略部分代码

    return contentParent;

}

看主要的代码,这个根据features的值加载相应的布局文件,由于我们并没有设置features,于是默认选择了R.layout.screen_simple布局载入.当然我们可以通过Activity的getWindow().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<
    mFeatures |= flag;
    mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
    return (mFeatures&flag) != 0;

}

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);方法就是把layoutResource资源文件加载进来,并且添加到mDecor。里面也是我们平常用的LayoutInflater.inflate方法。现在资源文件加载进来后,然后去查找了一个id为ID_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就是它。最外层是个LinearLayout ,@android:id/content是个FramenLayout@+id/action_mode_bar就是我们的默认头部内容

<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" />
    <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();
    }
    //没有设置feature为FEATURE_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容器控件,这个控件是根控件。这个根控件里面包含一个id为R.Id.Content的子容器,我们自己写的布局文件添加到该容器当中

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

setContentView.png

查看DialogsetContentView方法,也是设置到mWindow上面,mWindow又是何时赋值的?

public void setContentView(int layoutResID) {
    mWindow.setContentView(layoutResID);
}

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);
    //同样生成一个PhoneWindow
    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.我们写的布局也是添加到R.id.content父控件中去。

疑问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又是如何最终显示在我们手机界面上的呢?

疑问3 ActivityDialog的添加到R.id.content布局上,并且根目录都是DecorView。那是否其他的视图比如Toast也是如此?后面查看Toast显示源码再做分析

相关文章

网友评论

      本文标题:setContentView源码解读2

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