Activity —> Window —> DecorView
Activity设置显示内容的过程
将布局放入窗口界面(SDK23)
从MyActivity中使用的setContentView(…)方法开始,这个方法是父类AppCompatActivity的。
public class MyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.a_view_progress);
}
}
AppCompatActivity中setContentView(…)方法的实现
public class AppCompatActivity extends FragmentActivity implements ... {
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
....
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
}
进一步是由抽象类AppCompatDelegate对象调用setContentView(…)方法,现在要去找到这个方法是在那里实现的。看getDelegate方法的实现,进入看AppCompatDelegate.create(…)方法。
public abstract class AppCompatDelegate {
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return create(activity, activity.getWindow(), callback);
}
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (sdk >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV7(context, window, callback);
}
}
}
所以找到AppCompatDelegateImplV7类中实现的setContentView(…)方法。
class AppCompatDelegateImplV7 extends AppCompatDelegateImplBase
implements MenuBuilder.Callback, LayoutInflaterFactory {
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
//以后可以在onContentChanged()方法里面去findViewById了。
mOriginalWindowCallback.onContentChanged();
}
}
这个方法实现说明,我们调用Activity中的setContentView(…)中传入的View会被添加到ViewGroup对象contentParent 中,不难看出contentParent 是同为ViewGroup对象的mSubDecor的子控件。所以我们传入的View最后是添加到了mSubDecor中。
class AppCompatDelegateImplV7 extends AppCompatDelegateImplBase
implements MenuBuilder.Callback, LayoutInflaterFactory {
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
//初始化
mSubDecor = createSubDecor();
// 设置标题的
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
onTitleChanged(title);
}
//窗口大小设置
applyFixedSizeWindow();
//AppCompatDelegateImplV7 派生类可覆写的方法
onSubDecorInstalled(mSubDecor);
//是否初始化的标志位
mSubDecorInstalled = true;
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!isDestroyed() && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}
}
看看此对象的初始化过程ensureSubDecor()方法。
private ViewGroup createSubDecor() {
...
ViewGroup subDecor = null;
...
mWindow.setContentView(subDecor);
...
return subDecor;
}
主要是对界面、标题和内容的设置,最后是由抽象类Window对象调用setContentView(…)进行内容设置,具体实现稍后再看,我们先看applyFixedSizeWindow()对窗口大小的设置。
private void applyFixedSizeWindow() {
ContentFrameLayout cfl = (ContentFrameLayout) mSubDecor.findViewById(android.R.id.content);
final View windowDecor = mWindow.getDecorView();
cfl.setDecorPadding(windowDecor.getPaddingLeft(),windowDecor.getPaddingTop(),
windowDecor.getPaddingRight(),windowDecor.getPaddingBottom());
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor());
a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor());
if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) {
a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor,
cfl.getFixedWidthMajor());
}
if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) {
a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor,
cfl.getFixedWidthMinor());
}
if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) {
a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor,
cfl.getFixedHeightMajor());
}
if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) {
a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor,
cfl.getFixedHeightMinor());
}
a.recycle();
cfl.requestLayout();
} attach
对于mWindow对象,进行了 setContentView(…) 和 getDecorView() 的操作。现在我们要查找这两个方法的具体实现,mWindow对象是在AppCompatDelegateImplV7构造方法里面复制的。联系上文AppCompatDelegate.create(…)方法,mWindow是由activity.getWindow()或dialog.getWindow()方法获取,这里我们看Activity获取Window方式的。
public class Activity extends ContextThemeWrapper implements ... {
final void attach( ... ) {
...
mWindow = new PhoneWindow(this);
...
}
}
所以这两方法的具体实现还得再PhoneWindow里面去查看。
@Override
public void setContentView(int layoutResID) {
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);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
private void installDecor() {
...
if (mDecor == null) {
mDecor = generateDecor();
}
...
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
}
在执行setContentView(…),首先对mContentParent 为空时进行初始化操作,不为空时清除所有的View,然后通过LayoutInflater.inflate将我们传入的layout放置到mContentParent中。当mContentParent为空进行初始化时,会先获会调用generateDecor()获取mDecor,在将获取的mDecor传入generateLayout()方法获取mContentParent,这两个方法在后面看实现。
不难看出,Activity的setContentView(…)设置可见视图时,此视图先被DecorView中 id = android.R.id.content 的ViewGroup收容(在AppCompatDelegateImplV7类setContentView(…)方法中可以看出)。DecorView又被PhoneWindow对象收容(在AppCompatDelegateImplV7类createSubDecor()方法中可以看出)。以看出Activity中关于界 面的绘制实际上全是交给Window对象来做的。绘制类图的话,可以看出Activity聚合了一个Window对象。
从窗口界面拿控件使用(SDK23)
在Activity中使用findViewById最终是调用Window里面的方法。
public abstract class Window {
@Nullable
public View findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
}
前面就知道了getDecorView()返回的是对象是mDecor,这个对象初始化的时间是不是将布局设置进入窗口,初始化mContentParent 的时候。我们看看generateDecor()和generateLayout()方法的实现。
既然使用mDecor.findViewById(id)来获取我们在布局中写入的控件,那是不是在generateDecor()获取mDecor的时候将我们的布局放入这个对象中。
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
public DecorView(Context context, int featureId) {
super(context);
mFeatureId = featureId;
}
可以看出generateDecor( )方法只是初始化了一个FrameLayout对象,并没有在其内部压入布局文件,既然mDecor拥有我们的布局控件,在初始化的时候没有加入,那肯定在其他的时候加入了。看下generateLayout(mDecor)方法实现,这方法名都带了Layout,估摸着就是他了。
protected ViewGroup generateLayout(DecorView decor){
//先获取WindowStyle中的各种属性,对Window进行requestFeature或者setFlags等设置
TypedArray a=getWindowStyle();
if(a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle,false)){
requestFeature(FEATURE_NO_TITLE);
}
//...根据当前sdk的版本判断需不需要加入menukey
WindowManager.LayoutParams params=getAttributes();
//设置 params.softInputMode 软键盘的模式;
//当前是浮动Activity,在params中设置FLAG_DIM_BEHIND并记录dimAmount的值。
//在params.windowAnimations 中记录 WindowAnimationStyle
/**
* 判断features和mIsFloating,为layoutResource进行赋值操作(R.layout.screen_custom_title、R.layout.screen_action_bar等等)
* features,除了theme中设置,我们也可以在Activity的onCreate的setContentView之前进行requestFeature,设置
* 这说明了,为什么需要在setContentView前调用requestFeature设置全屏什么的。得到了layoutResource以后
*/
int layoutResource;
int features=getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if((features&((1<<FEATURE_LEFT_ICON)|(1<<FEATURE_RIGHT_ICON)))!=0){
if(mIsFloating){
TypedValue res=new TypedValue();
getContext().getTheme().resolveAttribute(
com.android.internal.R.attr.dialogTitleIconsDecorLayout,res,true);
layoutResource=res.resourceId;
}else{
layoutResource=com.android.internal.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=com.android.internal.R.layout.screen_progress;
// System.out.println("Progress!");
}else if((features&(1<<FEATURE_CUSTOM_TITLE))!=0){
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if(mIsFloating){
TypedValue res=new TypedValue();
getContext().getTheme().resolveAttribute(
com.android.internal.R.attr.dialogCustomTitleDecorLayout,res,true);
layoutResource=res.resourceId;
}else{
layoutResource=com.android.internal.R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
}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(
com.android.internal.R.attr.dialogTitleDecorLayout,res,true);
layoutResource=res.resourceId;
}else if((features&(1<<FEATURE_ACTION_BAR))!=0){
layoutResource=com.android.internal.R.layout.screen_action_bar;
}else{
layoutResource=com.android.internal.R.layout.screen_title;
}
// System.out.println("Title!");
}else if((features&(1<<FEATURE_ACTION_MODE_OVERLAY))!=0){
layoutResource=com.android.internal.R.layout.screen_simple_overlay_action_mode;
}else{
// Embedded, so no decoration is needed.
layoutResource=com.android.internal.R.layout.screen_simple;
// System.out.println("Simple!");
}
/**
* 使用LayoutInflater把布局加载为View,并加入到decor中。
*/
View in=mLayoutInflater.inflate(layoutResource,null);
decor.addView(in,new ViewGroup.LayoutParams(MATCH_PARENT,MATCH_PARENT));
/**
* 将mDecor.findViewById放入R.id.content中
* 返回mDecor(布局)中的id为content的View,一般为FrameLayout。
*/
ViewGroup contentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT);
//...
return contentParent;
}
}
有了mContentParent,然后把我们写的布局文件通过inflater加入到mContentParent中。
总结分析
View显示的流程:
Activity.setContentView()
–>AppCompatDelegateImplV7.setContentView()
–> 加入SubDecorView
(android.R.id.content)中SubDecorView
初始化:AppCompatDelegateImplV7.createSubDecor()
–> 建立SubDecorView
并mWindow.setContentView(subDecor)
mWindow
对象:Activity.attach()
中赋值PhoneWindow
mWindow.setContentView(subDecor)
相当于PhoneWindow.setContentView(subDecor)
–> 初始化mContentParent
并将subDecor
视图inflate
到mContentParent
中
Activity是Android程序的载体,没有Activity程序就没有可视界面,也就没有View了。这并不是说Activity就是一个View,它的功能更像是一个控制器,在Activity中我们对View进行控制操作。Activity中的setContentView()方法实际上是调用Window对象的setContentView()方法,所以绘图的操作是Window对象执行的。