Android非当前Activity桌面及第三方应用截图技术咨询
嘿,我来帮你搞定这个需求!要实现「截取Android桌面+其他应用,同时排除自身截图程序」的功能,MediaProjection API是官方推荐的最佳方案——它能捕获整个屏幕的内容,而非单个Activity的视图。下面是具体的实现步骤和关键技巧:
实现Android全屏桌面截图(排除自身App)
1. 先申请屏幕捕获权限
首先必须向用户请求屏幕捕获授权,这是系统强制要求的隐私权限:
private lateinit var mediaProjectionManager: MediaProjectionManager private var mediaProjection: MediaProjection? = null private var virtualDisplay: VirtualDisplay? = null private var imageReader: ImageReader? = null private const val REQUEST_CODE_SCREEN_CAPTURE = 1001 // 初始化MediaProjection管理器 mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager // 点击截图按钮时先发起权限请求(如果还没授权) screenshotBtn.setOnClickListener { startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_CODE_SCREEN_CAPTURE) }
然后在onActivityResult中接收授权结果:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_SCREEN_CAPTURE && resultCode == Activity.RESULT_OK) { mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data!!) // 授权成功后,先隐藏自身UI再执行截图 startCaptureWithUIFix() } }
2. 核心:捕获屏幕并排除自身App
MediaProjection会捕获整个屏幕,所以要排除自己的截图程序,关键是截图前临时隐藏自身所有可见UI(比如你的Chat Head、截图按钮),给系统一点时间刷新UI后再截图,完成后恢复UI。
private fun startCaptureWithUIFix() { // 第一步:隐藏自身UI(Chat Head、按钮等) hideChatHead() screenshotBtn.visibility = View.GONE // 延迟300ms执行截图(给系统足够时间更新屏幕内容) Handler(Looper.getMainLooper()).postDelayed({ startScreenCapture() }, 300) } private fun startScreenCapture() { // 获取屏幕尺寸 val metrics = resources.displayMetrics val screenWidth = metrics.widthPixels val screenHeight = metrics.heightPixels val screenDensity = metrics.densityDpi // 创建ImageReader用于接收屏幕画面 imageReader = ImageReader.newInstance(screenWidth, screenHeight, PixelFormat.RGBA_8888, 1) // 创建虚拟显示器,把屏幕内容投射到ImageReader的Surface virtualDisplay = mediaProjection?.createVirtualDisplay( "ScreenCapture", screenWidth, screenHeight, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader?.surface, null, null ) // 监听ImageReader的画面数据,处理截图 imageReader?.setOnImageAvailableListener({ reader -> val image = reader.acquireLatestImage() image?.let { // 把Image对象转成Bitmap val bitmap = convertImageToBitmap(it) // 调用你已有的保存截图代码 saveScreenshot(bitmap) // 释放资源 it.close() stopCaptureResources() // 恢复自身UI显示 showChatHead() screenshotBtn.visibility = View.VISIBLE } }, Handler(Looper.getMainLooper())) } // 工具方法:将Image转为Bitmap private fun convertImageToBitmap(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) // 裁剪掉多余的padding,得到准确的屏幕截图 return Bitmap.createBitmap(bitmap, 0, 0, image.width, image.height) } // 释放资源,避免内存泄漏 private fun stopCaptureResources() { virtualDisplay?.release() mediaProjection?.stop() imageReader?.close() }
3. 关键注意事项
- Android版本兼容:MediaProjection API从Android 5.0(API 21)开始支持,目前绝大多数设备都满足要求;
- 后台限制:Android 12+禁止后台启动MediaProjection,所以截图操作必须在前台触发(比如用户点击按钮);
- 资源释放:一定要在截图完成后释放
VirtualDisplay、MediaProjection和ImageReader,否则会造成内存泄漏; - UI隐藏时机:延迟300ms是经验值,你可以根据自己的UI复杂度调整这个时间,确保自身UI完全隐藏后再截图。
把这些代码和你已有的保存截图逻辑结合起来,就能完美实现你要的功能啦!
内容的提问来源于stack exchange,提问作者Adeel Faisal




