Android视图加载流程(1)之SetContent( )

关键类:window,PhoneWindow,DecorView
关键方法:setContentView(int layoutResID)及其重载方法

简单介绍:

  1. Window是一个抽象类,提供了绘制窗口的一组通用API
  2. PhoneWindow是Window的具体继承实现类。
  3. DecorView原为PhoneWindow的内部类,后独立出来。DecorView是所有窗口的根View (FrameLayout的子类)

关系图:


源码解读:

Step1:Activity类

setContentView()是非常关键的方法,其重载方法有3个

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

public void setContentView(View view) {
    getWindow().setContentView(view);
    initWindowDecorActionBar();
}

public void setContentView(View view, ViewGroup.LayoutParams params) {
    getWindow().setContentView(view, params);
    initWindowDecorActionBar();
}

可以清楚的看到3个重载方法都调用了getWindow()中相应的setContentView方法

Step2:PhoneWindow类

由于Window类为抽象类,所以我们要看实现类PhoneWindow里的setContent(int layoutResID)

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);
    }
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
} 
  1. 判断mCntentParent是否null,
    1. 为空:调用installDecor(),初始化DecorView
    2. 不为空:判断是否设置FEATURE_CONTENT_TRANSITIONS(默认false)
      • 若没设置:清除mContentParent的子视图
  2. mLayoutInflater.inflate(layoutResID, mContentParent)将资源文件转换为View并加载至mContentParent

Step3:PhoneWindow类

  • setContentView(int layoutResID)
  • setContentView(View view)
  • setContentView(View view, ViewGroup.LayoutParams params)`

后两个方法与第一个方法大同小异

@Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

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

setContentView(View view)实际上是调用了setContentView(View view, ViewGroup.LayoutParams params)只是LayoutParams设置为了MATCH_PARENT而已。

setContentView(View view, ViewGroup.LayoutParams params)方法调用addView()加载视图至mContentParent

Step4:PhoneWindow类

到这里,大家都知道我们所加载的布局最终都是add到mContentParent,那mContentParent从哪里来呢?我们回头看一下installDecor()方法

ViewGroup mContentParent;//内容视图
private DecorView mDecor;//所有视图的根视图
    
//实例化DecorView
protected DecorView generateDecor() {
    return new DecorView(getContext(), -1);
}
    
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) {
        //根据窗口的风格修饰,选择对应的修饰布局文件,并且将id为content的FrameLayout赋值给mContentParent
        mContentParent = generateLayout(mDecor);
        //......
        //初始化一堆属性值
    }
} 
  1. 判断mDecor是否为null,为空调用generateDecor()创建一个DecorView
  2. 判断mContentParent是否为null,为空调用generateLayout(mDecor)创建一个mContentParent对象

此时我们看到了前几步所需的mContentParent

Step5:PhoneWindow类

接着我们看mContentParent是具体如何创建的

protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    
    TypedArray a = getWindowStyle();
    
    //......
    //依据主题style设置一堆值进行设置
    
    // Inflate the window decor.
    
    int layoutResource;
        
    //要在setContentView之前调用requesetFeature的原因
    int features = getLocalFeatures();//获取本地特性
    //......
    //根据设定好的features值选择不同的窗口修饰布局文件,得到layoutResource值
    
    //把选中的窗口修饰布局文件添加到DecorView对象里,并且指定contentParent值
    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");
    }
    
    //......
    //继续一堆属性设置,完事返回contentParent
    return contentParent;
}   
  1. 根据app所设置的不用类型主题获取相对应的系统根布局文件(这些布局都有一个以id为content的帧布局:内容布局)
  2. 解析这些布局文件并加载至mDecor视图
  3. 接着通过mDecor获取id为content的帧布局返回给contentParent对象(前几步的关注对象)

主题的话以后有空的话专门写一篇文章介绍,这里大概认识一下~

当我们使用(Window.FEATURE_NO_TITLE)主题,则它对应的系统布局为R.layout.screen_simple

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

有没有看到R.id.content的FrameLayout么?这就是上面讲的系统布局内的内容视图

Step6:结束 Finish

此时我们的布局文件文件已经转换为视图并加载到DecorView当中,但是此时布局并没有显示!那视图是如何显示出来呢?接下去的文章会分析这一块。

总结 Summary

通过以上6步我们大致可以理解setContentView的整个过程:

  1. 创建DecorView的对象,并将此对象作为窗口的根视图
  2. 根据主题及其样式的不同获取对应的布局文件,解析并加载至mDecorView
  3. 通过mDecorView获取id为content的帧布局contentParent,
  4. 将Activity的布局文件解析并加载至contentParent
理解图:

额外 Extra

Step1 PhoneWindow类

这里我们聊一下与setContent有关的回调,我们回看一下刚才的setContentView

public void setContentView(int layoutResID) {
    ......
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

当我们setContentView执行完加载视图后,接着获取回调CallBack,并且执行改回调的方法onContentChanged()。此回调从哪里来呢?

Step2 Window类

private Callback mCallback;
    
public final Callback getCallback() {
    return mCallback;
}
    
 public void setCallback(Callback callback) {
    mCallback = callback;
}
    
public interface Callback{
    ......
     public void onContentChanged();
    ......
}

从这里我们可以清楚的看到Window类中包含了Callback和所对应的get( )和set( ),
getCallback()我们已经知道在哪里调用,那setCallBack呢?

Step3 Activity类

//此类为简化版
public class Activity implements Window.Callback {
    
    final void attach(Context context, ActivityThread aThread){
    ......
    mWindow.setCallback(this);
    ......
     }
     //接口所调用的方法
     public void onContentChanged() {}
}

我们可以看出Activity实现了Callback的接口,且设置setCallback为this。所以setContent()执行完毕后就会调用此方法。setContent()竟然是空方法

疑问 Wonder

此文只讲到DecorView由PhoneWindow生成并加载的。我们知道phoneWindow是从Activity的getWindow()获取的。那PhoneWindow跟Activity是如何产生关联的呢?

Android视图加载流程(2)之Window和WindowManager的创建与Activity


PS:本文整理自以下文章,若有发现问题请致邮caoyanglee92@gmail.com
工匠若水 Android应用setContentView与LayoutInflater加载解析机制源码分析
Hohohong Android窗口机制

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

推荐阅读更多精彩内容