概述
这篇文章纠结了很久,在想需要怎么写?因为window有关的篇幅,如果需要讲起来那可太多了。从层级,或是从关联,总之不是很好开口。这次也下定决心,决定从浅入深的讲讲window这个东西。
Window
Window是什么,直译是窗口,我们了解过Android应用显示层级就知道,一个应用从下到上分别是:Activity-Window-DecorView-ViewGroup-View,大致是这样的一个层级包裹。可说了这么多,还是没有说清楚window是什么?有一句话是这样说的:Window是视图的容器,视图是Window的内容。
又打个比方,把视图View比作水,Window比作装水的瓶子,如果没有瓶子,水就不知道放在哪里,根据瓶子的形状,大小,倒入的水就会在瓶子限制的形状,大小内呈现什么样子。这样一讲,是不是就更清楚了点呢。
Window创建
先说一下Activity,在attach方法中,会创建一个window,它是抽象的,创建的是它的实现类PhoneWindow。为什么从Activity说起呢,因为Activity是我们最常见的组件。除了Activity,Dialog和Toast也有他们的window,实现类通常也是PhoneWindow。它们的window创建,可以自行分析一下。
// Activity.java
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
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,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
IBinder shareableActivityToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mActivityInfo = info;
// 创建window
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
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();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mAssistToken = assistToken;
mShareableActivityToken = shareableActivityToken;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
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());
}
}
//设置window的管理者
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
}
Window管理
WindowManager,Window的管理者,上层窗口的管理,都是依赖它,而下层,我们在之后的文章会陆续揭开。从上述贴的aosp中Activity#attach源码可以看到,不仅仅创建window,获取WindowManager,并把它设置到我们创建的window中,也是在attach中进行的。而其中WindowManager是通过系统服务的方式获取的,这种获取的方式,可以认为是一种单例,ContextImpl在执行getSystemService会判断是否已经创建过了WindowManager,否则就创建一个返回。
不过要注意的是,这里的WindowManager也是抽象的概念,它是一个接口,有具体的实现WindowManagerImpl。而比较有趣的事情是,WindowManagerImpl内部有一个单例对象WindowManagerGlobal,关于View的操作都转发给了它去完成的。WindowManagerImpl通过这种代理的方式,让使用更加的灵活,使用者不需要关心具体的窗口管理实现细节,而可以在不同的上下文对象中使用。
这样一来,看上面这张图就很清晰的了解他们之间的关系了。唯一遗漏的可能就是ViewManager,我们知道WindowManager是一个接口,但是它也同样继承了一个ViewManager的接口类,这个类顾名思义是可以看出来的,对View进行管理。其内部很简单,就是View的添加、更新、删除的接口方法。
// ViewManager.java
public interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.
* <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
* errors, such as adding a second view to a window without removing the first view.
* <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
* secondary {@link Display} and the specified display can't be found
* (see {@link android.app.Presentation}).
* @param view The view to be added to this window.
* @param params The LayoutParams to assign to view.
*/
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
不知道有没有注意到,上面我们提到过,WindowManagerGlobal作为WindowManager真正的实现类,它帮助WindowManagerImpl操作的,就是对View的操作,可见ViewManager的接口的具体实现,在WindowManagerGlobal中能找到真正的实现逻辑。
Window和View的关联
回到第一张界面层级图,我们讲了Activity创建了window,讲了window的具体实现,讲了window管理其中的view是通过WindowManager,但是没有讲到DecorView。它在window和view关联中起了重要的作用。
我们知道的DecorView是什么,装饰视图?content view可以理解为内容视图,就是开发的应用界面,title view却不是界面中的标题。而更多理解为是除去应用部分,通用的一些样板界面,比如状态栏,菜单栏等,一些应用都会共有的部分。
解释了DecorView,那它是怎么创建的,可以去PhoneWindow里找找看。我们Activity在设置视图的时候,通常都是通过setContentView来实现的,其中两个重要的点,通过分析源码来介绍一下。
@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 {
//添加传入的view
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
installDecor(),创建装饰视图,并且将mDecor设置给当前这个window;判断mContentParent是否为空,为空则创建一个mContentParent并返回。
这里简单的贴一下创建的代码ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// PhoneWindow.java
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
mContentParent#addView,上面看到了ContentParent是一个ViewGroup,在创建的过程中需要传入mDecor,可见ContentParent的约束是依赖mDecor的,即ContentParent是DecorView中的一个ViewGroup。最后通过addView的方法,把内容View添加进去。
梳理一下他们的关联:
window - DecorView - ContentParent - View
补充
setContentView在onCreate的时候就关联了View和Window,视图的显示是在onResume执行完成之后,那么什么时候执行WindowManager#addView递交的绘制任务呢?
//Activity
public void setVisible(boolean visible) {
if (mVisibleFromClient != visible) {
mVisibleFromClient = visible;
if (mVisibleFromServer) {
if (visible) makeVisible();
else mDecor.setVisibility(View.INVISIBLE);
}
}
}
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
在onResume执行之后,会调用setVisible设置可见状态,这个时候内部有一个makeVisible的方法,里面获取了wm,并且执行了它的addView。
总结
1、window是view的容器,view是window的具体内容。
2、window的实现是PhoneWindow。
3、window的管理类是WindowManagerImpl。
4、window把View相关的操作交给了WindowManagerGlobal。
5、window和View的关联是通过DecorView
这样一来,应用层的window,以及相关的管理者,就基本将明白了。但是离我们需要弄明白的事情还有很多,之后会开始分析一个很重要的角色ViewRootImpl。