You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于前台MediaProjection按钮实现Activity外截图的示例请求

实现任意Activity外的MediaProjection截图功能

核心思路

要在Activity外触发截图,必须依赖前台服务(Foreground Service)持有MediaProjection实例,同时通过前台通知的按钮触发截图动作。MediaProjection需要用户授权,且授权后需在后台保持实例可用,前台服务是唯一合法的后台运行方式。

具体实现步骤及示例代码

1. 声明权限与注册服务

在AndroidManifest.xml中添加必要权限并注册前台服务:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" android:minSdkVersion="33" />

<service
    android:name=".ScreenshotService"
    android:foregroundServiceType="mediaProjection" />

2. 请求MediaProjection授权

在Activity中发起授权请求,获取用户允许的MediaProjection权限:

class MainActivity : AppCompatActivity() {
    private val REQUEST_CODE_SCREEN_CAPTURE = 1001

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
        val captureIntent = mediaProjectionManager.createScreenCaptureIntent()
        startActivityForResult(captureIntent, REQUEST_CODE_SCREEN_CAPTURE)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_CODE_SCREEN_CAPTURE && resultCode == RESULT_OK) {
            val serviceIntent = Intent(this, ScreenshotService::class.java).apply {
                putExtra("RESULT_CODE", resultCode)
                putExtra("DATA", data)
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                startForegroundService(serviceIntent)
            } else {
                startService(serviceIntent)
            }
        }
    }
}

3. 实现前台服务(持有MediaProjection并处理截图)

服务中保持MediaProjection实例,创建带截图按钮的前台通知,同时实现截图逻辑:

class ScreenshotService : Service() {
    private lateinit var mediaProjection: MediaProjection
    private var imageReader: ImageReader? = null
    private val screenshotReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action == "ACTION_SCREENSHOT") {
                takeScreenshot()
            }
        }
    }

    override fun onBind(intent: Intent?): IBinder? = null

    override fun onCreate() {
        super.onCreate()
        registerReceiver(screenshotReceiver, IntentFilter("ACTION_SCREENSHOT"))
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val resultCode = intent?.getIntExtra("RESULT_CODE", -1) ?: -1
        val data = intent?.getParcelableExtra<Intent>("DATA")
        if (resultCode != -1 && data != null) {
            val mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
            mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data)
            createForegroundNotification()
            
            val displayMetrics = resources.displayMetrics
            imageReader = ImageReader.newInstance(
                displayMetrics.widthPixels,
                displayMetrics.heightPixels,
                PixelFormat.RGBA_8888,
                1
            )
            mediaProjection.createVirtualDisplay(
                "ScreenshotDisplay",
                displayMetrics.widthPixels,
                displayMetrics.heightPixels,
                displayMetrics.densityDpi,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                imageReader?.surface,
                null,
                null
            )
        }
        return START_STICKY
    }

    private fun createForegroundNotification() {
        val screenshotIntent = Intent("ACTION_SCREENSHOT").apply {
            setPackage(packageName)
        }
        val screenshotPendingIntent = PendingIntent.getBroadcast(
            this,
            0,
            screenshotIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        val notification = NotificationCompat.Builder(this, "SCREENSHOT_CHANNEL")
            .setContentTitle("截图服务")
            .setContentText("点击按钮截取屏幕")
            .setSmallIcon(R.drawable.ic_screenshot)
            .addAction(
                R.drawable.ic_screenshot,
                "截图",
                screenshotPendingIntent
            )
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .build()

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                "SCREENSHOT_CHANNEL",
                "截图服务通道",
                NotificationManager.IMPORTANCE_LOW
            )
            getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
        }

        startForeground(1, notification)
    }

    private fun takeScreenshot() {
        imageReader?.acquireLatestImage()?.let { image ->
            val buffer = image.planes[0].buffer
            val bytes = ByteArray(buffer.remaining())
            buffer.get(bytes)

            val fileName = "Screenshot_${System.currentTimeMillis()}.png"
            val fileUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                MediaStore.Images.Media.insertImage(
                    contentResolver,
                    BitmapFactory.decodeByteArray(bytes, 0, bytes.size),
                    fileName,
                    "App截图"
                )
            } else {
                val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
                val screenshotFile = File(storageDir, fileName)
                FileOutputStream(screenshotFile).use { fos ->
                    BitmapFactory.decodeByteArray(bytes, 0, bytes.size).compress(Bitmap.CompressFormat.PNG, 100, fos)
                }
                screenshotFile.absolutePath
            }

            Toast.makeText(this, "截图已保存:$fileName", Toast.LENGTH_SHORT).show()
            image.close()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(screenshotReceiver)
        mediaProjection.stop()
        imageReader?.close()
    }
}

关键注意事项

  • MediaProjection生命周期:授权后需立即绑定到前台服务,后台随意持有会被系统回收实例。
  • 版本适配:文件存储逻辑需区分Android Q及以上的MediaStore和低版本的文件路径。
  • 权限要求:Android 13+需READ_MEDIA_IMAGES权限,低版本用WRITE_EXTERNAL_STORAGE。
  • PendingIntent标记:Android 12+必须添加FLAG_IMMUTABLE或FLAG_MUTABLE,此处用FLAG_IMMUTABLE即可。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 03:01:21