Android實現(xiàn)屏幕錄制與本地保存功能的完整指南
一、實現(xiàn)原理概述
Android 屏幕錄制主要依賴以下幾個核心組件:
- MediaProjection:獲取屏幕內(nèi)容的入口,出于安全和隱私的考慮,每次錄制前,系統(tǒng)都會彈出一個對話框,明確請求用戶的授權(quán)。
- MediaProjectionManager: 管理MediaProjection實例
- VirtualDisplay:虛擬顯示設(shè)備,將屏幕內(nèi)容投射到編碼器
- MediaRecorder: 負責(zé)錄制和編碼
由于屏幕錄制通常是持續(xù)性任務(wù),即使用戶切換到其他應(yīng)用或返回桌面,錄制也應(yīng)繼續(xù)。因此,我們必須將錄制邏輯放置在前臺服務(wù) (Foreground Service) 中。 這不僅能防止我們的應(yīng)用在后臺被系統(tǒng)終止,還能通過一個持續(xù)的通知告知用戶,屏幕正在被錄制,保證了操作的透明性。
二、環(huán)境準(zhǔn)備
1.配置 Manifest 文件
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 運行前臺服務(wù)的必要權(quán)限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- 聲明用于屏幕錄制的 Service -->
<service android:name=".ScreenCaptureService"
android:exported="false"
android:foregroundServiceType="mediaProjection"/>
2.請求用戶授權(quán)
我們無法直接請求屏幕捕獲權(quán)限。相反,我們必須通過 MediaProjectionManager 創(chuàng)建一個 Intent,然后啟動這個 Intent 來顯示一個系統(tǒng)對話框。
val mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
// 使用 ActivityResultLauncher 來處理返回結(jié)果
val screenCaptureLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
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 {
// 用戶拒絕了授權(quán)
Toast.makeText(this, "需要屏幕捕獲權(quán)限才能錄制", Toast.LENGTH_SHORT).show()
}
}
// 點擊錄屏按鈕調(diào)用
fun startScreenCapture() {
screenCaptureLauncher.launch(mediaProjectionManager.createScreenCaptureIntent())
}
3.創(chuàng)建并實現(xiàn)前臺服務(wù)
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) {
//創(chuàng)建通知并啟動前臺服務(wù)
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,設(shè)置視頻源、輸出格式、編碼器、文件路徑等。
mediaRecorder = MediaRecorder().apply {
setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setOutputFile(getOutputFileDescriptor(applicationContext,fileName))
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
setVideoSize(1080, 1920) // 根據(jù)實際需求調(diào)整
setVideoFrameRate(30)
prepare()
}
callBack = object : MediaProjection.Callback() {
override fun onStop() {
}
}
// 注冊回調(diào)
mediaProjection.registerCallback(callBack, null)
// 創(chuàng)建一個虛擬顯示 (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 = "屏幕錄制服務(wù)正在運行"
}
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager)
.createNotificationChannel(channel)
}
}
// 設(shè)置視頻保存路徑
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 stopRecording() {
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
}
三、總結(jié)
本文利用Android屏幕錄制API完成了基本的屏幕錄制功能,后續(xù)還可以結(jié)合音視頻編碼將屏幕錄制的數(shù)據(jù)利用RTMP推流到服務(wù)端實現(xiàn)錄屏直播功能。
到此這篇關(guān)于Android實現(xiàn)屏幕錄制與本地保存功能的完整指南的文章就介紹到這了,更多相關(guān)Android屏幕錄制與本地保存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android列表實現(xiàn)(3)_自定義列表適配器思路及實現(xiàn)代碼
Android 自定義列表適配器會提供很多的便利;下面的例子為使用自定義的列表適配器來顯示列表,感興趣的朋友可以研究下2012-12-12
android 關(guān)于webview 加載h5網(wǎng)頁開啟定位的方法
今天小編就為大家分享一篇android 關(guān)于webview 加載h5網(wǎng)頁開啟定位的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07
詳解Android開發(fā)中Activity的四種launchMode
這篇文章主要介紹了Android開發(fā)中Activity的四種launchMode,launchMode主要用于控制多個Activity間的跳轉(zhuǎn),需要的朋友可以參考下2016-03-03
Android之IphoneTreeView帶組指示器的ExpandableListView效果
在正在顯示的最上面的組的標(biāo)簽位置添加一個和組視圖完全一樣的視圖,作為組標(biāo)簽。這個標(biāo)簽的位置要隨著列表的滑動不斷變化,以保持總是顯示在最上方,并且該消失的時候就消失2013-06-06
Android編程四大組件之BroadcastReceiver(廣播接收者)用法實例
這篇文章主要介紹了Android編程四大組件之BroadcastReceiver(廣播接收者)用法,結(jié)合實例形式較為詳細的分析了BroadcastReceiver的功能.定義,用法及相關(guān)使用技巧,需要的朋友可以參考下2016-01-01
Android實現(xiàn)仿美團、順豐快遞數(shù)據(jù)加載效果
本片文章教給大家用Android實現(xiàn)美團和順豐快遞APP的數(shù)據(jù)加載的動畫效果,有興趣的朋友跟著學(xué)習(xí)嘗試下吧。2017-12-12

