You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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_TOASTTYPE_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

火山引擎 最新活动