如何借助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
相关产品推荐
相关产品推荐

