You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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,所以截图操作必须在前台触发(比如用户点击按钮);
  • 资源释放:一定要在截图完成后释放VirtualDisplayMediaProjectionImageReader,否则会造成内存泄漏;
  • UI隐藏时机:延迟300ms是经验值,你可以根据自己的UI复杂度调整这个时间,确保自身UI完全隐藏后再截图。

把这些代码和你已有的保存截图逻辑结合起来,就能完美实现你要的功能啦!

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

火山引擎 最新活动