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

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

简单介绍:

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

关系图:

Android视图加载流程(1)之SetContent( )_第1张图片

源码解读:

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


    
    

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

Step6:结束 Finish

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

总结 Summary

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

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

额外 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:本文整理自以下文章,若有发现问题请致邮[email protected]
工匠若水 Android应用setContentView与LayoutInflater加载解析机制源码分析
Hohohong Android窗口机制

你可能感兴趣的:(Android视图加载流程(1)之SetContent( ))