Window是什么?
1.先看官方的定义:1
2
3
4
5
6
7
8
9
10
11
12
13/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
一、作为顶层窗口的抽象类,并规范了窗口的外观与行为,同时也是顶层view的窗口管理器。
二、提供诸如背景,标题、区域、默认事件处理等等。
三、只有一个唯一实现类->PhoneWindow
2.文档解释比较简单,其实就是一个抽象的管理类。当我们启动一个Activity
,生命周期开始时setContentView()
,都知道这个方法要传入即是我们的布局ID:1
2
3
4public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
3.由于Window
是抽象类,而PhoneWindow
作为其唯一实现类,很好理解,这里即让Activity
与Window(PhoneWindow)
关联起来,这是第一步。
4.PhoneWindow
中的成员变量DecorView
,注释也给出了解释:This is the top-level view of the window, containing the window decor.
。DecorView
继承自FrameLayout,本质就是一个ViewGroup。为什么需要这个DecorView
?往前回忆setContentView()
这个方法,在PhoneWindow
的重写方法中对DecorView
的初始化方法installDecor()
,在我们开发过程中,安卓系统默认为我们提供了一些Theme
主题,不同的主题对应了不同的资源布局文件。在初始化时,通过addView
为DecorView
中加入了一个view root
并且这个root
包含的就是开发者设置的主题信息对应的系统布局。root
是一个线性布局,包含了actionbar
与一个FrameLayout
,这个就是包含我们自身布局的最终容器。到这里Window
就和View
关联起来了。
5.总结一下:activity中调用的setContenView将window与activity相关联,而phonewindow作为window的唯一实现类,每个activity中包含一个phonewindow,phonewindow中又持有了decorview的实例。decorview作为一个viewgroup,通过addview添加了一个view(root),最终我们设置的布局会被添加到这个root中的fagmelayout中。这样,activity就与view相关联了起来。window作为管理器的角色。包括view的添加移除,但是这些操作要借助另一个重要的类windowManager。
Window是否多余?
1.经过分析,发现window所包含的一些作用其实view基本都已经涵盖了,那么这个window是否多余呢?答案是否定的,类比MVC,MVP,MVVM
等设计模式,window的作用也很类似,使activity与view解耦。view本身代码量很大,处理的事情很多。另外window的添加涉及到IPC的调用处理,再来看看WindowManager这个接口,继承了viewmanager:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/** Interface to let you add and remove child views to an Activity. To get an instance
* of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
*/
//可见windowmanager功能很明确,就是对activity中的view进行添加和移除操作,windowmanager的实现类为WindowManagerImpl
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);
}
Dialog的context
1.看一段代码:1
2
3
4
5Dialog dialog = new Dialog(getApplicationContext());
dialog.show();
运行之后报出的错误:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
因为我们传入的是getApplicationContext(),那么为什么getApplicationContext不行呢?
2.看看dialog的构造方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29Dialog(int themeResId, boolean createContextThemeWrapper) { Context context,
if (createContextThemeWrapper) {
if (themeResId == Resources.ID_NULL) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
//关键代码,activity的context获得的是activity中windowmanager,而application获得的是一个新对象-->WindowManagerImpl
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
3.mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
假设我们传入的是activity的context,那么得到的是activity的windowManager:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
看windowManager的实现类WindowManagerImpl,其中包含成员变量private IBinder mDefaultToken;
很明显这个token指向的就是我们的activity,因此传入activity而实例化的dialog自然不会报token null is not valid这个错误。
4.现在看dialog中的show方法,解释为什么application类型的context会报错:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50public void show() {
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
if (!mCreated) {
dispatchOnCreate(null);
} else {
// Fill the DecorView in on any configuration changes that
// may have occured while it was removed from the WindowManager.
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
onStart();
mDecor = mWindow.getDecorView();
if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
final ApplicationInfo info = mContext.getApplicationInfo();
mWindow.setDefaultIcon(info.icon);
mWindow.setDefaultLogo(info.logo);
mActionBar = new WindowDecorActionBar(this);
}
WindowManager.LayoutParams l = mWindow.getAttributes();
boolean restoreSoftInputMode = false;
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
l.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
restoreSoftInputMode = true;
}
//上面经过分析得出,如果是application或者service的context,获得了新对象windowmanagerimpl,此时mDefaultToken为null
mWindowManager.addView(mDecor, l);
if (restoreSoftInputMode) {
l.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
}
mShowing = true;
sendShowMessage();
}
5.看windowmanagerimpl中两个关键方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33private IBinder mDefaultToken;
/**
* Sets the window token to assign when none is specified by the client or
* available from the parent window.
*
* @param token The default token to assign.
*/
public void setDefaultToken(IBinder token) {
mDefaultToken = token;
}
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
//为token赋值,由于基于application的context创建的,所以token还是null,也就是为什么报了viewrootimpl中错误
//Unable to add window --tokenis not valid; is your activity running?"
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
// Only use the default token if we don't have a parent window.
if (mDefaultToken != null && mParentWindow == null) {
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
// Only use the default token if we don't already have a token.
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (wparams.token == null) {
wparams.token = mDefaultToken;
}
}
}
系统电量低“弹窗”?
1.作为一个系统的全局“弹窗”,如果真的是dialog,那么上述分析就是错误,因为这个“弹窗”不依赖任何activity。下面是构造方法:1
2
3
4
5
6
7
8
9
10
public PowerNotificationWarnings(Context context, ActivityStarter activityStarter) {
//原来是个通知
mContext = context;
mNoMan = mContext.getSystemService(NotificationManager.class);
mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mKeyguard = mContext.getSystemService(KeyguardManager.class);
mReceiver.init();
mActivityStarter = activityStarter;
}