Android 实现屏幕录制

  1. 添加权限和服务声明

    xml 复制代码
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <service
                android:name=".ScreenService"
                android:enabled="true"
                android:exported="true"
                android:foregroundServiceType="mediaProjection"></service>
  2. 创建屏幕录制的 Service

    kotlin 复制代码
    import android.app.*
    import android.content.Context
    import android.content.Intent
    import android.graphics.BitmapFactory
    import android.hardware.display.DisplayManager
    import android.hardware.display.VirtualDisplay
    import android.media.CamcorderProfile
    import android.media.MediaRecorder
    import android.media.projection.MediaProjection
    import android.media.projection.MediaProjectionManager
    import android.os.Build
    import android.os.IBinder
    import android.util.DisplayMetrics
    import android.util.Log
    import android.view.WindowManager
    import androidx.core.app.NotificationCompat
    import java.io.IOException
    
    class ScreenService : Service() {
    
        private var mContext:Context?=null
        private var projectionManager:MediaProjectionManager?=null
        private var mMediaProjection:MediaProjection?=null
        override fun onBind(intent: Intent): IBinder {
            TODO("Return the communication channel to the service.")
        }
    
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            mContext = this
            var resultCode = intent?.getIntExtra("resultCode", -1)
            var path = intent?.getStringExtra("path")
            var resultData: Intent? = intent?.getParcelableExtra("data")
            startNotification();
            projectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
            mMediaProjection = resultCode?.let { resultData?.let { it1 -> projectionManager?.getMediaProjection(it, it1) } }
            path?.let { startRecording(it) }
            return super.onStartCommand(intent, flags, startId)
        }
    
        private var NOTIFICATION_CHANNEL_ID="id";
        private var NOTIFICATION_CHANNEL_NAME="channel";
        private var NOTIFICATION_CHANNEL_DESC="desc";
        private fun startNotification() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                var notificationIntent = Intent(mContext, ScreenService::class.java)
                var pendingIntent: PendingIntent?=null
                pendingIntent = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
                    PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
                } else {
                    PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_ONE_SHOT);
                }
                var  notificationBuilder = mContext?.let {
                    NotificationCompat.Builder(it, NOTIFICATION_CHANNEL_ID)
                        .setLargeIcon(BitmapFactory.decodeResource(mContext!!.resources, R.drawable.ic_launcher_foreground))
                        .setSmallIcon(R.drawable.ic_launcher_foreground)
                        .setContentTitle("start record")
                        .setContentText("=== start record ===")
                        .setContentIntent(pendingIntent)
                };
                var  notification = notificationBuilder?.build();
                var  channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
                channel.description = NOTIFICATION_CHANNEL_DESC;
                var  notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
                notificationManager.createNotificationChannel(channel)
                startForeground(1, notification);
            }
        }
    
    
        private var isScreenRecoding = false
        private var  mMediaRecorder: MediaRecorder?=null
        private var mVirtualDisplay: VirtualDisplay? = null
        private fun startRecording(filePath:String) {
            if (!isScreenRecoding){
                try {
                    // 创建 MediaRecorder 并设置参数
                    val metrics = DisplayMetrics()
                    val windowManager: WindowManager = mContext?.getSystemService(WINDOW_SERVICE) as WindowManager
                    windowManager.defaultDisplay.getMetrics(metrics)
                    mMediaRecorder = MediaRecorder()
                    mMediaRecorder?.setVideoSource(MediaRecorder.VideoSource.SURFACE)
                    mMediaRecorder?.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
                    mMediaRecorder?.setOutputFile(filePath)
                    mMediaRecorder?.setVideoSize(metrics.widthPixels, metrics.heightPixels)
                    mMediaRecorder?.setVideoEncoder(MediaRecorder.VideoEncoder.H264)
                    mMediaRecorder?.setVideoEncodingBitRate(1920*1080 * 3)
                    mMediaRecorder?.setVideoFrameRate(30)
    
                    // 准备 MediaRecorder
                    mMediaRecorder?.prepare()
    
                    // 创建 VirtualDisplay 以获取屏幕内容
                    mVirtualDisplay = mMediaProjection?.createVirtualDisplay(
                        "ScreenRecord",
                        metrics.widthPixels, metrics.heightPixels, metrics.densityDpi,
                        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                        mMediaRecorder?.surface, null, null
                    )
    
                    // 开始录制
                    mMediaRecorder?.start()
                    isScreenRecoding = true
                    Log.i(ScreenUtil.TAG,"开始录屏 $filePath")
                } catch (e: IOException) {
                    Log.e(ScreenUtil.TAG, "录屏失败: " + e.message)
                    e.printStackTrace()
                }
            }
        }
        public fun stopRecording() {
            if (isScreenRecoding) {
                try {
                    // 停止录制
                    mMediaRecorder?.stop()
                    mMediaRecorder?.reset()
                    mMediaRecorder?.release()
                    mMediaRecorder = null
    
                    // 停止 VirtualDisplay
                    mVirtualDisplay?.release()
    
                    // 停止 MediaProjection
                    mMediaProjection?.stop()
                    Log.i(ScreenUtil.TAG,"结束录屏")
                } catch (e: Exception) {
                    Log.e(ScreenUtil.TAG, "停止录屏失败: " + e.message)
                    e.printStackTrace()
                }
                isScreenRecoding = false
            }
        }
    
        override fun onDestroy() {
            stopRecording()
            super.onDestroy()
        }
    }
  3. 启动和停止录制

    kotlin 复制代码
    private var mProjectionManager: MediaProjectionManager? = null
    var screenService:Intent?=null
    fun start(){
    	mProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
    	// 请求录屏权限
    	startActivityForResult(mProjectionManager?.createScreenCaptureIntent(), 500);
    }
    fun stop(){
    	stopService(screenService)
    }
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 500){
            screenService = Intent(mConext, ScreenService::class.java)
            screenService?.let {
                it.putExtra("resultCode", resultCode);
                it.putExtra("data", data);
                it.putExtra("path", "screen.mp4");
                startForegroundService(it)
            }
        }
    }
相关推荐
雨白35 分钟前
深入理解广播机制 (BroadcastReceiver)
android
婵鸣空啼4 小时前
GD图像处理与SESSiON
android
sunly_5 小时前
Flutter:导航固定背景图,滚动时导航颜色渐变
android·javascript·flutter
用户2018792831676 小时前
简单了解android.permission.MEDIA_CONTENT_CONTROL权限
android
_一条咸鱼_6 小时前
Android Runtime类卸载条件与资源回收策略(29)
android·面试·android jetpack
顾林海6 小时前
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
android·面试·性能优化
砖厂小工6 小时前
Now In Android 精讲 8 - Gradle build-logic 现代构建逻辑组织方式
android
玲小珑6 小时前
Auto.js 入门指南(八)高级控件与 UI 自动化
android·前端
harry235day6 小时前
Compose 带动画的待办清单列表页
android·android jetpack
vocal6 小时前
我的安卓第一课:四大组件之一Activity及其组件RecyclerView
android