Android setContentView 加载布局源码解析

Posted yian_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android setContentView 加载布局源码解析相关的知识,希望对你有一定的参考价值。

转载请注明出处:http://blog.csdn.net/yianemail/article/details/51753815

1,背景

作为android 四大组件之一的Activity 在应用开发中在常见不过。
而回调Activity 生命周期的onCreat()以及加载布局的setContentView()我们更是耳闻熟详。
但是我们却很少真正去关注Activity的布局到底是怎样被加载,又如何去显示的。

2,源码分析

2-1,典型使用

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
   

这段代码我们再熟悉不过。我们来看下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) 
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    

其中getWindow() 返回局部变量mWindow;

  /**
     * Retrieve the current @link android.view.Window for the activity.
     * This can be used to directly access parts of the Window API that
     * are not available through Activity/Screen.
     *
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() 
        return mWindow;
    

我们查找可知。mWindow 是在Activity的attach()方法中被实例化的。其实例化对象为PhoneWindow,PhoneWindow为抽象Window类的实现类。关于attach()方法,我们稍后会在activity的启动中讲到。在这我们只需要知道mWindow再此实例化就好。

2-2,这里先简要说下有关类的相关职责:

public abstract class Window 
public class PhoneWindow extends Window ...
private final class DecorView extends FrameLayout...

1,Window是abstract修饰。提供有关Window的通用方法

2,PhoneWindow是Window的实现,提供具体的操作逻辑

3,DecorView是PhoneWindow 中的内部类,继承了FrameLayout,是所有应用窗口的根View 。

看下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) 
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(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;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
 mWindow = new PhoneWindow(this);

我们可以发现,得知mWindow其实是PhoneWindow对象。
也就是说,我们的 getWindow().setContentView(layoutResID);其实就是调用PhoneWindow 的setContentView(layoutResID);方法。
有关PhoneWindow 这是个隐藏class. 需要在android sdk source中查看。路径为
sdk/source/android-x/com/android/internal/policy/impl/PhoneWindow.java。

2-3,PhoneWindow.setContentView(layoutResID)实现

看下PhoneWindow 的setContentView(layoutResID)的实现


    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) 
        // 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)) 
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
         else 
            mContentParent.addView(view, params);
        
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) 
            cb.onContentChanged();
        
    

继续看下hasFeature(FEATURE_CONTENT_TRANSITIONS)

   public boolean hasFeature(int feature) 
        return (getFeatures() & (1 << feature)) != 0;
    

getFeatures()

 protected final int getFeatures()
    
        return mFeatures;
    

而mFeatures默认是false,
首次执行mContentParent为null,执行installDecor()方法。否则移除mContentParent所有内部view,然后通过LayoutInflater.inflate将我们传入的layout放置到mContentParent中。

分析下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);

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

                        ....

这个方法有点长,同样初次执行,mDecor为null,执行 mDecor = generateDecor(); 所以基本确定generateDecor() 就是实例化mDecor对象的。

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

可以看见generateDecor方法仅仅是new一个DecorView的实例。
就往下分析。同样初次执行mContentParent(mContentParent是一个ViewGroup)为null,执行generateLayout(mDecor);

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

        TypedArray a = getWindowStyle();

        if (false) 
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < R.styleable.Window.length; i++) 
                s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                        + a.getString(i);
            
            System.out.println(s);
        
    //style 样式...
        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) 
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
         else 
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        

        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) 
            requestFeature(FEATURE_NO_TITLE);
         else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) 
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
            .
            .
            .
              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;
            .
            .
            .

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

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) 
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) 
                progress.setIndeterminate(true);
            
        

        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) 
            registerSwipeCallbacks();
        
        ..
        .
        .

        mDecor.finishChanging();
        return contentParent;

额。好吧,又是个巨长的方法,我们挑重要的分析。
可以发现,在
mDecor.startChanging(); 之前基本都是要一系列的样式
虽然不明白到底在干什么。但是我们仔细分析一下,以及结合之前分析的DecorView 的作用装饰布局的作用,结合下代码

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

我们在看下layoutResource的取值范围

 int layoutResource;
 ..
  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;
            
   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 

基本可以得出结论:
上面方法主要作用就是根据窗口的风格修饰类型为该窗口选择不同的窗口根布局文件。

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

mDecor做为根视图将该装饰窗口根布局添加进去。
继续分析

 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) 
            throw new RuntimeException("Window couldn't find content container view");
        

通过mDecor.findViewById传入R.id.content(相信这个id大家或多或少都听说过),返回mDecor(布局)中的id为content的View,一般为FrameLayout。

(在这你应该可以猜到。我们的setContentView 的布局是加入这个id 为content 的FrameLayout 中的)

然后返回这个contentParent

  mDecor.finishChanging();
  return contentParent;

2-4,显示xml文件显示到屏幕

在以上的分析中,我们大都分析了一下Window,PhoneWidow以及DecroView 的工作关系 以及各自的作用,DecroView 装饰窗口风格的工作过程。
那么我们的xml 文件是怎样被添加到窗口布局上去的呢?
重点来了,之前的分析的代码最终是返回了mDecor(布局)中的id为content的View,一般为FrameLayout。
我们在回顾一下,也就是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);
        

没错,就是 mLayoutInflater.inflate(layoutResID, mContentParent)。把我们的xml布局 inflate 到mContentParent 中。

3,总结

首先初始化mDecor,然后根据设置的属性去得到不同的mDecor 装饰窗口风格。通过infalter.inflater放入到我们的mDecor中
在这些布局中,一般会包含ActionBar,Title,和一个id为content的FrameLayout。
最后,我们在Activity中设置的布局,会通过infalter.inflater压入到我们的id为content的FrameLayout中去。

以上是关于Android setContentView 加载布局源码解析的主要内容,如果未能解决你的问题,请参考以下文章

Android 通知 setContentView

Android 9 (API 28) 活动在启动时崩溃 (setContentView)

findViewById 返回 null - Android - setContentView

Android切换页面--setContentView

android setContentView

android setContentView()源码解析