Android 屏幕录制与本地保存完整实现指南

365淘房APP官网下载 2025-08-09 10:04:26 阅读: 2803

本文将详细介绍如何在 Android 应用中实现屏幕录制功能,并将录制的视频保存到本地存储。我们将涵盖从权限获取到最终视频保存的完整流程,包括关键代码实现。

一、实现原理概述

Android 屏幕录制主要依赖以下几个核心组件:

MediaProjection:获取屏幕内容的入口,出于安全和隐私的考虑,每次录制前,系统都会弹出一个对话框,明确请求用户的授权。

MediaProjectionManager: 管理MediaProjection实例

VirtualDisplay:虚拟显示设备,将屏幕内容投射到编码器

MediaRecorder: 负责录制和编码

由于屏幕录制通常是持续性任务,即使用户切换到其他应用或返回桌面,录制也应继续。因此,我们必须将录制逻辑放置在前台服务 (Foreground Service) 中。 这不仅能防止我们的应用在后台被系统终止,还能通过一个持续的通知告知用户,屏幕正在被录制,保证了操作的透明性。

环境准备

1.配置 Manifest 文件

js

复制代码

android:exported="false"

android:foregroundServiceType="mediaProjection"/>

2.请求用户授权

我们无法直接请求屏幕捕获权限。相反,我们必须通过 MediaProjectionManager 创建一个 Intent,然后启动这个 Intent 来显示一个系统对话框。

js

复制代码

val mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager

// 使用 ActivityResultLauncher 来处理返回结果

val screenCaptureLauncher = registerForActivityResult(

ActivityResultContracts.StartActivityForResult()

) { result ->

if (result.resultCode == Activity.RESULT_OK) {

val serviceIntent = Intent(this, ScreenCaptureService::class.java).apply {

action = "START"

putExtra("resultCode", result.resultCode)

putExtra("data", result.data)

}

startForegroundService(serviceIntent)

} else {

// 用户拒绝了授权

Toast.makeText(this, "需要屏幕捕获权限才能录制", Toast.LENGTH_SHORT).show()

}

}

// 点击录屏按钮调用

fun startScreenCapture() {

screenCaptureLauncher.launch(mediaProjectionManager.createScreenCaptureIntent())

}

3.创建并实现前台服务

kotlin

复制代码

class ScreenCaptureService : Service() {

private lateinit var mediaProjection: MediaProjection

private lateinit var virtualDisplay: VirtualDisplay

private lateinit var mediaRecorder: MediaRecorder

private lateinit var callBack:MediaProjection.Callback

private var currentVideoUri: Uri? = null

companion object {

const val RESULT_CODE = "resultCode"

const val RESULT_DATA = "resultData"

const val NOTIFICATION_ID = 1001

const val CHANNEL_ID = "screen_record_channel"

}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

val resultCode = intent?.getIntExtra(RESULT_CODE, 0) ?: 0

val resultData = intent?.getParcelableExtra(RESULT_DATA)

if (resultCode != 0 && resultData != null) {

startRecording(resultCode, resultData)

}

return START_STICKY

}

private fun startRecording(resultCode: Int, resultData: Intent) {

//创建通知并启动前台服务

startForeground(NOTIFICATION_ID, createNotification())

// 获取mediaProjection实例

val projectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager

mediaProjection = projectionManager.getMediaProjection(resultCode, resultData)

val fileName = "ScreenRecord_${System.currentTimeMillis()}.mp4"

// 配置 MediaRecorder,设置视频源、输出格式、编码器、文件路径等。

mediaRecorder = MediaRecorder().apply {

setVideoSource(MediaRecorder.VideoSource.SURFACE)

setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)

setOutputFile(getOutputFileDescriptor(applicationContext,fileName))

setVideoEncoder(MediaRecorder.VideoEncoder.H264)

setVideoSize(1080, 1920) // 根据实际需求调整

setVideoFrameRate(30)

prepare()

}

callBack = object : MediaProjection.Callback() {

override fun onStop() {

}

}

// 注册回调

mediaProjection.registerCallback(callBack, null)

// 创建一个虚拟显示 (VirtualDisplay),并将其渲染的画面输出到 MediaRecorder 的 Surface 上

virtualDisplay = mediaProjection.createVirtualDisplay(

"ScreenRecorder",

1080, 1920, resources.displayMetrics.densityDpi,

DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,

mediaRecorder.surface, null, null

)

// 开始录制

mediaRecorder.start()

}

private fun createNotification(): Notification {

createNotificationChannel()

return NotificationCompat.Builder(this, CHANNEL_ID)

.setContentTitle("屏幕录制中")

.setContentText("正在录制您的屏幕操作")

.setSmallIcon(R.mipmap.ic_launcher)

.setPriority(NotificationCompat.PRIORITY_LOW)

.build()

}

private fun createNotificationChannel() {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

val channel = NotificationChannel(

CHANNEL_ID,

"屏幕录制",

NotificationManager.IMPORTANCE_LOW

).apply {

description = "屏幕录制服务正在运行"

}

(getSystemService(NOTIFICATION_SERVICE) as NotificationManager)

.createNotificationChannel(channel)

}

}

// 设置视频保存路径

private fun getOutputFileDescriptor(context: Context, fileName: String): FileDescriptor? {

val contentValues = ContentValues().apply {

put(MediaStore.Video.Media.DISPLAY_NAME, fileName)

put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {

put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/")

put(MediaStore.Video.Media.IS_PENDING, 1)

}

}

val collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)

val itemUri = context.contentResolver.insert(collection, contentValues)

currentVideoUri = itemUri

return if (itemUri != null) {

context.contentResolver.openFileDescriptor(itemUri, "w")?.fileDescriptor

} else {

null

}

}

override fun onDestroy() {

mediaProjection.unregisterCallback(callBack)

super.onDestroy()

stopRecording()

}

// 停止录制并释放资源

private fun stopRecording() {

mediaRecorder.apply {

stop()

reset()

release()

}

virtualDisplay.release()

if (::mediaProjection.isInitialized) {

mediaProjection.stop()

}

// 将录制的视频保存到本地

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && currentVideoUri != null) {

val contentValues = ContentValues().apply {

put(MediaStore.Video.Media.IS_PENDING, 0)

}

contentResolver.update(currentVideoUri!!, contentValues, null, null)

}

}

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

}

总结

本文利用Android屏幕录制API完成了基本的屏幕录制功能,后续还可以结合音视频编码将屏幕录制的数据利用RTMP推流到服务端实现录屏直播功能。