开发者

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

目录
  • 一、实现原理概述
  • 二、环境准备
    • 1.配置 Manifest 文件
    • 2.请求用户授权
    • 3.创建并实现前台服务
  • 三、总结

    一、实现原理概述

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

    1. MediaProjection:获取屏幕内容的入口,出于安全和隐私的考虑,每次录制前,系统都会弹出一个对话框,明确请求用户的授权。
    2. MediaProjectionManager: 管理MediaProjection实例
    3. VirtuuETjjalDisplay:虚拟显示设备,将屏幕内容投射到编码器
    4. MediaRecorder: 负责录制和编码

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

    二、环境准备

    1.配置 Manifest 文件

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- 运行前台服务的必要权限 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    
    <!-- 声明用于屏幕录制的 Service --> 
    <service android:name=".ScreenCaptureService"
        android:exported="false"
        android:foregroundServiceType="mediaProjection"/>
    

    2.请求用户授权

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

    val mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
    
    // 使用 ActivityResultLauncher 来处理返回结果
    val screenCaptureLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == Actphpivity.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.创建并实现前台服务

    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<Intent>(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 Media编程客栈ProjectionManager
            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 stop编程客栈Recording() {
            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推流到服务端实现录屏直播功能。

    到此这篇关于Android实现屏幕录制与本地保存功能的完整指南的文章就介绍到这了,更多相关Android屏幕录制与本地保存内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