安卓实现录屏app

安卓给app提供了:

MediaRecorder:针对某个Surface录制成视频

MediaProjection:创建一个虚拟屏幕显示与手机屏幕相同的内容

使用MediaRecorder对MediaProjection创建的虚拟屏幕进行录制即可实现录屏的功能。

安卓还要求虚拟屏幕创建在一个前台服务中,因此我们可以首先实现一个ScreenRecorderActivity提供开始录屏和结束录屏的按钮,其布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/start_record_screen_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="@string/start_record_screen" />
    <Button
        android:id="@+id/stop_record_screen_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="@string/stop_record_screen" />

</LinearLayout>

相应的ScreenRecorderActivity代码如下:

class ScreenRecorderActivity : AppCompatActivity() {

    private lateinit var mediaProjectionManager: MediaProjectionManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_screen_recorder)

        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), 0)
        mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager

        findViewById<Button>(R.id.start_record_screen_button).setOnClickListener {
            val captureIntent = mediaProjectionManager.createScreenCaptureIntent()
            startActivityForResult(captureIntent, 0)
        }

        findViewById<Button>(R.id.stop_record_screen_button).setOnClickListener {
            stopService(Intent(this, ScreenRecorderService::class.java))
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        for (gr in grantResults) {
            if (gr != PackageManager.PERMISSION_GRANTED) {
                finish()
                break
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        ScreenRecorderService.actionStart(this, resultCode, data)
    }
}

根据mediaProjectionManager.createScreenCaptureIntent调用startActivityForResult,然后在onActivityResult方法中拿到resultCode和data才能创建虚拟屏幕。

接下来实现相应的前台ScreenRecorderService,其在AndroidManifest中需要加上android:foregroundServiceType=“mediaProjection”,其代码如下:

class ScreenRecorderService : Service() {

    private var started = false
    private lateinit var saveDir: File
    private lateinit var saveFile: File
    private lateinit var mediaProjectionManager: MediaProjectionManager
    private lateinit var mediaRecorder: MediaRecorder
    private lateinit var mediaProjection: MediaProjection
    private lateinit var virtualDisplay: VirtualDisplay

    override fun onCreate() {
        super.onCreate()
        saveDir = File(externalMediaDirs[0], "ScreenRecorder")
        if (!saveDir.mkdirs()) Log.e(TAG, "Failed to create directory: $saveDir")
        mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
        startForegroundNotification()
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        if (started) return super.onStartCommand(intent, flags, startId)
        started = true
        saveFile = File(saveDir, SimpleDateFormat("yyyyMMddHHmmss").format(Date()) + ".mp4")

        val resultCode = intent.getIntExtra("resultCode", -1)
        val resultData = intent.getParcelableExtra<Intent>("resultData")

        val width = resources.displayMetrics.widthPixels
        val height = resources.displayMetrics.heightPixels
        val dpi = resources.displayMetrics.densityDpi

        mediaRecorder = MediaRecorder().apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            setVideoSource(MediaRecorder.VideoSource.SURFACE)
            setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
            setVideoEncoder(MediaRecorder.VideoEncoder.H264)
            setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
            setVideoSize(width, height)
            setVideoFrameRate(30)
            setVideoEncodingBitRate(10000000)
            setOutputFile(saveFile)
            prepare()
            start()
        }

        mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, resultData!!)
        virtualDisplay = mediaProjection.createVirtualDisplay("scScreenRecorder", width, height, dpi,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder.surface, null, null)

        return super.onStartCommand(intent, flags, startId)
    }

    private fun startForegroundNotification() {
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        val channel = NotificationChannel("ScreenRecorder", "ScreenRecorder", NotificationManager.IMPORTANCE_HIGH)
        notificationManager.createNotificationChannel(channel)

        val intent = Intent(this, ScreenRecorderActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
        val notification = Notification.Builder(this, "ScreenRecorder")
                .setSmallIcon(R.drawable.ic_screen_recorder)
                .setContentTitle("ScreenRecorder")
                .setContentText("Recording...")
                .setContentIntent(pendingIntent)
                .build()
        startForeground(123, notification)
    }

    override fun onBind(intent: Intent): IBinder {
        TODO("Return the communication channel to the service.")
    }

    override fun onDestroy() {
        super.onDestroy()
        mediaRecorder.stop()
        mediaRecorder.release()
        mediaProjection.stop()
        virtualDisplay.release()
        Toast.makeText(this, "$saveFile saved.", Toast.LENGTH_LONG).show()
        stopForeground(true)
    }

    companion object {
        private val TAG = ScreenRecorderService::class.simpleName!!

        fun actionStart(context: Context, resultCode: Int, resultData: Intent?) {
            val intent = Intent(context, ScreenRecorderService::class.java)
            intent.putExtra("resultCode", resultCode)
            intent.putExtra("resultData", resultData)
            context.startForegroundService(intent)
        }
    }
  1. 首先在onActivityResult方法中拿到的resultCode和data通过Intent的extra传给了Service的onStartCommand方法,用来通过mediaProjectionManager.getMediaProjection方法创建MediaProjection对象。

  2. 然后通过构造器创建MediaRecorder对象并配置VideoSource为SURFACE,并完成相应的其它配置后,调用start方法开始录制视频。

  3. 最后通过mediaProjection.createVirtualDisplay创建虚拟屏幕并将mediaRecorder.surface传给虚拟屏幕即可实现录屏。

最后记得在AndroidManifest中申请需要的权限:

    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

就这样实现了安卓录屏app。创建虚拟屏幕录屏的底层原理见录屏流程

源代码地址:https://github.com/SSSxCCC/SCApp

总结

1 需要在一个前台服务中录屏;

2 录屏时会创建一个虚拟显示屏VirtualDisplay。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值