狂风暴雨默默晴转多云 發表於 2025-7-31 10:10:17

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

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>一、实现原理概述</li><li>二、环境准备</li><ul class="second_class_ul"><li>1.配置 Manifest 文件</li><li>2.请求用户授权</li><li>3.创建并实现前台服务</li></ul><li>三、总结</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>一、实现原理概述</h2>
<p>Android 屏幕录制主要依赖以下几个核心组件:</p>
<ol><li><strong>MediaProjection</strong>:获取屏幕内容的入口,出于安全和隐私的考虑,每次录制前,系统都会弹出一个对话框,明确请求用户的授权。</li><li><strong>MediaProjectionManager</strong>: 管理MediaProjection实例</li><li><strong>VirtualDisplay</strong>:虚拟显示设备,将屏幕内容投射到编码器</li><li><strong>MediaRecorder</strong>: 负责录制和编码</li></ol>
<p>由于屏幕录制通常是持续性任务,即使用户切换到其他应用或返回桌面,录制也应继续。因此,我们必须将录制逻辑放置在<strong>前台服务 (Foreground Service)</strong> &nbsp;中。 这不仅能防止我们的应用在后台被系统终止,还能通过一个持续的通知告知用户,屏幕正在被录制,保证了操作的透明性。</p>
<p class="maodian"></p><h2>二、环境准备</h2>
<p class="maodian"></p><h3>1.配置 Manifest 文件</h3>
<div class="jb51code"><pre class="brush:xml;">&lt;uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /&gt;
&lt;!-- 运行前台服务的必要权限 --&gt;
&lt;uses-permission android:name="android.permission.FOREGROUND_SERVICE" /&gt;

&lt;!-- 声明用于屏幕录制的 Service --&gt;
&lt;service android:name=".ScreenCaptureService"
    android:exported="false"
    android:foregroundServiceType="mediaProjection"/&gt;
</pre></div>
<p class="maodian"></p><h3>2.请求用户授权</h3>
<p>我们无法直接请求屏幕捕获权限。相反,我们必须通过&nbsp;MediaProjectionManager&nbsp;创建一个&nbsp;Intent,然后启动这个&nbsp;Intent&nbsp;来显示一个系统对话框。</p>
<div class="jb51code"><pre class="brush:java;">val mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager

// 使用 ActivityResultLauncher 来处理返回结果
val screenCaptureLauncher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { result -&gt;
    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())
}
</pre></div>
<p class="maodian"></p><h3>3.创建并实现前台服务</h3>
<div class="jb51code"><pre class="brush:java;">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&lt;Intent&gt;(RESULT_DATA)

      if (resultCode != 0 &amp;&amp; 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 &gt;= 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 &gt;= 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 &gt;= Build.VERSION_CODES.Q &amp;&amp; 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
}
</pre></div>
<p class="maodian"></p><h2>三、总结</h2>
<p>本文利用Android屏幕录制API完成了基本的屏幕录制功能,后续还可以结合音视频编码将屏幕录制的数据利用RTMP推流到服务端实现录屏直播功能。</p>
<p>到此这篇关于Android实现屏幕录制与本地保存功能的完整指南的文章就介绍到这了,更多相关Android屏幕录制与本地保存内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Android实现屏幕录制功能</li><li>Android 5.0+ 屏幕录制实现的示例代码</li><li>Android5.1 取消录制屏幕跳出的权限对话框问题</li><li>Android 录制手机屏幕视频生成GIF图片实例详解</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Android实现屏幕录制与本地保存功能的完整指南