一、概述
1 ) 什么是Window?什么是WindowManager?
1. Window
- Window是一个抽象类,PhoneWindow是它的唯一实现类。
- Window实际上是View的直接管理者。
- Android中的所有视图都是通过Window来实现的。
- 不管是Activity、Dialog还是Toast,它们的视图实际上都是附加在Window上的。
- View是Android中呈现视图的方式,但是View不能单独存在,必须附着在Window这个抽象的概念上。
- 有视图的地方就有Window。
2. WindowManager
- 创建一个Window需要通过WindowManager。
- Window的具体实现在WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。
2 ) 添加一个Window实例
1. 处理权限
AndroidManifest.xml
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
AddWindowActivity # onCreate()
if (Build.VERSION.SDK_INT >= 23 && !Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent, 1);
} else {
addWindow();
}
2. 完成addWindow()
AddWindowActivity # addWindow()
在获得权限之后,就会在屏幕正中间显示一个Button了。这里需要注意type的设置,如果不设置type,就会报错,在下面会说关于type参数的知识点。
private void addWindow() {
// 先创建一个button
mButton = new Button(this);
mButton.setText("Button");
// 配置window的参数
mLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
0, 0, PixelFormat.TRANSPARENT);
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL;
mLayoutParams.gravity = Gravity.CENTER;
// 8.0以上需要这样设置type
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mLayoutParams.type = LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ALERT;
}
// 获得WindowManager
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
if (mWindowManager != null) {
// 通过WindowManager添加Window
mWindowManager.addView(mButton, mLayoutParams);
}
}
3 ) Window的参数
上面实例中,设置属性都是通过一个LayoutParams来实现的,这个WindowManager.LayoutParams是WindowManager的静态内部类,用来管理Window的参数。
1. Flags参数
Flags参数可以控制Window的显示特性。Flags参数非常多,详细看这里。
常用的有下面几种:
- FLAG_NOT_TOUCH_MODAL
- 设置后Window区域外的事件传递给下层的Window,区域内的事件自己处理。
- 一般都会设置该参数,否则其它Window会收不到事件。
- FLAG_NOT_FOCUSABLE
- 不接受任何输入事件,跳不出软键盘。
- 这个参数会同时也会开启FLAG_NOT_TOUCH_MODAL。
- FLAG_SHOW_WHEN_LOCKED
- 表示Window可以在锁屏界面上显示。
- 只适用于最顶层的全屏幕Window。
这个参数在AIP27时过期了,推荐使用 R.attr.showWhenLocked参数或者Activity.setShowWhenLocked(boolean)。
2. Type参数
这个参数表示Window的类型,分为三种:
- 应用Window:对应着一个Activity。
- 子Window:不能单独存在,附属在父Window中,比如Dialog。
- 系统Window:需要声明权限,比如Toast、系统状态栏。
Window是分层的,每个Window都有对应的z-ordered,大层级的Window会覆盖在小层级的上面。层级就对应着Type参数。
// 应用Window的层级范围
public static final int FIRST_APPLICATION_WINDOW = 1;
public static final int LAST_APPLICATION_WINDOW = 99;
// 子Window的层级范围
public static final int FIRST_SUB_WINDOW = 1000;
public static final int LAST_SUB_WINDOW = 1999;
// 系统Window的层级范围
public static final int FIRST_SYSTEM_WINDOW = 2000;
public static final int LAST_SYSTEM_WINDOW = 2999;
比如在上面的实例中使用的TYPE_APPLICATION_OVERLAY,实际上就是一个系统层级的Type。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mLayoutParams.type = LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ALERT;
}
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
因为是系统层级的Window,所以需要处理这个权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
4 ) WindowManager提供的方法
WindowManager继承了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);
}
WindowManager操作Window实际上就是在操作Window中的View。
二、Window内部机制
每个Window都对应一个View和一个ViewRootImpl,View和Window通过ViewRootImpl连接,所以Window其实是以View的形式存在的。
WindowManager也是一个接口,实现类是WindowManagerImpl,实现了ViewManager的方法,来看这些方法的实现。
1 ) Window的添加过程
Window的添加是通过addView()来实现的。
WindowManagerImpl # addView()
可以看出WindowManagerImpl并没有直接实现,而是全部交给mGlobal。
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerGlobal # getInstance()
mGlobal是一个WindowManagerGlobal对象。提供一个单例对象管理着所有的WindowManager中调用的操作。
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
WindowManagerGlobal中有四个全局集合,存储着所有的Window的参数、所有Window对应的ViewRootImpl和它们对应的顶级View,最后一个存储的是调用了removeView()但是操作还尚未完成的View。
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>();
WindowManagerGlobal # addView()
下面就是addView()的代码,主要做了这些工作:
- 检查参数。
- 判断是否有父Window
- 有,调整title和token。
- 无,应用开启了硬件加速的话,该View就开启。
- 遍历所有ViewRootImpl,更新系统参数。
- 如果需要添加的View已经在mViews集合中。
- 如果还在dying集合中,就将原先的Window删除,之后继续。
- 如果不在,就直接结束该方法,因为已经添加好了。
- 如果是子Window,就遍历搜索父Window,赋值引用。
- 新建一个ViewRootImpl,给要添加的View配置LayoutParams,将三个对象添加到集合中。
- 调用ViewRootImpl的setView()更新界面。
- 最后,在setView()中完成Window的添加。
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");
}
// 判断params是否是WindowManager.LayoutParams类型的
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
// 如果有父Window,就根据type赋值params的title和token
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
// 如果没有父Window并且应用开启了硬件加速,就设置该Window开启硬件加速
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
// 先上锁
synchronized (mLock) {
if (mSystemPropertyUpdater == null) {
// 赋值一个Runnable,用来遍历更新所有ViewRootImpl的有关参数
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
// 添加到执行队列中
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
// 看需要add的view是否已经在mViews中
int index = findViewLocked(view, false);
if (index >= 0) {
// 如果被添加过,就看是否在死亡队列里也存在
if (mDyingViews.contains(view)) {
// 如果存在,就将这个已经存在的view对应的window移除
mRoots.get(index).doDie();
} else {
// 否则,说明view已经被添加,不需要重新添加了
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
}
// 如果属于子Window层级
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
// 遍历ViewRootImpl,看是否存在一个Window的IBinder对象和需要添加的Window的token一致,之后赋值引用
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
// 新创建一个ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
// 给需要添加的View设置params
view.setLayoutParams(wparams);
// 将三个参数加入三个集合中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
// 调用ViewRootImpl的setView(),这个方法会调用开始ViewRootImpl的performTraversals()
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
ViewRootImpl # setView()
- 调用requestLayout(),post一个调用performTraverslas()的Runnable去更新界面。
- 调用IWindowSession的addToDisplay()远程调用WindowManagerService处理添加Window的请求。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// ......
mAdded = true;
int res;
// 内部调用提交一个更新界面的Runnable去执行performTraversals()
requestLayout();
// ......
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
// IPC调用,调用WindowManagerService去处理
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} // ......
}
}
}
2 ) Window的删除过程
WindowManagerImpl # removeView()
同样还是交给WindowManagerGlobal处理。
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
WindowManagerGlobal # removeView()
主要工作是获取了下标,做了判断。调用removeViewLocked()删除。
public void removeView(View view, boolean immediate) {
// 判空
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
// 上锁
synchronized (mLock) {
// 获取需要移除的对象在顶级View集合中的下标
int index = findViewLocked(view, true);
// 获得需要移除对象的Window的顶级View
View curView = mRoots.get(index).getView();
// 删除逻辑
removeViewLocked(index, immediate);
// 如果是同一个对象,就返回
if (curView == view) {
return;
}
// 如果不是,抛出一个异常,删除的View不是Window的顶级View
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
WindowManagerGlobal # removeViewLocked()
处理输入法后调用die()去删除。
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());
}
}
// 调用die去进行删除操作
// immediate参数指是否使用同步删除
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
// 加入死亡集合
mDyingViews.add(view);
}
}
}
ViewRootImpl # die()
removeView()传入的immediate为false表示异步删除,removeViewImmediate()传入的是true,异步删除。
- 如果是同步就直接调用doDie()直接删除。
- 如果是异步就发送一个消息给Handle。
boolean die(boolean immediate) {
// 如果是同步并且没有正在执行traversal
if (immediate && !mIsInTraversal) {
// 直接调用删除
doDie();
// 返回false表示没有排队,立即执行删除了。
return false;
}
if (!mIsDrawing) {
// 如果不在绘制流程中,就关掉硬件加速
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
// 给ViewRootImpl发送一个MSG_DIE消息
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
在handleMessage()中,处理MSG_DIE也是调用doDie()。
case MSG_DIE:
doDie();
break;
ViewRootImpl # doDie()
doDie()主要调用了两个方法,dispatchDetachedFromWindow()和doRemoveView(),这两个方法来处理删除。
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
dispatchDetachedFromWindow();
}
// ......
mAdded = false;
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
ViewRootImpl # dispatchDetachedFromWindow()
主要做了这些事:
- 调用Window移除相关的监听回调方法。
- 移除回调,移除Window的数据,释放、置空。
- 远程调用WindowManagerService的removeWindow()。
- 切断通信通道。
- 移除traverslas的Runnable。
void dispatchDetachedFromWindow() {
if (mView != null && mView.mAttachInfo != null) {
// 调用监听的onXXX()回调方法
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
}
mAccessibilityInteractionConnectionManager.ensureNoConnection();
// 移除回调
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mAccessibilityManager.removeHighTextContrastStateChangeListener(
mHighContrastTextManager);
removeSendWindowContentChangedCallback();
// 移除硬件加速
destroyHardwareRenderer();
// 将数据置为null或释放
setAccessibilityFocus(null, null);
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
mSurface.release();
if (mInputQueueCallback != null && mInputQueue != null) {
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
mInputQueue.dispose();
mInputQueueCallback = null;
mInputQueue = null;
}
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
try {
// IPC调用WindowManagerService删除Window
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
// 切断和远程通信
if (mInputChannel != null) {
mInputChannel.dispose();
mInputChannel = null;
}
// 取消监听
mDisplayManager.unregisterDisplayListener(mDisplayListener);
// 移除消息队列中准备执行traversals的Runnable
unscheduleTraversals();
}
WindowManagerGlobal # doRemoveView()
在调用dispatchDetachedFromWindow()移除Window之后,还调用了这个方法,它主要是处理WindowManagerGlobal几个集合,将删除的Window相关对象从集合中移除。
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 (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
doTrimForeground();
}
}
3 ) Window的更新过程
WindowManagerImpl # updateViewLayout()
同样也是交给WindowManagerGlobal处理。
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
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设置新的参数
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
// 移除之前这个view的Params
mParams.remove(index);
// 加入新的
mParams.add(index, wparams);
// 给ViewRootImpl设置新的参数
root.setLayoutParams(wparams, false);
}
}
ViewRootImpl # setLayoutParams()
在这个方法里主要做两件事,一个是赋值新参数,另一个是提交traversals重新侧脸布局绘制。
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
// ......
// 复制赋值参数
mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
// ......
mWindowAttributesChanged = true;
// 调用performTraversals()
scheduleTraversals();
}
}
到这里还没有结束,addView()、removeView()都会调用到WindowManagerService,updateViewLayout()也不例外。回顾一下,在performTraversals()中,除了三个流程的调用,还有这样一行,远程调用了WindowManagerService的relayoutWindow()。
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);