一、概论
Window 是一个窗口的概念,是所有视图的载体,不管是 Activity,Dialog,还是 Toast,他们的视图都是附加在 Window 上面的。例如在桌面显示一个悬浮窗,就需要用到 Window 来实现。WindowManager 是访问 Window 的入口。
- Window 是一个抽象类,他的实现类是 PhoneWidow
- Activity 中的 DecorView ,Dialog 中的 View 都是在 PhoneWindow 中创建的
因此 Window 实际是 View 的直接管理者
1.1 如何新增一个window
/**
* 添加新窗口
*/
fun addNewWindow() {
val mTextView = TextView(this).apply {
text = "window"
textSize = 20f
setTextColor(Color.WHITE)
setBackgroundColor(Color.RED)
}
val mParent = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
0, 0, PixelFormat.TRANSPARENT
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mParent.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
mParent.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
}
mParent.flags =
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
mParent.x = 250
mParent.y = 450
windowManager.addView(mTextView, mParent)
}
以上代码就是往页面新增一个系统window,效果图如下:需要注意的几点是:
- 在Android O (13)上需要改对应的 TYPE
- 在Android O (13)上需要给 悬浮窗 权限
- 需要在清单文件中加上这句权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
上面代码中 Type 和 Flags 的属性比较重要,这里把它列出来
Type 窗口属性
Window 类型 | 层级范围 | 说明 |
---|---|---|
应用 Window | 1 ~ 99 | 对应着一个 Activity |
子 Window | 1000 ~ 1999 | 不能单独存在,需要附属在特定的 Window 之中,例如常见的 PopupDialog,就是子 Window。 |
系统 Window | 2000 ~ 2999 | 需要声明权限才能创建的 Window,,例如 Toast 和 系统状态栏这些都是系统的 Window |
- 子 Window 无法单独存在,必须依赖父级 Window,例如 PopWindow 必须依赖 Activity
- Window 分层,在显示时层级高的会覆盖层级低的窗口
Flags窗口的标志
Flags 表示 Window 的属性,它有多选项,通过这些可以通知 Window 显示的特性,例如:
Flags | 特性 |
---|---|
FLAG_NOT_FOCUSABLE | 表示 Window 不需要获取焦点,也不需要各种输入事件,此标记通同时启用 FLAG_NOT_TOUCH_MODAL最终事件会直接传递给下层具有焦点的 Window。 |
FLAG_NOT_TOUCH_MODAL | 将 Window 区域以外的单击事件传递给底层的 Window,当前 Window 内的单击事件自己处理,一般都要开启此事件,否则其他 Window 无法收到单击事件 |
FLAG_SHOW_WHEN_LOCKED | 可以将 Window 显示在锁屏的界面上 |
FLAG_TURN_SCREEN_ON | Window 显示时将屏幕点亮 |
二、源码流程解析
上面demo的最后一句代码中通过 windowManager
在屏幕上添加了一个系统级别的 Window,通过 windowManager
的addView
方法。那我们从这里跟进去看源码,看看具体的添加流程是怎样的。
2.1 WindowManager & ViewManager
#WindowManager
@[SystemService]
public interface WindowManager extends ViewManager
--------------------------------------------------------------------------------------
#ViewManager
public interface ViewManager{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
可以看到 Windowmanger
其实是个接口,其中 (addView,updateViewLayout,removeView)
更新方法其实是继承自ViewManager
。而真正实现 WindowManger
这3个方法的实现类是 WindowManagerImpl
public final class WindowManagerImpl implements WindowManager {
//·····
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
}
上面代码看到,真正实现操作的是 mGlobal 这个对象:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
2.2 WindowManagerGlobal.addView
可以看到 WindowManagerImpl 并没有直接实现 Window
三大操作,而是全部交给了 WindowManagerGlobal
来处理。
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//检测参数是否合法
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
//....
}
// 真正设置window的主
ViewRootImpl root;
View panelParentView = null;
//创建 ViewRootImpl,并赋值给 root
root = new ViewRootImpl(view.getContext(), display);
//设置 View 的params
view.setLayoutParams(wparams);
//将 view,RootRootImpl,wparams 添加到列表中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
//调用 ViewRootImpl 来更新界面并完成 Window 的添加过程
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
首先声明了几个列表:
- mViews 中是所有 Window 对应的 View
- mRoots 中是所有 Window 对应的 ViewRootImpl
- mParams 存储的是所有 Window 所对应的布局参数
- 而 mDyingViews 中是哪些真在被删除的 View,或者说是已经调用 RemoveView 但是删除操作没有完成的 Window 对象。
上面代码中,创建了 ViewRootImpl,然后将 view,ViewRootImpl,wparams 添加到列表中。最后通过 ViewRootImpl 来完成添加 Window 的过程。
2.2 ViewRootImpl.setView
接着我们进到 ViewRootImpl 中的 setView 方法来看这里的添加过程
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// Binder 对象
final IWindowSession mWindowSession;
synchronized (this) {
if (mView == null) {
mView = view;
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
// 添加window之前,先进行一次刷新请求
requestLayout();
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//调用了 mWindowSession.addToDisplay 方法,来完成最终的 Window 的添加过程
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
}
//.....
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
在上面的代码中首先会调用 requestLayout
来进行一次刷新请求,其中 scheduleTraversals()
是 View 绘制的入口
requestLayout
调用之后,调用了 mWindowSession.addToDisplay
方法,来完成最终的 Window
的添加过程。
在上面代码中,mWindowSession
的类型是 IWindowSession
,他是一个 Binder
对象,真正的实现是 Session
,也就是 Window
的添加过程是一次 IPC
调用。
// IWindowSession
interface IWindowSession {
int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in InsetsVisibilities requestedVisibilities,
out InputChannel outInputChannel, out InsetsState insetsState,
out InsetsSourceControl[] activeControls, out Rect attachedFrame,
out float[] sizeCompatScale);
//.......
}
//------------------------------------------------------------------------------------------------------------------
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
final WindowManagerService mService;
@Override
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,
outActiveControls, outAttachedFrame, outSizeCompatScale);
}
可以看到 IWindowSession
是个aidl
文件的接口,真实的实现类是Session
,在其中真正实现了addToDisplay
方法,接着就交给了mService
调用addWindow()
方法。而这个mService
就是 WindowManagerService
。
如此一来,Window
的添加过程就交给了WindowManagerService
去处理。WMS
会为其分配 Surface
,确定窗口显示的次序,最终通过 SurfaceFlinger
将这些 Surface
绘制到屏幕上。这部分的内容我们在后续的文章进行分析。
2.3 添加window流程分析
windowManager.addView
WindowManagerImpl.addView
在addView
中将实现委托给了WindowManagerGlobal.addView()
- 在
addView
中创建了ViewRootImpl
赋值给了root
。然后将view
,params
,root
全部存入了各自的列表中。最后调用了ViewRootImpl.setView()
- 在
setView
中通过调用requestLayout
完成刷新的请求,接着会通过IWindowSession
来完成最终的Window
添加的过程,IWindowSession
是一个Binder
对象,真正的实现类是Session
,也就是说Window
的添加过程试一次IPC
的调用。在Session
中会通过WindowManagerService
的addWindow
方法来实现Window
的添加。
2.4 Window 更新源码分析
上面看到window
的更新也是通过 WindowManagerGlobal
这个代理类来进行操作的,那就从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");
}
//将更新的参数设置到 view 中
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
//获取到 view 在列表中的索引
int index = findViewLocked(view, true);
//拿到 view 对应的 ViewRootImpl
ViewRootImpl root = mRoots.get(index);
//从参数列表中移除旧的参数
mParams.remove(index);
//将新的参数添加到指定的位置中
mParams.add(index, wparams);
//调用 ViewRootImpl.setLayoutPrams 对参数进行更新
root.setLayoutParams(wparams, false);
}
}
通过 ViewRootImpl
的setLayoutParams
方法对Window
进行更新操作:
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
//..........
scheduleTraversals();
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
//通过runnable进行刷新
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 进行刷新操作
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
}
void doTraversal() {
//.....
// 刷新操作
performTraversals();
//.....
}
private void performTraversals() {
//刷新操作
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
// 通过mWindowSession进行刷新
int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
mPendingMergedConfiguration, mSurface);
}
}
在 setLayoutPrams
方法中,最终调用了 scheduleTraversals
方法来对 View
重新策略,布局,重绘。
@Override
public int relayout(IWindow window, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
int lastSyncSeqId, ClientWindowFrames outFrames,
MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl,
InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
Bundle outSyncSeqIdBundle) {
// 通过WindowManagerService来更新视图
int res = mService.relayoutWindow(this, window, attrs,
requestedWidth, requestedHeight, viewFlags, flags, seq,
lastSyncSeqId, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
outActiveControls, outSyncSeqIdBundle);
return res;
}
除了 View
本身的重绘外,ViewRootImpl
还会通过 WindowSession
来更新 Window
视图,这个过程是由 WindowManagerService
的 relayoutWindow
来实现的,这同样也是一个 IPC
过程。
2.5 Window 删除源码分析
Window
的删除过程和添加过程都一样,都是先通过 WindowManagerImpl
后,在进一步通过 WindowManagerGlobal
来实现的:
@Override
public void removeView(View view) {
// 异步移除
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
// 同步移除
mGlobal.removeView(view, true);
}
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
//找到在 views 列表中的索引
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
上面代码中,找到在 views
列表中的索引,然后调用了 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);
}
}
}
removeViewLocked
是通过 ViewRootImpl
来完成删除操作的。在 WindowManager
中提供了两种删除接口 removeView
和 removeViewImmedialte
分别是异步删除和同步删除。
一般不会使用 removeViewImmedialte
来删除 Window
,以免发生意外错误。
所以这里使用的是 异步的删除情况,采用的是 die
方法。die
方法只是发送了一个请求删除的消息就立刻返回了,这个时候 View 并没有完成删除操作,所以最后会将其添加到 mDyingViews
列表中。
#ViewRootImpl
/**
* @param immediate True, do now if not in traversal. False, put on queue and do later.
* @return True, request has been queued. False, request has been completed.
*/
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
从源码的注释看出:
- 如果传入是
true
则马上移除,同时返回false
代表移除完成(同步删除) - 如果传入的是
false
则加入到队列中等待执行,同时返回true
表示等待执行。(异步删除)
这个方法里面做了判断,如果是异步删除就会发送一个 MSG_DIE
的消息,ViewRootImpl
中的 handler
会收到这个消息,并调用 doDie
方法,这就是这两种删除方式的区别。
@Override
public void handleMessage(Message msg) {
case MSG_DIE:
doDie();
break
}
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
//真正执行删除的逻辑
dispatchDetachedFromWindow();
}
if (mAdded && !mFirst) {
destroyHardwareRenderer();
//...........
mAdded = false;
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
void dispatchDetachedFromWindow() {
try {
//........
mWindowSession.remove(mWindow);
//........
} catch (RemoteException e) {
}
}
在上面代码中最后通过ViewRootImpl
的 doDie
方法执行移除操作,接着在dispatchDetachedFromWindow
方法中执行真正的移除操作。而最终还是交给了mWindowSession
的remove
方法。
依旧是通过IPC
,最终交给Session
的 remove
方法来执行移除操作。
@Override
public void remove(IWindow window) {
mService.removeWindow(this, window);
}
可以看到还是通过WindowManagerService
来执行最后的移除操作。
三、总结
Window 的 创建,更新,移除 操作都是通 ViewRootImpl 使用 IPC与WMS(WindowManagerService )通信来实现的。