如何开发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:
Android locks down overlays for security, so this is non-negotiable:
- Pre-Android 10: You’ll need to request the
SYSTEM_ALERT_WINDOWpermission. 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).
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
TextViewworks great for basic FPS display, or a custom View if you want fancy styling). - Configure
WindowManager.LayoutParamscorrectly 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_FOCUSABLEflag is critical—it ensures your overlay doesn’t block touch input to the apps below it.
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) } })
- 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
xandyvalues in theWindowManager.LayoutParamsand callwindowManager.updateViewLayout()to save the new position. - Performance: Keep the FPS calculation lightweight—avoid heavy computations in the
doFramecallback, as it runs every frame and can impact the very FPS you’re trying to measure. - Cleanup: Always remove the overlay view from
WindowManagerwhen 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

