Android 10全局悬浮窗开发:问题排查与实现方案
解决Android 10+悬浮窗(Overlay)创建报错:
Unable to add window -- token null is not valid; is your activity running? 我明白你想要实现类似自定义ROM开发者选项里"Show CPU stats"的悬浮窗功能,用来展示CPU、GPU频率等内核状态,但在Android 10上一直碰窗口添加失败的问题。这个报错主要是悬浮窗类型参数配置错误,再加上Android 8.0+对前台服务的强制限制导致的,下面我来拆解问题并给出完整的解决思路。
错误原因分析
你最初的HUD Service代码里,WindowManager.LayoutParams的type参数混用了TYPE_TOAST和TYPE_APPLICATION_OVERLAY,这是核心问题:
- 在Android 8.0(API 26)及以上版本,自定义悬浮窗必须使用
TYPE_APPLICATION_OVERLAY作为窗口类型 TYPE_TOAST是系统Toast专用的窗口类型,不能用来创建自定义悬浮窗,强行使用会导致token无效的报错
另外,Android 8.0+要求后台启动的服务必须转为前台服务,需要显示一条通知,否则会被系统限制启动,这也是你需要注意的细节。
完整解决实现方案
你已经自己解决了问题,这里我把关键要点和最终代码整理得更清晰:
1. 清单文件权限配置
确保已经声明悬浮窗权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <application> <!-- 你的其他配置 --> <service android:name="org.pierre2324.nogravity.HUD"/> </application>
2. 权限检查与动态请求
在启动悬浮窗服务前,必须检查并请求SYSTEM_ALERT_WINDOW权限(Android 6.0+需要动态请求):
private boolean isSystemAlertPermissionGranted(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return Settings.canDrawOverlays(context); } return true; } private void requestSystemAlertPermission(Activity activity, int requestCode) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + activity.getPackageName())); activity.startActivityForResult(intent, requestCode); } }
3. 正确的HUD Service实现
使用TYPE_APPLICATION_OVERLAY作为窗口类型,同时适配Android 8.0+的前台服务要求:
public class HUD extends Service{ static final String CHANNEL_ID = "Overlay_notification_channel"; private static final int LayoutParamFlags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; private LayoutInflater inflater; private View layoutView; private WindowManager windowManager; private WindowManager.LayoutParams params; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); // 配置悬浮窗核心参数 params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // Android 8.0+必须使用此类型 LayoutParamFlags, PixelFormat.TRANSLUCENT); params.gravity = Gravity.TOP | Gravity.END; windowManager = (WindowManager) this.getSystemService(WINDOW_SERVICE); // 加载自定义悬浮窗布局 inflater = LayoutInflater.from(this); layoutView = inflater.inflate(R.layout.ngk_overlay, null); windowManager.addView(layoutView, params); // Android 8.0+必须启动前台服务,避免被系统杀死 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, getString(R.string.ngk_overlay_notification), NotificationManager.IMPORTANCE_LOW); // 设置为低优先级,减少对用户的打扰 notificationChannel.setSound(null, null); notificationManager.createNotificationChannel(notificationChannel); Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) .setContentTitle(getString(R.string.ngk_overlay)) .setContentText(getString(R.string.ngk_overlay_notification)) .setSmallIcon(R.drawable.ic_mono2); startForeground(1, builder.build()); } } @Override public void onDestroy() { super.onDestroy(); if (layoutView != null) { windowManager.removeView(layoutView); } } }
4. 优化后的开关控制逻辑
确保在权限获取成功后再启动服务,避免无效操作:
overlaySwitch.setOnClickListener(new OnClickListener() { public void onClick(View view) { if (overlaySwitch.isChecked()) { if(!isSystemAlertPermissionGranted(getActivity())){ requestSystemAlertPermission(getActivity(), 1); return; // 权限未授予时,先引导用户授权,不执行后续操作 } // 启动悬浮窗服务 Intent serviceIntent = new Intent(getContext(), HUD.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { getContext().startForegroundService(serviceIntent); } else { getContext().startService(serviceIntent); } newEditor.putBoolean("Overlay", true); Toast.makeText(getContext(), "NGK Overlay enabled!", Toast.LENGTH_SHORT).show(); } else { getContext().stopService(new Intent(getContext(), HUD.class)); newEditor.putBoolean("Overlay", false); Toast.makeText(getContext(), "NGK Overlay disabled", Toast.LENGTH_SHORT).show(); } newEditor.apply(); } });
关键注意事项
- 窗口类型:Android 8.0+必须使用
TYPE_APPLICATION_OVERLAY,旧的TYPE_SYSTEM_ALERT等类型已经被废弃 - 前台服务:Android 8.0+启动悬浮窗服务必须调用
startForeground(),并创建对应的通知渠道 - 权限检查:必须确保用户已经授予
SYSTEM_ALERT_WINDOW权限,否则无法创建悬浮窗
内容的提问来源于stack exchange,提问作者Pierre2324




