Window做为一个窗口的概念,在Android的日常开发中很常见,但是一般不直接操作Window,而是操作View。Window其实是高于View的一个层次了,View都是由Window接管的,具体实现是WindowManagerService,外界通过WindowManager访问Window。Activity(视图层),Dialog,Toast本质上都是通过Window来展示的,它们都附加在Window之上,Window是View的管理者。就连点击事件也是从Window传给DecorView,最后再由DecorView传给我们的View。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
//android.app.Activity#attach()
final void attach(Context context, ActivityThread aThread, ... Window window ...) {
...
//这里进行mWindow的初始化,可以看到Activity中的Window实现类是PhoneWindow,
//目前为止,PhoneWindow也是Window的唯一实现类
mWindow = new PhoneWindow(this, window, activityConfigCallback);
//这里是给Window设置了WindowManager,WindowManager是通过IPC获取的系统服务,
//WindowManager只是一个接口类型,具体实现是WindowManagerImpl类,
//当然WindowManagerImpl又将实际的逻辑实现交给了WindowManagerGlobal类
mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
//Activity持有的WindowManager也是从Window中拿过来的
mWindowManager = mWindow.getWindowManager();
....
}
@Override
public void setContentView(int layoutResID) {
//mContentParent其实就是android.R.id.content布局对应的实际展示的内容
if (mContentParent == null) {
//这个方法时构造一个顶层的DecorView对象,其实是直接通过new DecorView产生的实例对象,并赋值给mDecor变量
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//第二次调用setContentView()方法时走这里,会先remove掉所有的子View再通过inflate进行加载布局
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();
}
//这个变量,会在requestFeature()方法调用时判断时机是否正确,
//如果实在setContentView之后调用的,会抛出"requestFeature() must be called before adding content"的异常
mContentParentExplicitlySet = true;
}
private void installDecor() {
...
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
//这里又将PhoneWindow引用和DecorView进行了绑定,现在PhoneWindow和DecorView可以互相访问了
mDecor.setWindow(this);
}
if (mContentParent == null) {
//DecorView已经构造好了,可以从DecorView中通过findViewById的方式实例化mContentParent对象了
mContentParent = generateLayout(mDecor);
}
...
}
protected ViewGroup generateLayout(DecorView decor) {
...
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = R.layout.screen_simple;
}
....
//mDecorView虽然已经初始化了,但是他的布局还未加载,通过上面对features变量值的一堆if-else判断,
//获取到对应的feature值的布局文件,再通过inflater对象加载布局
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//这里获取的就是mContentParent对象,findViewById是View的方法,这里是间接调用了DecorView的findViewById方法,
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
//这里inflate方法的root直接传null,的确这已经是跟布局了,肯定是没有父View可以传了
final View root = inflater.inflate(layoutResource, null);
...
}
由以上2.1~2.6对源码的分析,可以得到以下结论:
Activity展示的其实是PhoneWindow上的内容。那么其实 setContentView 实际上是调用的 PhonwWindow的setContentView。可以看出Window是做为中介者连接了Activity和View。
protected ViewGroup generateLayout(DecorView decor) {
...
int layoutResource;
int features = getLocalFeatures(); //获取的是mLocalFeatures值
//下面是一堆用feastures值来判断改用哪个布局文件的逻辑
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute( R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
removeFeature(FEATURE_ACTION_BAR);
}
...
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
}
public boolean requestFeature(int featureId) {
if (mContentParentExplicitlySet) {
throw new AndroidRuntimeException("requestFeature() must be called before adding content");
}
final int features = getFeatures();
final int newFeatures = features | (1 << featureId);
if ((newFeatures & (1 << FEATURE_CUSTOM_TITLE)) != 0 &&
(newFeatures & ~CUSTOM_TITLE_COMPATIBLE_FEATURES) != 0) {
throw new AndroidRuntimeException(
"You cannot combine custom titles with other title features");
}
...
//Window的requestFeature()会将featureId通过按位或运算记录到mFeatures变量中
return super.requestFeature(featureId);
}
这样也能解释的通了,为什么在Activity中使用requestFeature()要在setContentView()前调用了,以为这个值是给Activity的DecorView选择加载那种样式的布局文件用的。如果在setContentView之后调用,这个时候PhoneWindow::generateLayout(DecorView)已经执行完成了,并不会生效。