Acticity之setContentView()详解

一、前言
Activity是承载UI显示的重要组件,而setContentView()又占据着重要的位置,我们平时所看到的界面都是与它有关系。如果没有设置setContentView()的话Activity就像是没有女朋友的屌丝,比如Service就是默默的服务。

二、关键函数、类与变量:

attach()、Window、PhoneWindow、DecorView、mConentRoot、mContentParent
1. Widnow (虚类)
Window的意思是窗口,用来承载布局。

  1. PhoneWindow
    PhoneWindow是Window的实现类,在Android手机上的使用的便是PhoneWindow(见名知意)。

  2. DecorView
    DecorView继承自FrameLayout,并且是PhoneWindow的内部类,且被final 和private所修饰。 DecorView包含一个子布局mContentRoot。

  3. mContentRoot
    mContentRoot为LinearLaoyout线性布局,垂直方向。mContentRoot与DecorView关联。mContentRoot包含标题栏和mConntentParent两个部分,如果标题栏被取消的话那么mContentRoot只包含mContentParent。

  4. mContentParent
    mContentParent是resLayout布局的根部局,类型为FrameLayout。我们setContentView()所使用的布局最终被mContentParent所俘获。 成为mContentParent的子布局。

三、setContentView()的内幕

看了这么多的名词其实我们还是一头雾水。卧槽,他们好陌生呀。PhoneWindow是位于FrameWork层,所以在我们AS的项目中并不能看到。安利一下网址:Github之Android Framework层源码

简单来说,在调用setContentView()之前会先调用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) {
    attachBaseContext(context);

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

    mWindow = new PhoneWindow(this);//mWindow被实例化PhoneWindow
    mWindow.setCallback(this);//Activity中的事件响应会分发到Window中
    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();//UI线程原来在这里启动啊。。。

    mMainThread = aThread;
    mInstrumentation = instr;
    mToken = token;
    mIdent = ident;
    mApplication = application;
    mIntent = intent;
    mReferrer = referrer;
    mComponent = intent.getComponent();
    mActivityInfo = info;
    mTitle = title;
    mParent = parent;
    mEmbeddedID = id;
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    if (voiceInteractor != null) {
        if (lastNonConfigurationInstances != null) {
            mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
        } else {
            mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                    Looper.myLooper());
        }
    }

    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    }
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config;
}  

在attach()这个函数里边我们看到了Window是如何实例化并且与Activity关联起来的。

接下来,我们看看setContentView()具体发生了什么,可能你也看过它的源代码,但是是这样的。

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

然后是这样的:

    public abstract void setContentView(@LayoutRes int layoutResID);

当然是这样子,因为这些都是基类,我们要去实现类里边去看。PhoneWindow里都做了些什么呢?我们要好好了解一下。
首先还是看代码:

 @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) {//mContentParent是layoutResID的父布局。
            installDecor();  //重点在这里,初始化DecorView
        } 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();
        }
    }

我们看看installDecor()函数具体发生了什么。

 private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor(); //实例化mDecor
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);//实例化mContentParent
            ...    
        }
       ......//忽略不重要的代码
    }


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

    //generateLayout(mDecor)

        protected ViewGroup generateLayout(DecorView decor) {
      ....
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;//终于发现了mContentParent的父布局mContentRoot

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//原来mContentParent对应的资源是ID_ANDROID_CONTENT。
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

      .....

       return contentParent;
}

由上面的分析,我们看到了Activity是怎么和Window关联上的,我们的布局文件又是如何与Window关联上的。

四、拓展

  1. 问题一、为什么我们不能在setConentView()后修改window 的Feature
 public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

    //我们发现了initWindowDecorActionBar();它是干嘛的呢?

     /**
     * Creates a new ActionBar, locates the inflated ActionBarView,
     * initializes the ActionBar with the view, and sets mActionBar.
     */
    private void initWindowDecorActionBar() {//初始化ActionBar
        Window window = getWindow();

        // Initializing the window decor can change window feature flags.初始化window decor会改变widnow feature的一些标志
        // Make sure that we have the correct set before performing the test below.
        window.getDecorView();

        ....
    }
    //我们看看getDecorView();的注释
     /**
     * Retrieve the top-level window decor view (containing the standard
     * window frame/decorations and the client's content inside of that), which
     * can be added as a window to the window manager.
     *
     * <p><em>Note that calling this function for the first time "locks in" 
     * various window characteristics as described in
     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p>
     *//大概的意思是会锁定各种window 特点,比如我们的Feature。
     * @return Returns the top-level window decor view.
     */
    public abstract View getDecorView();


五、总结

通过对setContentView()内幕的挖掘才能更加深度理解Activity是如何呈现布局的。

我们看到的一个小函数,背后还有很多故事。仔细挖掘Android源码你会发现很多内涵知识。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值