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的授权是一次性的,设备重启后需要重新向用户请求授权。 - 内存优化:
ImageReader和Bitmap会占用较多内存,使用后务必及时调用close()和recycle()释放,避免内存溢出。 - 多屏适配:如果设备连接了多个屏幕,可以指定对应的Display ID来捕获特定屏幕的内容。
内容的提问来源于stack exchange,提问作者Kaj




