Acticity之setContentView()详解

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

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

attach()、Window、PhoneWindow、DecorView、mConentRoot、mContentParent

  1. Widnow (虚类)
    Window的意思是窗口,用来承载布局。

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

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

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

  5. 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源码你会发现很多内涵知识。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容