安卓给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)
}
}
-
首先在onActivityResult方法中拿到的resultCode和data通过Intent的extra传给了Service的onStartCommand方法,用来通过mediaProjectionManager.getMediaProjection方法创建MediaProjection对象。
-
然后通过构造器创建MediaRecorder对象并配置VideoSource为SURFACE,并完成相应的其它配置后,调用start方法开始录制视频。
-
最后通过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。