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

Android Xamarin非Root设备截取系统全屏截图问题求助

Android非Root全局屏幕截取解决方案(API 21+)

首先得纠正一个关键认知:非Root设备下,真正支持全局屏幕捕获(包括桌面、其他应用,甚至自己应用最小化时的屏幕)的功能是从Android 5.0(API 21)开始的,API 19并没有提供官方的非Root全局截图能力——这也是你之前尝试各种方案无效的核心原因之一。

下面是经过验证的完整实现方案,涵盖从权限申请到截图保存的全流程:

核心原理

利用系统提供的MediaProjection API,通过用户明确授权后创建虚拟显示(VirtualDisplay),将系统屏幕的内容实时渲染到ImageReader的Surface上,再从ImageReader中提取图像数据进行处理。

实现步骤

1. 声明必要权限

AndroidManifest.xml中添加权限(如果需要保存截图到外部存储,还要加存储权限):

<!-- Android 10+ 后台截图必须的前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- 仅在Android 9及以下需要外部存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />

2. 请求屏幕捕获授权

首先获取系统服务实例,启动授权Intent让用户确认:

private lateinit var mediaProjectionManager: MediaProjectionManager
private val REQUEST_CODE_SCREEN_CAPTURE = 100

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    
    mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
    
    // 用按钮触发授权流程(你也可以根据自己的业务逻辑触发)
    btnStartCapture.setOnClickListener {
        val captureIntent = mediaProjectionManager.createScreenCaptureIntent()
        startActivityForResult(captureIntent, REQUEST_CODE_SCREEN_CAPTURE)
    }
}

3. 处理授权结果并初始化截图逻辑

在授权回调中获取MediaProjection实例,创建用于捕获画面的ImageReader和虚拟显示:

private var mediaProjection: MediaProjection? = null
private var virtualDisplay: VirtualDisplay? = null
private lateinit var imageReader: ImageReader

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_CODE_SCREEN_CAPTURE && resultCode == RESULT_OK) {
        // 获取授权后的MediaProjection实例
        mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data!!)
        // 初始化图像读取器
        initImageReader()
        // 启动虚拟显示
        startVirtualDisplay()
        // 延迟一小段时间再捕获,确保虚拟显示初始化完成
        Handler(Looper.getMainLooper()).postDelayed({
            captureAndProcessScreen()
        }, 500)
    }
}

private fun initImageReader() {
    val displayMetrics = resources.displayMetrics
    val screenWidth = displayMetrics.widthPixels
    val screenHeight = displayMetrics.heightPixels
    // 选用RGBA_8888格式,方便后续转Bitmap
    imageReader = ImageReader.newInstance(screenWidth, screenHeight, ImageFormat.RGBA_8888, 1)
}

private fun startVirtualDisplay() {
    mediaProjection?.let { projection ->
        virtualDisplay = projection.createVirtualDisplay(
            "GlobalScreenCapture",
            imageReader.width,
            imageReader.height,
            resources.displayMetrics.densityDpi,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
            imageReader.surface,
            null, // 虚拟显示回调可选
            null  // 处理回调的Handler可选
        )
    }
}

4. 捕获屏幕并处理图像

ImageReader中提取图像,转成Bitmap后可以显示或保存:

private fun captureAndProcessScreen() {
    val image = imageReader.acquireLatestImage() ?: return
    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

    // 创建Bitmap并填充像素
    val bitmap = Bitmap.createBitmap(
        image.width + rowPadding / pixelStride,
        image.height,
        Bitmap.Config.ARGB_8888
    )
    bitmap.copyPixelsFromBuffer(buffer)
    
    // 裁剪掉多余的padding(部分设备会有)
    val croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, image.width, image.height)
    bitmap.recycle()

    // 这里可以自定义处理:比如显示到ImageView,或者保存到文件
    ivScreenshotPreview.setImageBitmap(croppedBitmap)
    saveScreenshotToLocal(croppedBitmap)

    // 及时释放资源
    image.close()
    stopVirtualDisplay()
}

private fun saveScreenshotToLocal(bitmap: Bitmap) {
    val saveDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    val saveFile = File(saveDir, "screen_capture_${System.currentTimeMillis()}.png")
    try {
        val outputStream = FileOutputStream(saveFile)
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
        outputStream.flush()
        outputStream.close()
        Toast.makeText(this, "截图已保存:${saveFile.path}", Toast.LENGTH_LONG).show()
    } catch (e: IOException) {
        e.printStackTrace()
        Toast.makeText(this, "截图保存失败", Toast.LENGTH_SHORT).show()
    }
}

5. 清理资源

在Activity销毁或不需要截图时,释放所有占用的系统资源:

private fun stopVirtualDisplay() {
    virtualDisplay?.release()
    mediaProjection?.stop()
}

override fun onDestroy() {
    super.onDestroy()
    stopVirtualDisplay()
}

关键注意事项

  • Android 10+后台限制:如果你的应用需要在后台(比如最小化状态)截图,必须启动前台服务并显示通知,否则系统会直接禁止MediaProjection运行。
  • 权限生命周期MediaProjection的授权是一次性的,设备重启后需要重新向用户请求授权。
  • 内存优化ImageReaderBitmap会占用较多内存,使用后务必及时调用close()recycle()释放,避免内存溢出。
  • 多屏适配:如果设备连接了多个屏幕,可以指定对应的Display ID来捕获特定屏幕的内容。

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

火山引擎 最新活动