Kotlin实现悬浮窗服务时抛出Unable to add window -- token null is not valid; is your activity running?错误
嘿,我刚看完你的代码和错误日志,这个问题其实挺常见的,核心原因是你窗口类型参数设置错误,再加上权限校验没做全导致的。咱们一步步来解决:
错误原因分析
你代码里明明定义了LAYOUT_FLAG来根据Android版本选择正确的窗口类型(O以上用TYPE_APPLICATION_OVERLAY,低版本用TYPE_PHONE),但实际创建WindowManager.LayoutParams的时候,却硬写了WindowManager.LayoutParams.TYPE_APPLICATION_PANEL——这个类型是给Activity内部窗口用的,需要绑定Activity的token,而Service本身没有Activity的token,自然就抛出"token null is not valid"的错误了。
另外,SYSTEM_ALERT_WINDOW权限在Android 6.0+的处理比较特殊,光在Manifest里声明还不够,必须引导用户去系统设置里手动开启权限,否则悬浮窗也无法正常显示。
解决方案
1. 修正窗口类型参数
把创建WindowManager.LayoutParams时的类型替换成你已经定义好的LAYOUT_FLAG,替换这行代码:
val parameters = WindowManager.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, // 把这里的TYPE_APPLICATION_PANEL改成LAYOUT_FLAG LAYOUT_FLAG, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT )
2. 添加权限校验逻辑
在启动悬浮窗服务之前,先检查是否已经获取了悬浮窗权限:
- 在MainActivity中,启动服务前先判断权限:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { // 引导用户去设置页开启权限 val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName")) startActivityForResult(intent, 1001) } else { // 权限已获取,启动服务 startService(Intent(this, FloatingWindow::class.java)) } } else { // 低版本直接启动服务 startService(Intent(this, FloatingWindow::class.java)) }
- 然后在MainActivity中处理权限申请的结果:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == 1001) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Settings.canDrawOverlays(this)) { startService(Intent(this, FloatingWindow::class.java)) } else { Toast.makeText(this, "需要开启悬浮窗权限才能使用", Toast.LENGTH_SHORT).show() } } } }
3. 优化Service中的空安全处理
你的代码里用了很多!!强制非空调用,容易触发空指针,建议改成安全调用?.:
比如把wm!!.addView(ll, parameters)改成wm?.addView(ll, parameters),同理其他地方的!!都替换成?.,或者提前做空判断。
修改后的FloatingWindow关键代码片段
@RequiresApi(Build.VERSION_CODES.O) @SuppressLint("ClickableViewAccessibility") override fun onCreate() { super.onCreate() wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager? ll = LinearLayout(this@FloatingWindow).apply { setBackgroundColor(Color.WHITE) layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 400) orientation = LinearLayout.VERTICAL } val LAYOUT_FLAG = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY } else { WindowManager.LayoutParams.TYPE_PHONE } val parameters = WindowManager.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, LAYOUT_FLAG, // 这里用正确的窗口类型 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT ).apply { gravity = Gravity.TOP or Gravity.END x = 0 y = 0 } // ... 后面的Button、TextView初始化代码不变 ... // 安全调用添加View wm?.addView(ll, parameters) // ... 触摸监听和点击事件代码不变 ... }
这样修改之后,应该就能解决你遇到的错误了。核心就是用对窗口类型,确保权限到位,再做好空安全处理~
备注:内容来源于stack exchange,提问作者Wanjala Alex

