You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何开发Android全局悬浮FPS计数Widget?已了解Choreographer方案

Absolutely, building an always-on-top FPS counter widget that floats over all active apps is totally feasible on Android. Let’s break this down into actionable steps, tying in your existing Choreographer-based FPS tracking logic:

1. First: Nail the Overlay Permission

Android locks down overlays for security, so this is non-negotiable:

  • Pre-Android 10: You’ll need to request the SYSTEM_ALERT_WINDOW permission. Since this is a special permission, you can’t request it via the normal runtime permission flow—instead, send users to the system settings page:
    if (!Settings.canDrawOverlays(context)) {
        val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                            Uri.parse("package:${context.packageName}"))
        startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE)
    }
    
  • Android 10+: The same overlay permission applies, but you may also need to handle the "display over other apps" permission in some device-specific cases (most modern devices honor the above flow).
2. Build the Floating Widget with WindowManager

The WindowManager class is your bread and butter for creating overlays that sit above all apps. Here’s how to set it up:

  • Create a lightweight View (a TextView works great for basic FPS display, or a custom View if you want fancy styling).
  • Configure WindowManager.LayoutParams correctly to ensure the overlay behaves as expected:
    val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
    val params = WindowManager.LayoutParams().apply {
        type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
        } else {
            WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
        }
        flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or 
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
        width = WindowManager.LayoutParams.WRAP_CONTENT
        height = WindowManager.LayoutParams.WRAP_CONTENT
        gravity = Gravity.TOP or Gravity.START
        x = 50 // Initial position (pixels from top-left)
        y = 50
        format = PixelFormat.TRANSLUCENT
    }
    // Add your FPS view to the window manager
    windowManager.addView(fpsView, params)
    
  • The FLAG_NOT_FOCUSABLE flag is critical—it ensures your overlay doesn’t block touch input to the apps below it.
3. Integrate Choreographer for Real-Time FPS Tracking

You already know Choreographer is the right tool for frame timing—here’s how to tie it to your floating widget:

  • Track the time between consecutive frames to calculate FPS. Use a rolling average to smooth out fluctuations:
    private var lastFrameTimeNanos = 0L
    private var fps = 0f
    private val frameCount = mutableListOf<Long>()
    
    Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
        override fun doFrame(frameTimeNanos: Long) {
            if (lastFrameTimeNanos != 0L) {
                val frameDuration = frameTimeNanos - lastFrameTimeNanos
                val currentFps = 1_000_000_000 / frameDuration.toFloat()
                // Use a rolling average of the last 10 frames for smoother output
                frameCount.add(frameDuration)
                if (frameCount.size > 10) frameCount.removeAt(0)
                fps = 1_000_000_000 / (frameCount.average().toFloat())
                // Update the floating TextView directly (Choreographer runs on main thread)
                fpsView.text = String.format("FPS: %.1f", fps)
            }
            lastFrameTimeNanos = frameTimeNanos
            // Re-register the callback to keep tracking frames
            Choreographer.getInstance().postFrameCallback(this)
        }
    })
    
4. Key Optimizations & Best Practices
  • Lifecycle Management: Host the overlay logic in a ForegroundService (Android 8+ requires foreground services for long-running background tasks) to prevent the system from killing your FPS tracker when the app is in the background.
  • Draggable Overlay: Add touch listeners to your FPS view so users can drag it around the screen—update the x and y values in the WindowManager.LayoutParams and call windowManager.updateViewLayout() to save the new position.
  • Performance: Keep the FPS calculation lightweight—avoid heavy computations in the doFrame callback, as it runs every frame and can impact the very FPS you’re trying to measure.
  • Cleanup: Always remove the overlay view from WindowManager when your service is destroyed to avoid memory leaks:
    override fun onDestroy() {
        super.onDestroy()
        windowManager.removeView(fpsView)
    }
    

Once you piece all this together, you’ll have a smooth, reliable FPS counter that stays on top of every app. Feel free to tweak the UI (add color coding for low FPS, adjust text size, etc.) to fit your needs!

内容的提问来源于stack exchange,提问作者Artem Garkusha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 07:52:26