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

如何借助MediaProjection API在悬浮窗模式下实现截图?

现有代码

MainActivity.kt

class MainActivity : ComponentActivity() { 
    private val TAG = "MainActivity" 
    override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState) 
        setContent { 
            MisInfoTheme { 
                Column( 
                    Modifier 
                        .fillMaxSize() 
                        .padding(16.dp), 
                    verticalArrangement = Arrangement.Center, 
                    horizontalAlignment = Alignment.CenterHorizontally 
                ) { 
                    Button(onClick = { 
                        startService(Intent(this@MainActivity, FloatingWindowService::class.java)) 
                    }) { 
                        Text(text = "Floating Widget") 
                    } 
                } 
            } 
        } 
    } 
}

FloatingWindowScreen.kt

@Composable 
fun floatWindowScreen() { 
    val context = LocalContext.current 
    Box( 
        modifier = Modifier 
            .clip(RoundedCornerShape(10.dp)) 
            .width(200.dp) 
            .height(50.dp) 
            .background(Color.LightGray), 
        contentAlignment = Alignment.Center 
    ) { 
        ClickableText( 
            text = AnnotatedString("On click take screenshot and disoplay it in the original app"), 
            onClick = { 
                Toast.makeText(context, "TODO", Toast.LENGTH_SHORT).show() 
            } 
        ) 
    } 
}

FloatingWindowService.kt

class FloatingWindowService : Service() { 
    private val TAG = "FloatingWindowService" 
    private var resultCode = 0 
    private val windowManager get() = getSystemService(WINDOW_SERVICE) as WindowManager 
    private val params = initWindowManagerParams() 

    override fun onBind(intent: Intent): IBinder? = null 

    override fun onCreate() { 
        super.onCreate() 
        drawFloatingWindow() 
    } 

    private fun initWindowManagerParams(): WindowManager.LayoutParams { 
        val paramType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY 
        } else { 
            WindowManager.LayoutParams.TYPE_PHONE 
        } 
        val paramFlags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or 
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or 
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or 
                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 
        val params = WindowManager.LayoutParams( 
            WindowManager.LayoutParams.WRAP_CONTENT, 
            WindowManager.LayoutParams.WRAP_CONTENT, 
            0, 0, 
            paramType, paramFlags, PixelFormat.TRANSLUCENT 
        ) 
        params.gravity = Gravity.TOP or Gravity.LEFT 
        return params 
    } 

    private fun drawFloatingWindow() { 
        val composeView = ComposeView(this) 
        composeView.setContent { 
            floatWindowScreen() 
        } 
        // 模拟生命周期绑定
        val viewModelStore = ViewModelStore() 
        val lifecycleOwner = FloatingWindowLifeCycleOwner() 
        lifecycleOwner.performRestore(null) 
        lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) 
        ViewTreeLifecycleOwner.set(composeView, lifecycleOwner) 
        ViewTreeViewModelStoreOwner.set(composeView) { viewModelStore } 
        composeView.setViewTreeSavedStateRegistryOwner(lifecycleOwner) 
        windowManager.addView(composeView, params) 
    } 
}

实现方案

1. 申请必要权限

在AndroidManifest.xml中添加权限:

<!-- 悬浮窗权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 屏幕捕获权限(Android 10+) -->
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<!-- 存储权限(可选,用于保存截图) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

注意:Android 6.0+需动态申请悬浮窗权限;Android 10+需通过MediaProjectionManager请求屏幕捕获授权。

2. 实现屏幕捕获逻辑

修改FloatingWindowService,添加屏幕捕获核心代码:

class FloatingWindowService : Service() {
    private val TAG = "FloatingWindowService"
    private lateinit var mediaProjectionManager: MediaProjectionManager
    private var mediaProjection: MediaProjection? = null
    private var imageReader: ImageReader? = null
    private val windowManager get() = getSystemService(WINDOW_SERVICE) as WindowManager
    private val params = initWindowManagerParams()
    private lateinit var composeView: ComposeView

    override fun onCreate() {
        super.onCreate()
        mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
        drawFloatingWindow()
    }

    // 启动屏幕捕获授权流程
    fun startScreenCapture() {
        val intent = mediaProjectionManager.createScreenCaptureIntent()
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        startActivity(intent)
    }

