window表示窗口的概念,平时开发中使用不多,但是某些时候我们需要在桌面上显示一个类似悬浮窗的东西(比如系统弹的吐司:Toast,就是在window上弹得)那么这种效果就需要window来实现。
1、window是一个抽象类,他的具体唯一实现类phonewindow
2、创建window使用windowmanager即可,windowmanager是外界访问window的入口
3、window的具体实现位于windowManagerService中,windowmanager和windowManagerService的交互是一个ipc过程
4、安卓中所有的视图都是通过window来呈现的,不管是activity,dialog还是toast,他们视图实际上都是附加在window上的
5、通过view的事件分发机制我们知道,事件由window传递给decorview,然后由decorview传递给我们的view,就连activity的setContentView底层也是通过window来完成的。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 打开权限设置 设置允许后 就可以 在window上显示按钮
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 {
Button button = new Button(this);
button.setText("按钮");
//1 声明布局参数
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
0, 0, PixelFormat.TRANSPARENT);
// 设置flag
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
layoutParams.gravity = Gravity.CENTER;
layoutParams.x = 100;
layoutParams.y = 100;
// 8.0 处理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
// 2、添加到window
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(button,layoutParams);
}
}
}
app运行跳转权限界面,我们允许相关的显示权限即可。如下图桌面上显示出来了
代码很简单就是else中的逻辑就三步
1、声明布局参数: WindowManager.LayoutParams
2、设置一些布局参数(主要是flag、type的设置)
3、添加到window(通过windowmanager就可以添加了)
上面的栗子中有个重要的类:WindowManager.LayoutParams这个类是WindowManager的内部类用来管理window的参数。
FLAG_NOT_FOCUSABLE
表示window不需要获得焦点,也不需要各种输入事件(跳不出软键盘)此标记同时也会开启FLAG_NOT_TOUCH_MODAL标记,最终事件传递给具有焦点的window。
FLAG_NOT_TOUCH_MODAL
这个标记设置后,系统会将当前window以外的单击事件,传递给底层的window,当前window以内的点击事件自己处理。这个标记很重要一般来说都开启,否则其他window我发收到单击事件。
FLAG_SHOW_WHEN_LOCKED
表示Window可以在锁屏界面上显示,只适用于最顶层的全屏幕Window
ps:这个参数在AIP27时过期了,推荐使用 R.attr.showWhenLocked参数或者Activity.setShowWhenLocked(boolean)。
Window是分层的,每个Window都有对应的z-ordered,大层级的Window会覆盖在小层级的上面。层级就对应着Type参数。
用用window的层级范围:1-99
子window的层级范围:1000-1999
系统Window的层级范围:2000-2999
1、我们上文使用的:
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
TYPE_APPLICATION_OVERLAY这个type进入源码发现其数值为2038
2、我们上文使用的:
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
TYPE_SYSTEM_ALERT进入源码我们发现其数值为2003
如果想要window位于所有的window的最顶层,只需要采用较大的层级即可(type使用大数值的),很显然系统window的层级时最大的而且系统层级有很多值我们一般使用TYPE_APPLICATION_OVERLAY或者TYPE_SYSTEM_ALERT都可,同时采用系统类型window需要加权限(我们栗子中使用就是系统类型window)。
WindowManager所提供的功能很简单,常用的有三个方法:
1、 addView添加view
2、updateViewLayout更新 view
3、 removeView 删除view
这三个方法定义在viewManager这个接口中WindowManager是他的实现类
如果我们想要窗口上的view可以触摸滑动只需要给view设置触摸事件,在触摸事件中不断修改布局参数的位置,在调用updateViewLayout更新view即可
1、window是一个抽象概念,每个window都对应一个view和和一个viewRootImpl
2、window通过viewRootImpl和view建立联系,所以window不是真实存在的,他是以view的形式存在。
3、我们无法直接访问window,windowManager提供了操作window的api,他的常用方法也也是针对view的。
为了分析window的机制我们从window的添加、删除、更新说起
window的添加过程是通过windowManager的addview来完成的,windowManager也是一个接口,他的实现类是windowManagerImpl接下来我们看看其三大操作(添加删除更新)源码:
// mGlobal 是 WindowManagerGlobal 对象
public void addView( View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
public void updateViewLayout(View view, ViewGroup.LayoutParams params){
mGlobal.updateViewLayout(view,params);
}
public void removeView(View view){
mGlobal.removeView(view);
}
可以看到windowManagerImpl并没有直接实现而是交给了WindowManagerGlobal 去实现
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;
}
}
}
可以看到主要做了如下操作:(参考图解)
1、检查参数是否合法,如果是子window还需要调整布局参数
2、创建ViewRootImpl对象,并将view添加到列表
3、通过ViewRootImpl的setView来完成window的添加过程
ps:通过view的事件体系我们知道view的绘制过程是由ViewRootImpl来完成的,这里当然不例外。setView内部调用requestLayout方法来完成异步刷新请求,requestLayout内部的scheduleTraversals就是view绘制入口。接着通过WindowSession来完成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);
} // ......
}
}
}
通过上面我们知道删除过程也是WindowManagerGlobal 来接管的。源码如下:
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);
}
}
逻辑清晰:首先通过findViewLocked来查找待删除view的索引。
然后调用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());
}
}
// 调用die去进行删除操作
// immediate参数指是否使用同步删除
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
// 加入死亡集合
mDyingViews.add(view);
}
}
}
这里创建了ViewRootImpl 对象具体的删除操作还是由ViewRootImpl 对象的die方法来完成
注意:die方法传递参数 false表示异步删除,true表示同步删除。
我们继续看die的源码:
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;
}
如果是同步调用doDie处理,立即删除。
如果是异步发送MSG_DIE消息给ViewRootImpl的Handler,handleMessage()中也是调用doDie处理
doDie源码:
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);
}
可以看到主要调用两个方法:dispatchDetachedFromWindow、doRemoveView
dispatchDetachedFromWindow源码:
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();
}
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 (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
doTrimForeground();
}
}
dispatchDetachedFromWindow和doRemoveView主要做了四件事:
1、垃圾回收相关工作,比如清除数据消息,移除回调
2、通过Session的remove方法删除view,这是个ipc过程,最终是远程调用WindowManagerService的removeWindow()
3、调用view对象的dispatchDetachedFromWindow(),内部调用view的onDetachedFromWindow()完成回收工作
4、调用windowManagerGloba的doRemoveView刷新数据,包括mRoots,mParams,mDyingViews,将当前view所关联三类对象从列表删除
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);
}
}
1、首先更新view的LayoutParams ,替换老的布局参数。
2、创建ViewRootImpl 对象更新ViewRootImpl 的布局参数
3、ViewRootImpl 的setLayoutParams内部通过,scheduleTraversal来重新完成view的测量布局重绘三个过程。还通过windowSession来完成window的视图更新。
这里主要总结了下window添加删除更新view的情况,下节总结下各种window的创建
参考文章:https://www.jianshu.com/p/7e589ddb634a
本文来自<安卓开发艺术探索>笔记总结
下篇:理解Window和WindowManager(二)三种window的创建过程