WindowManager这东西,说难也不算难,但真要用起来,尤其是第一次接触悬浮窗或者动态添加View的朋友,十有八九会碰到各种奇怪的坑。我自己前阵子做项目就栽在它手里好几次,什么BadTokenException、权限没弹、View加不上去,折腾得够呛。所以今天干脆把WindowManager的用法和那些常见问题一次性说清楚,全是自己实测过的经验,希望能帮大家少走点弯路。
![WindowManager 使用教程 悬浮窗 添加View 常见问题 排查][[FIND: WindowManager 权限 BadTokenException 解决][[FIND: WindowManager addView removeView 报错 布局参数](https://p.qqan.com/up/2026-8/2026082920161979035.png)
简单讲,WindowManager就是用来管理窗口的,它负责把View加到屏幕上,也能把View从屏幕上移除。平时我们用setContentView,其实底层也是通过WindowManager来操作的。只不过系统帮我们封装好了,感觉不到它的存在。如果你想自己往屏幕上加点东西,比如悬浮球、弹窗、或者一些特殊动画+效果,那就得直接跟WindowManager打交道了。
用的时候,先通过getSystemService拿到WindowManager实例,然后创建一个View,再准备一个WindowManager.LayoutParams,设置好宽高、类型、flags这些参数,最后调用addView把View加进去。看起来挺简单的,对吧?但实际一跑,各种问题就冒出来了。
先说说addView,这是最核心的方法。我用它做过一个类似悬浮球的功能,代码大概是这样:
WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
View floatingView = LayoutInflater.from(this).inflate(R.layout.float_view, null);
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
windowManager.addView(floatingView, params);
这里有个地方要特别注意,就是TYPE_APPLICATION_OVERLAY这个类型。以前很多人用TYPE_PHONE或者TYPE_SYSTEM_ALERT,但在Android 8.0以上系统里,这些类型已经不好使了,必须用TYPE_APPLICATION_OVERLAY,否则会直接报错。
再一个就是removeView,这个容易出问题。如果你在子线程里直接调removeView,或者View还没加到WindowManager里就调removeView,那肯定要崩。我一开始就是没判断isAttachedToWindow,结果崩了好几次。
说到悬浮窗权限,这真的是WindowManager使用里最容易踩的坑。Android 6.0以上,悬浮窗权限是单独管理的,不像普通权限那样在清单文件里写一下就行。你得在代码里用Settings.canDrawOverlays(this)检查是否已经授权,如果没有,还要跳转到设置页面让用户手动打开。
我记得有一次测试,明明在清单文件里写了
另外,如果你只是在应用内用WindowManager,不涉及跨应用显示,那权限要求会低一些,但最好还是把权限检查做完整,免得在部分机型上翻车。
这个话题估计很多人感兴趣,因为我搜过好多次,论坛里问的人也特别多。BadTokenException通常出现在你addView的时候,WindowManager拿到的token无效。最常见的情况就是,你在Activity销毁之后还去addView,或者你用的是TYPE_APPLICATION这种需要token的类型。
解决的办法,一个是确保addView的时候Activity还在运行,二是在onDestroy里把View移除掉,三是尽量用TYPE_APPLICATION_OVERLAY这种不需要token的类型。我自己的做法是,在BaseActivity里统一管理,onPause的时候记录状态,onResume再恢复,这样能很大程度避免莫名其妙崩溃。
还有个小细节,如果你addView的时候用了FLAG_NOT_FOCUSABLE,那键盘弹出来的时候可能不会自动顶上去,这个要根据实际需求取舍。
removeView报错,常见的原因就是View已经不在WindowManager里了,或者你重复remove了。我在测试的时候发现,有时候明明remove成功了,但还是会崩,后来查了源码才知道,原来View的parent可能已经被清理了。
最稳的做法是,先检查view.getParent() != null,然后再调用removeView。另外,如果你用的View有动画,最好等动画结束再移除,不然也会出问题。我后来封装了一个工具类,专门处理add和remove,每次remove前都做判断,这样基本没再崩过。
悬浮窗是WindowManager用得最多的场景,但坑也最多。除了权限、类型之外,还有一个就是触摸事件。悬浮窗默认不接收触摸事件,你要想拖动它,得自己处理onTouchListener,设置FLAG_NOT_TOUCH_MODAL之类的flag,不然点击了没反应。
我记得第一次做悬浮球,点击了没反应,我还以为代码写错了,后来才发现是flag没设置对。这个细节很容易被忽略,建议大家多看看官方文档。
还有一点,悬浮窗的尺寸和位置,最好用像素值来指定,因为不同屏幕密度下,dp转px容易出偏差。我一般用Resources.getSystem().getDisplayMetrics().density来换算,这样在大部分手机上显示都比较正常。
WindowManager这个东西,用熟了其实挺顺手,但刚开始确实容易碰壁。我的经验是,多看源码,多写demo,遇到问题别急着一顿乱改,先定位是权限、类型还是生命周期的问题。很多时候,报错信息已经告诉你答案了,只是你太着急没仔细看。
另外,如果你要在多个页面使用WindowManager,建议封装成一个单例工具类,统一管理View的添加和移除,这样代码会干净很多,也方便排查问题。好了,今天就分享到这儿,希望对大家有帮助。