    // 处理授权结果,初始化捕获组件
    fun handleScreenCaptureResult(resultCode: Int, data: Intent?) {
        if (resultCode == RESULT_OK && data != null) {
            mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data)
            val displayMetrics = resources.displayMetrics
            val width = displayMetrics.widthPixels
            val height = displayMetrics.heightPixels
            
            imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1)
            mediaProjection?.createVirtualDisplay(
                "ScreenCapture",
                width, height, displayMetrics.densityDpi,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                imageReader?.surface, null, null
            )

            // 监听截图完成事件
            imageReader?.setOnImageAvailableListener({ reader ->
                val image = reader.acquireLatestImage()
                image?.let {
                    val bitmap = imageToBitmap(it)
                    sendScreenshotToActivity(bitmap)
                    it.close()
                }
            }, Handler(Looper.getMainLooper()))
        }
    }

    // Image转Bitmap工具方法
    private fun imageToBitmap(image: Image): Bitmap {
        val planes = image.planes
        val buffer = planes[0].buffer
        val pixelStride = planes[0].pixelStride
        val rowStride = planes[0].rowStride
        val rowPadding = rowStride - pixelStride * image.width
        
        val bitmap = Bitmap.createBitmap(
            image.width + rowPadding / pixelStride,
            image.height, Bitmap.Config.ARGB_8888
        )
        bitmap.copyPixelsFromBuffer(buffer)
        return Bitmap.createBitmap(bitmap, 0, 0, image.width, image.height)
    }

    // 广播传递截图到原Activity
    private fun sendScreenshotToActivity(bitmap: Bitmap) {
        val intent = Intent("com.example.SCREENSHOT_CAPTURED")
        val bundle = Bundle()
        bundle.putParcelable("screenshot", bitmap)
        intent.putExtras(bundle)
        sendBroadcast(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        mediaProjection?.stop()
        imageReader?.close()
        windowManager.removeView(composeView)
    }

    // 其余原有代码保持不变...
}

3. 修改悬浮窗点击事件

更新FloatingWindowScreen.kt,绑定点击回调:

@Composable 
fun floatWindowScreen(onCaptureClick: () -> Unit) { 
    Box( 
        modifier = Modifier 
            .clip(RoundedCornerShape(10.dp)) 
            .width(200.dp) 
            .height(50.dp) 
            .background(Color.LightGray)
            .clickable { onCaptureClick() }, 
        contentAlignment = Alignment.Center 
    ) { 
        Text(text = "点击截图并在原应用显示") 
    } 
}

在FloatingWindowService的drawFloatingWindow中传入回调:

private fun drawFloatingWindow() { 
    composeView = ComposeView(this) 
    composeView.setContent { 
        floatWindowScreen {
            if (Settings.canDrawOverlays(this@FloatingWindowService)) {
                startScreenCapture()
            } else {
                // 引导开启悬浮窗权限
                val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                startActivity(intent)
            }
        } 
    } 
    // 原有生命周期绑定代码保持不变...
}

4. MainActivity接收并显示截图

修改MainActivity.kt,注册广播接收器处理截图:

class MainActivity : ComponentActivity() { 
    private val TAG = "MainActivity" 
    private var screenshotBitmap: Bitmap? = null
    private val screenshotReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            intent?.getParcelableExtra<Bitmap>("screenshot")?.let {
                screenshotBitmap = it
                invalidate()
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState) 
        registerReceiver(screenshotReceiver, IntentFilter("com.example.SCREENSHOT_CAPTURED"))
        
        setContent { 
            MisInfoTheme { 
                Column( 
                    Modifier 
                        .fillMaxSize() 
                        .padding(16.dp), 
                    verticalArrangement = Arrangement.Center, 
                    horizontalAlignment = Alignment.CenterHorizontally 
                ) { 
                    Button(onClick = { 
                        if (Settings.canDrawOverlays(this@MainActivity)) {
                            startService(Intent(this@MainActivity, FloatingWindowService::class.java))
                        } else {
                            val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
                            startActivity(intent)
                        }
                    }) { 
                        Text(text = "打开悬浮窗") 
                    }

                    // 显示截图
                    screenshotBitmap?.let {
                        Image(
                            bitmap = it.asImageBitmap(),
                            contentDescription = "截图",
                            modifier = Modifier.fillMaxWidth().padding(top = 16.dp)
                        )
                    }
                } 
            } 
        } 
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(screenshotReceiver)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 17:42:36