1 Window 和 WindowManager
1.1 说一说对于 Window 的理解。
- 从概念上来说,
Window
表示一个窗口的概念。 - 从类结构上来说,
Window
是一个抽象类,它的具体实现是PhoneWindow
,同时也是唯一的实现。 - 从获取方式上来说,在实际使用中无法直接访问
Window
,外界访问Window
必须要通过WindowManager
,也就是说,WindowManager
是外界访问Window
的入口; - 从内部机制上来说,
Window
的具体实现位于WindowManagerService
中,WindowManager
和WindowManagerService
的交互是一个 IPC 过程。 - 从和
View
的关系上来说,Android 中所有的视图都是通过Window
来呈现的,不管是Activity
,Dialog
还是Toast
,以及PopupWindow
,菜单等,它们的视图实际上都是附加在Window
之上的,因此Window
实际是View
的直接管理者。
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.
1.2 WindowManager 的常用方法有哪些?
WindowManager
是一个接口,常用的方法有三个,即添加 View
,更新 View
和删除 View
,这三个方法是定义在 ViewManager
接口中,WindowManager
是继承于 ViewManager
的。
可以看到,addView
(添加 View
),updateViewLayout
(更新 View
)和 removeView
(删除 View
) 这三个接口方法都是针对 View
来操作的。
1.3 WindowManager 中有一个静态内部类 LayoutParams,绘制一下它的类图。
参考链接:·WindowManager.LayoutParams详解总结和对应实例
1.4 Window 的类型分类
WindowManager.LayoutParams
的 type
参数表示 Window
的层级范围。
类型 | 含义 | 层级范围 | 说明 | 举例 |
---|---|---|---|---|
Application windows | normal top-level application windows | FIRST_APPLICATION_WINDOW(1)~LAST_APPLICATION_WINDOW(99) | For these types of windows, the token must be set to the token of the activity they are a part of (this will normally be done for you if token is null | 比如 Activity的窗口类型是 TYPE_BASE_APPLICATION(1) |
Sub-windows | associated with another top-level window | FIRST_SUB_WINDOW(1000)~ LAST_SUB_WINDOW(1999) | For these types of windows, the token must be the token of the window it is attached to | |
System windows | special types of windows for use by the system for specific purposes | FIRST_SYSTEM_WINDOW(2000)~ LAST_SYSTEM_WINDOW(2999) | They should not normally be used by applications, and a special permission is required to use them | 状态栏窗口的类型是TYPE_STATUS_BAR(2000),瞬态通知(如 Toast)的窗口类型是 TYPE_TOAST(2005) |
Window
是分层的,每个 Window
都有对应的 z-ordered,层级大的会覆盖在层级小的 Window
的上面。
应该注意的是,在有些手机上,需要手动打开 android.permission.SYSTEM_ALERT_WINDOW
才可以。
Window 是分层的,每个 Window 都有对应的 z-ordered,层级大的会覆盖在层级小的 Window 之上。
1.5 说一说实际开发中 Window 的应用
-
在应用接管通话页面时,通话页退到后台了,需要显示一个悬浮窗来显示当前通话的时长;
-
在通过辅助权限来做权限申请时,显示一个 Window 在最顶层,防止用户再去操作页面;
-
有的应用的锁屏页面就是通过 Window 来实现的。
2 Window 的内部机制
2.1 Window 和 View 的关系
Window
是一个抽象的概念,每一个 Window
都对应着一个 View
和 ViewRootImpl
,Window
和 View
通过 ViewRootImpl
来建立联系。所以,Window
并不是实际存在的,它是以 View
的形式存在的。View
才是 Window
存在的实体。
2.2 Window 的添加过程
public class WindowActivity extends Activity implements View.OnTouchListener {
private WindowManager mWindowManager;
private Button mFloatingButton;
private WindowManager.LayoutParams mLayoutParams;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_window);
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
}
public void button(View view) {
mFloatingButton = new Button(this);
mFloatingButton.setText("click me");
mLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
// 如果不写下面这行,那么锁屏后竟然无法解锁了。
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL // 设置了这个标记,即便当前 Window 具有焦点,
// 也允许将当前 Window 区域以外的单击事件传递给底层的 Window;否则,当前窗口将消费所有的点击事件,不管点击事件是否在窗口之内。
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE // 表示 Window 不需要获取焦点,也不需要接收各种输入事件,
// 此标记会同时启用 FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的 Window
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; // 让 Window 显示在锁屏的界面上。
mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mFloatingButton.setOnTouchListener(this);
// 添加
mWindowManager.addView(mFloatingButton, mLayoutParams);
}
}
从上面的代码中,可以看到:Window
的添加过程需要通过 WindowManager
的 addView
来实现。但是,WindowManager
是一个接口,那么它的实现类是谁呢?
其实在 ContextImpl
里有一个静态代码块,包含了初始化 WindowManager
的代码:
static {
registerService(WINDOW_SERVICE, new ServiceFetcher() {
Display mDefaultDisplay;
public Object getService(ContextImpl ctx) {
Display display = ctx.mDisplay;
if (display == null) {
if (mDefaultDisplay == null) {
DisplayManager dm = (DisplayManager)ctx.getOuterContext().
getSystemService(Context.DISPLAY_SERVICE);
mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
}
display = mDefaultDisplay;
}
// 构造了 WindowManagerImpl 的实例
return new WindowManagerImpl(display);
}});
}
private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
new HashMap<String, ServiceFetcher>();
private static void registerService(String serviceName, ServiceFetcher fetcher) {
if (!(fetcher instanceof StaticServiceFetcher)) {
fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
}
SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}
再看一下 ContextImpl
的 getSystemService
方法:
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
这里获取的正是 WindowManagerImpl
的实例。所以,WindowManager
接口的真正实现是 WindowManagerImpl
类。
我们看一下 WindowManagerImpl
类如何实现添加 Window
的:
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Display mDisplay;
private final Window mParentWindow;
public WindowManagerImpl(Display display) {
this(display, null);
}
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
}
可以看到,WindowManagerImpl
并没有直接实现 Window
的添加,更新以及删除,而是全部交给了 WindowManagerGlobal
来处理。
WindowManagerGlobal
是单例类,这一点需要特别注意一下。
WindowManagerImpl
与 WindowManagerGlobal
的关系用到了桥接模式,如下:
先介绍 WindowManagerGlobal
里几个重要的列表:
// 存储的是所有 Window 对应的 View 对象
private final ArrayList<View> mViews = new ArrayList<View>();
// 存储的是所有 Window 对应的 ViewRootImpl 对象
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
// 存储的是所有 Window 对应的布局参数
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
// 存储的是那些正在被删除的 View 对象,或者说是那些已经调用 removeView 方法
// 但是删除操作还未完成的 View 对象
private final ArraySet<View> mDyingViews = new ArraySet<View>();
在 WindowManagerGlobal
的 addView
方法的执行流程:
-
检查参数合法性,如果是子
Window
(存在父Window
的就是子Window
)就调整一些布局参数;如果不存在父Window
并且版本大于等于 21,会默认设置窗口的硬件加速属性。 -
检查
View
是否在mViews
集合中,是否在mDyingViews
集合中。如果已经在mView
里,是不可以添加的,会抛出异常;如果已经在mDyingViews
集合里,需要立马销毁掉对应的View
。int index = findViewLocked(view, false); if (index >= 0) { if (mDyingViews.contains(view)) { // Don't wait for MSG_DIE to make it's way through root's queue. mRoots.get(index).doDie(); } else { throw new IllegalStateException("View " + view + " has already been added to the window manager."); } // The previous removeView() had not completed executing. Now it has. }
-
创建
ViewRootImpl
对象,并将VIew
添加到列表中相关的代码如下:
// 创建 ViewRootImpl 对象 root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); // 添加到列表中 mViews.add(view); mRoots.add(root); mParams.add(wparams);
-
通过
VIewRootImpl
来更新界面完成Window
的添加过程root.setView(view, wparams, panelParentView);
4.1 在
setView
方法内部,先通过requestLayout()
来完成异步刷新请求:实际完成的View
的布局,测量以及绘制过程public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { // 检查是否是 UI 线程 checkThread(); mLayoutRequested = true; // 开启异步刷新请求(这里面用到了同步屏障消息和异步消息) scheduleTraversals(); } }
4.2 接着通过
WindowSession
最终来完成Window
的添加过程。// 这个返回值,就是平常开发中遇到的 `BadTokenException`,`InvalidDisplayException` 的来源。 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mInputChannel);
mWindowSession
的类型是IWindowSession
,它是一个Binder
对象,真正的实现类是Session
,这说明Window
的添加过程需要一次 IPC 调用。注意,
mWindow
在ViewRootImpl
的构造方法里完成初始化:mWindow = new W(this);
,它是ViewRootImpl
对象的包装。4.3 在
Session
内部通过WindowManagerService
来实现Window
的添加。public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outInputChannel); }
2.3 Window 的删除过程
mWindowManager.removeView(mFloatingButton);
-
调用
WindowManager
的removeView
方法,是调用它的实现类WindowManagerImpl
的removeView
方法;再通过桥接调用WindowManagerGolbal
对象的removeView(View view, boolean immediate)
方法(第二个参数取值为false
)如下:public void removeView(View view, boolean immediate) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } synchronized (mLock) { // 先在 mViews 集合里查找索引,找不到会抛出异常 int index = findViewLocked(view, true); // 从 mRoots 里根据索引找到 ViewRootImpl 对象,获取对应的 `View` 对象 View curView = mRoots.get(index).getView(); // 最后移除 View 对象 removeViewLocked(index, immediate); if (curView == view) { return; } throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView); } }
-
removeViewLocked
方法:private void removeViewLocked(int index, boolean immediate) { ViewRootImpl root = mRoots.get(index); View view = root.getView(); if (view != null) { InputMethodManager imm = InputMethodManager.getInstance(); if (imm != null) { imm.windowDismissed(mViews.get(index).getWindowToken()); } } boolean deferred = root.die(immediate); if (view != null) { view.assignParent(null); if (deferred) { mDyingViews.add(view); } } }
这里就是调用了
ViewRootImpl
的die
方法。这里传给
die
的实参是false
,表示异步删除,由WindowManagerImpl
的removeView
方法调用而来;传true
时,表示同步删除,由WindowManagerImpl
的removeViewImmediate
方法调用而来。一般不要使用同步删除方式来删除Window
以免发生意外的错误。 -
ViewRootImpl
的die
方法:boolean die(boolean immediate) { // Make sure we do execute immediately if we are in the middle of a traversal o // done by dispatchDetachedFromWindow will cause havoc on return. if (immediate && !mIsInTraversal) { doDie(); return false; } if (!mIsDrawing) { destroyHardwareRenderer(); } else { Log.e(TAG, "Attempting to destroy the window while drawing!\n" + " window=" + this + ", title=" + mWindowAttributes.getTitle()); } mHandler.sendEmptyMessage(MSG_DIE); return true;
如果
immediate
取值为true
,即同步删除,那么会直接调用doDie()
方法,die
方法返回false
,表示已经完成删除;如果
immediate
取值为false
,即异步删除,那么发送一个请求删除的消息(MSG_DIE
),die
方法返回true
,表示请求在排队中(消息被处理后,依然调用的是doDie()
方法),再回到removeViewLocked
方法中,会把view
添加到mDyingViews
集合(表示存那些正在被删除的 View 对象集合)。 -
ViewRootImpl
的doDie()
方法:void doDie() { checkThread(); if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface); synchronized (this) { if (mRemoved) { return; } mRemoved = true; if (mAdded) { dispatchDetachedFromWindow(); } mAdded = false; } // 刷新几个列表的数据 WindowManagerGlobal.getInstance().doRemoveView(this); }
-
ViewRootImpl
的dispatchDetachedFromWindow()
方法:void dispatchDetachedFromWindow() { if (mView != null && mView.mAttachInfo != null) { mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false); // 回调 View 的 onDetachedFromWindow 方法 mView.dispatchDetachedFromWindow(); } mView.assignParent(null); mView = null; try { // Session 的 remove 方法移除 Window,最终会调用 WMS 的 removeWindow 方法 mWindowSession.remove(mWindow); } catch (RemoteException e) { } // Dispose the input channel after removing the window so the Window Manager // doesn't interpret the input channel being closed as an abnormal termination. if (mInputChannel != null) { mInputChannel.dispose(); mInputChannel = null; } mDisplayManager.unregisterDisplayListener(mDisplayListener); unscheduleTraversals(); }
-
WindowManagerGlobal
的doRemoveView
方法刷新数据:void doRemoveView(ViewRootImpl root) { synchronized (mLock) { final int index = mRoots.indexOf(root); if (index >= 0) { mRoots.remove(index); mParams.remove(index); final View view = mViews.remove(index); mDyingViews.remove(view); } } if (HardwareRenderer.sTrimForeground && HardwareRenderer.isAvailable()) { doTrimForeground(); } }
2.4 Window 的更新过程
直接看 WindowManagerGlobal
的 updateViewLayout
方法:
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
首先是对参数的一些检查,接着把布局参数设置给 view
;int index = findViewLocked(view, true);
检查 view
是否还在 mViews
集合里,不在的话会抛出异常;最后调用 ViewRootImpl
对象的 setLayoutParams
方法更新 Window
。
查看 ViewRootImpl
的 setLayoutParams
方法:
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
final int oldInsetLeft = mWindowAttributes.surfaceInsets.left;
final int oldInsetTop = mWindowAttributes.surfaceInsets.top;
final int oldInsetRight = mWindowAttributes.surfaceInsets.right;
final int oldInsetBottom = mWindowAttributes.surfaceInsets.bottom;
final int oldSoftInputMode = mWindowAttributes.softInputMode;
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
// Preserve compatible window flag if exists.
final int compatibleWindowFlag = mWindowAttributes.privateFlags
& WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
// Transfer over system UI visibility values as they carry current state.
attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
if ((mWindowAttributesChangesFlag
& WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
// Recompute system ui visibility.
mAttachInfo.mRecomputeGlobalAttributes = true;
}
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
mWindowAttributes.privateFlags |= compatibleWindowFlag;
// Restore old surface insets.
mWindowAttributes.surfaceInsets.set(
oldInsetLeft, oldInsetTop, oldInsetRight, oldInsetBottom);
applyKeepScreenOnFlag(mWindowAttributes);
if (newView) {
mSoftInputMode = attrs.softInputMode;
requestLayout();
}
// Don't lose the mode we last auto-computed.
if ((attrs.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
== WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
& ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
| (oldSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);
}
// 标记 mWindowAttributesChanged 为 true
mWindowAttributesChanged = true;
scheduleTraversals();
}
}
在 ViewRootImpl
里会通过 scheduleTraversals
方法来对 View
重新布局,包括测量,布局,重绘三个过程。除了 View
本身的重绘外,ViewRootImpl
还会通过 WindowSession
来更新 Window
的视图,这个过程最终是 WMS 的 relayoutWindow()
来实现的,它同样是一个 IPC 过程。
3 Window 的创建过程
3.1 Activity 的 Window 创建过程
-
在
ActivityThread
的handleLaunchActivity
方法里先后调用performLaunchActivity
方法和handleResumeActivity
方法private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { // performLaunchActivity 方法完成创建 Activity 的实例对象, // 并调用其 attach 方法关联其运行过程中所依赖的一系列上下文环境变量, // 创建 Window,回调 onCreate 方法(在 onCreate 方法中通过 setContentView // 方法完成 DecorView 的创建和初始化)。 Activity a = performLaunchActivity(r, customIntent); if (a != null) { r.createdConfig = new Configuration(mConfiguration); Bundle oldState = r.state; // handleResumeActivity 方法会回调 onResume 方法完成顶层 View 的添加和显示 handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed); }
-
ActivityThread
的performLaunchActivity
方法完成创建Activity
的实例对象,并调用其attach
方法关联其运行过程中所依赖的一系列上下文环境变量,回调onCreate
方法(在onCreate
方法中通过setContentView
方法完成DecorView
的创建和初始化)private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { Activity activity = null; java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); // 通过 Instrumentation 对象创建 Activity 的实例 activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); r.intent.prepareToEnterProcess(); if (r.state != null) { r.state.setClassLoader(cl); } // 调用 Activity 对象的 attach 方法关联其运行过程中所依赖的一系列上下文环境变量 activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.voiceInteractor); // 回调 Activity 的 onCreate 方法 mInstrumentation.callActivityOnCreate(activity, r.state); }
-
在
Activity
的attach
方法里创建Window
对象并设置回调接口// 创建 PhoneWindow 对象 mWindow = new PhoneWindow(this); // 设置回调接口,目的是 Window 接收到外界的状态改变时就会回调到 Activity 实现的方法。 mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); // 创建并设置 Window 的 WindowManager 对象,实际上是一个 WindowManagerImpl 对象。 mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); mWindowManager = mWindow.getWindowManager();
目前我们创建了
PhoneWindow
对象,类包含图如下:
-
在
Activity
的onCreate
方法中调用setContentView
方法设置页面布局:Activity
的setContentView
方法如下:public void setContentView(int layoutResID) { // getWindow() 返回的是 mWindow 对象,即是在 attach 方法里创建的 PhoneWindow 对象。 getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
PhoneWindow
的setContentView
方法:public void setContentView(int layoutResID) { // 如果 mContentParent 为 null,就调用 installDecor() 方法 if (mContentParent == null) { installDecor(); } // 把布局填充到 mContentParent 里面 mLayoutInflater.inflate(layoutResID, mContentParent); // 调用回调方法,通知 Activity 视图已经发生改变了。 final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } }
这里留个小疑问:
mContentParent
的含义是什么?这里面我们需要去看一下
installDecor()
方法:private void installDecor() { // 如果 DecorView 对象不存在,就调用 generateDecor() 方法给它赋值。 if (mDecor == null) { mDecor = generateDecor(); } // 如果 mContentParent 不存在,就调用 generateLayout 方法给它赋值。 if (mContentParent == null) { mContentParent = generateLayout(mDecor); } }
在
generateDecor()
方法中创建DecorView
protected DecorView generateDecor() { return new DecorView(getContext(), -1); }
这里创建了
DecorView
对象,DecorView
是什么呢?它是PhoneWindow
的内部类,它是FrameLayout
的子类。public class PhoneWindow extends Window { private final class DecorView extends FrameLayout { } }
现在的类包含图如下:
接着看generateLayout(DecorView decor)
方法,这个方法有些长,但是里面的注释可以说明代码的意图:public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. 开始的大段代码的作用是应用来自当前主题的数据 // Inflate the window decor. 需要关注的是填充 window decor // 首先查找要把哪个布局填充给 window decor // 需要说明的是,布局资源的共同点是要有一个 id 为 @android:id/content 的 FrameLayout 节点。 int layoutResource; // Embedded, so no decoration is needed. layoutResource = R.layout.screen_simple; // 使用布局填充器把 xml 的布局资源转为 View, // 返回的是 xml 布局的根节点 View 对象,并且对象上拥有根节点上的布局参数。 View in = mLayoutInflater.inflate(layoutResource, null); // 把布局资源的根 View 添加到 DecorView 里面 decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); // 把 xml 布局的根节点 View 对象赋值给 mCotentRoot. mContentRoot = (ViewGroup) in; // 获取 id 为 @android:id/content 的节点对象,赋值给 contentParent. ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); // 一定要有 contentParent,否则抛出异常:Window 不能找到 content 的容器 View 对象。 if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } return contentParent; }
当
generateLayout()
方法结束后,会返回contentParent
对象,在installDecor()
方法中把返回值赋值给mContentParent
成员变量。到这里,我们就知道了
mContentParent
就是 id 为 @android:id/content 的节点对象。类包含图如下:
回到PhoneWindow
的setContentView
方法:public void setContentView(int layoutResID) { // 如果 mContentParent 为 null,就调用 installDecor() 方法 if (mContentParent == null) { installDecor(); } // 把布局填充到 mContentParent 里面 mLayoutInflater.inflate(layoutResID, mContentParent); // 调用回调方法,通知 Activity 视图已经发生改变了。 final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } }
mLayoutInflater.inflate(layoutResID, mContentParent);
就是把通过setContentView
方法传递过来的布局资源(我们这里称为 Activity 的视图),填充到mContentParent
里面。
-
在
ActivityThread
类的handleResumeActivity
方法中添加和显示DecorView
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { // 调用 performResumeActivity 获取了 ActivityClientRecord 对象, // 并回调了 Activity 的 onResume 方法 ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; // 添加 DecorView,但是还不会显示 if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); // 设置 DecorView 对象不可见 decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; // 把 DecorView 对象添加到 WindowManager。 wm.addView(decor, l); } } if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) { // 显示 DecorView if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } } } }
Activity
类的makeVisble()
方法:void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } // 设置 DecorView 对象可见 mDecor.setVisibility(View.VISIBLE); }
3.2 Dialog 的 Window 创建过程
- 在
Dialog
的构造方法中创建Window
对象并设置回调接口Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); // 创建 PhoneWindow 对象 final Window w = new PhoneWindow(mContext); mWindow = w; // 设置回调接口 w.setCallback(this); w.setOnWindowDismissedCallback(this); w.setOnWindowSwipeDismissedCallback(() -> { if (mCancelable) { cancel(); } }); // 创建 WindowManagerImpl 对象 w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); }
- 初始化
DecorView
对象并将Dialog
的视图添加到DecorView
中
这部分和public void setContentView(@LayoutRes int layoutResID) { mWindow.setContentView(layoutResID); }
Activity
的对应部分是一致的。 - 在
Dialog
的show
方法中将DecorView
对象添加到Window
中显示public void show() { // 从 PhoneWindow 对象中获取 DecorView 对象 mDecor = mWindow.getDecorView(); WindowManager.LayoutParams l = mWindow.getAttributes(); // 把 DecorView 添加到 Window 中显示 mWindowManager.addView(mDecor, l); }
3.3 Dialog 对应的 Context 必须是 Activity 的 Context 吗?
对于普通的 Dialog
而言,确实必须传递 Activity
的 Context
,否则会报错:
Dialog dialog = new Dialog(getApplicationContext());
dialog.setTitle("Title");
dialog.setContentView(R.layout.dialog_content);
dialog.show();
报错信息如下:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:1141)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:409)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:111)
at android.app.Dialog.show(Dialog.java:342)
at com.wzc.chapter_8.MainActivity.dialog(MainActivity.java:44)
at java.lang.reflect.Method.invoke(Native Method)
at android.view.View$DeclaredOnClickListener.onClick(View.java:6324)
at android.view.View.performClick(View.java:7509)
at android.view.View.performClickInternal(View.java:7486)
at android.view.View.access$3600(View.java:841)
at android.view.View$PerformClick.run(View.java:28720)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:236)
at android.app.ActivityThread.main(ActivityThread.java:8059)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)
查看日志是因为没有应用 token 导致的。这时只要传入 Activity
的 Context
来显示对话框就可以了,应用 token 一般只有 Activity 才有。
Dialog dialog = new Dialog(MainActivity.this);
如果非要传入非 Activity 的 Context 来显示对话框,就需要使用系统 Window,系统 Window 可以不需要 token。需要做的是:
- 在 AndroidManifest 文件中声明权限使应用可以使用系统 Window:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
- 指定 Dialog 的 Window 类型为系统类型(FIRST_SYSTEM_WINDOW(2000)~ LAST_SYSTEM_WINDOW(2999))
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
- 如果是小米手机,对话框不显示时,需要手动打开显示悬浮窗的开关。
3.4 Toast 的 Window 创建过程
-
创建
Toast
对象,并对Toast
对象的mNextView
和mDuration
进行赋值:public Toast(Context context) { mContext = context; mTN = new TN(); mTN.mY = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.toast_y_offset); mTN.mGravity = context.getResources().getInteger( com.android.internal.R.integer.config_toastDefaultGravity); }
result.mNextView = v; result.mDuration = duration;
可以看到通过构造方法传入了一个
Context
对象,并且把它赋值给mContext
成员变量。在构造方法里面,还初始化了一个TN
类型的成员变量。 -
创建
TN
对象private static class TN extends ITransientNotification.Stub { final Runnable mShow = new Runnable() { @Override public void run() { handleShow(); } }; final Runnable mHide = new Runnable() { @Override public void run() { handleHide(); mNextView = null; } }; private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); final Handler mHandler = new Handler(); View mView; View mNextView; WindowManager mWM; TN() { final WindowManager.LayoutParams params = mParams; params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.format = PixelFormat.TRANSLUCENT; params.windowAnimations = com.android.internal.R.style.Animation_Toast; params.type = WindowManager.LayoutParams.TYPE_TOAST; params.setTitle("Toast"); params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; } @Override public void show() { mHandler.post(mShow); } @Override public void hide() { mHandler.post(mHide); } public void handleShow() { if (mView != mNextView) { handleHide(); mView = mNextView; Context context = mView.getContext().getApplicationContext(); if (context == null) { context = mView.getContext(); } mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); if (mView.getParent() != null) { mWM.removeView(mView); } mWM.addView(mView, mParams); } } public void handleHide() { if (mView != null) { if (mView.getParent() != null) { mWM.removeView(mView); } mView = null; } } }
可以看到,
TN
继承于ITransientNotification.Stub
,这说明TN
是一个Binder
类,它实现了show()
和hide()
方法。在
TN
的构造方法里,主要是完成了对mParams
的属性赋值操作,比如对窗口层级属性type
赋值为WindowManager.LayoutParams.TYPE_TOAST
(2005),这属于系统 Window 了(FIRST_SYSTEM_WINDOW(2000)~ LAST_SYSTEM_WINDOW(2999))。在
show()
和hide()
方法内部通过Handler
对象mHandler
把要执行的任务切换到当前线程执行。那么,mHandler
是在哪里创建的?mHandler
是作为TN
对象的成员变量被显式初始化的。接着看具体任务执行的内容,即
handleShow()
和handleHide()
方法。可以看到,在handleShow()
方法内部完成了Window的添加:mWM.addView(mView, mParams);
,在handleHide()
方法内部完成了Window的删除:mWM.removeView(mView);
。但是,我们目前还不知道
show()
和hide()
方法在哪里调用的。 -
调用
Toast
的show()
方法来显示 Toastpublic void show() { INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; try { service.enqueueToast(pkg, tn, mDuration); } catch (RemoteException e) { } }
getService()
方法如下:private static INotificationManager sService; static private INotificationManager getService() { if (sService != null) { return sService; } sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification")); return sService; }
这里
getService()
方法获取的是客户端需要的INotificationManager
类型的对象,因为NotificationManagerService
作为服务端运行在系统的进程中,所以这里获取到的是INotificationManager.Stub.Proxy
对象。这一点可以通过反射获取sService
静态变量的值看出来。
那么,通过调用service.enqueueToast(pkg, tn, mDuration)
就是发起一次 IPC 通信过程了,传递的第一个参数是当前应用的包名,第二个参数是TN
对象,它其实是作为远程回调传入的,第三个参数是 Toast 的时长类型。最终会调用到运行在 Binder 线程池里的enqueueToast(String pkg, ITransientNotification callback, int duration)
方法。可以看到第二个形参的名字是callback
,这也说明了TN
的作用是回调接口对象。 -
调用
NotificationManagerService
里实现的enqueueToast
方法需要说明的是,
enqueueToast
并不是NotificationManagerService
的一个成员方法,而是它内部的一个匿名内部类里的方法。public class NotificationManagerService extends SystemService { private final IBinder mService = new INotificationManager.Stub() { public void enqueueToast(String pkg, ITransientNotification callback, int duration) { // 把参数信息封装在 ToastRecord 对象里面 record = new ToastRecord(callingPid, pkg, callback, duration); // 显示 Toast showNextToastLocked(); } } // showNextToastLocked 方法是 NotificationManagerService 的成员方法 void showNextToastLocked() { ToastRecord record = mToastQueue.get(0); while (record != null) { try { // 调用 TN 对象这个远程 Binder 的 show 方法 record.callback.show(); // 定时取消 Toast scheduleTimeoutLocked(record); return; } catch (RemoteException e) { Slog.w(TAG, "Object died trying to show notification " + record.callback + " in package " + record.pkg); // remove it from the list and let the process die int index = mToastQueue.indexOf(record); if (index >= 0) { mToastQueue.remove(index); } keepProcessAliveLocked(record.pid); if (mToastQueue.size() > 0) { record = mToastQueue.get(0); } else { record = null; } } } } }
通过调用
record.callback.show();
方法,会通过跨进程调用Toast
对象的mTN
的show()
方法,将 Toast 显示出来。 -
定时取消 Toast
查看 NMS 的
scheduleTimeoutLocked
方法static final int LONG_DELAY = 3500; // 3.5 seconds static final int SHORT_DELAY = 2000; // 2 seconds private void scheduleTimeoutLocked(ToastRecord r) { mHandler.removeCallbacksAndMessages(r); Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; mHandler.sendMessageDelayed(m, delay); }
通过
Handler
发送延时消息,延时消息执行时,会调用void cancelToastLocked(int index) { ToastRecord record = mToastQueue.get(index); // 调用 TN 对象这个远程 Binder 的 hide 方法 record.callback.hide(); mToastQueue.remove(index); keepProcessAliveLocked(record.pid); }
通过调用
record.callback.hide();
方法,会通过跨进程调用Toast
对象的mTN
的hide()
方法,将 Toast 隐藏掉。