基于前台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
相关产品推荐
相关产品推荐

