Android實現(xiàn)一鍵錄屏功能(附源碼)
一、項目介紹
在 Android 5.0(API 21)及以上版本,系統(tǒng)提供了 MediaProjection API,允許應用在用戶授權下錄制屏幕內容并輸出到視頻文件?;诖?,我們可以實現(xiàn)一個 一鍵錄屏 功能,無需 ROOT,也無需系統(tǒng)簽名。完整功能包含:
申請錄屏權限:使用 MediaProjectionManager 觸發(fā)系統(tǒng)授權對話框
屏幕采集與編碼:結合 MediaProjection、VirtualDisplay 與 MediaRecorder(或 MediaCodec + Muxer)進行音視頻同步錄制
后臺服務管理:將錄屏流程封裝在 Service 或 Foreground Service 中,確保在應用切后臺時仍可錄制
用戶交互:在應用內通過按鈕控制錄屏的啟動、暫停、停止,并提供錄制時長實時顯示
文件存儲與管理:自動將錄制文件保存到外部存儲或 MediaStore,支持列表預覽與播放
性能與兼容:兼容不同分辨率、適配屏幕方向變化,優(yōu)化視頻碼率與幀率,控制錄屏對系統(tǒng)性能的影響
二、相關技術與原理
| 技術 | 功能 |
|---|---|
| MediaProjection | 提供屏幕內容采集權限,輸出到 VirtualDisplay |
| VirtualDisplay | 將屏幕內容鏡像到指定 Surface(例如 MediaRecorder 的輸入) |
| MediaRecorder | 原生音視頻錄制 API,簡化同步編碼、封裝 AV 文件 |
| MediaCodec + Muxer | 手動編碼與封裝,獲得更細粒度控制(可選高級方案) |
| Service | 后臺管理錄屏任務,結合前臺服務確保錄制不中斷 |
| MediaStore | Android 10+ 存儲協(xié)議,安全寫入公共相冊 |
| Notification | 前臺服務需在通知欄持續(xù)顯示錄制狀態(tài) |
API 要求:最早 API 21,建議應用最小支持 API 23 以上,以便簡化外部存儲和權限管理。
編解碼參數(shù):典型分辨率與幀率組合:1080×1920@30fps、720×1280@30fps;視頻碼率 4–8 Mbps;音頻碼率 128 kbps;
三、系統(tǒng)權限與用戶授權
Manifest 權限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
運行時授權
Android 6.0+ 需請求存儲與麥克風權限
通過 MediaProjectionManager.createScreenCaptureIntent() 發(fā)起系統(tǒng)錄屏授權對話框
四、項目架構與流程
用戶點擊“開始錄制”按鈕
│
▼
MainActivity 發(fā)起錄屏授權 Intent
│
▼
onActivityResult 收到授權 RESULT_OK & data Intent
│
▼
啟動 ScreenRecordService(前臺服務),傳入 data
│
▼
Service 中:
├─ 初始化 MediaRecorder
├─ 獲取 MediaProjection
├─ 創(chuàng)建 VirtualDisplay,Surface 指向 MediaRecorder
├─ 調用 MediaRecorder.start()
│
▼
循環(huán)錄制屏幕與麥克風數(shù)據(jù)
│
▼
用戶點擊“停止錄制”
│
▼
Service 調用 MediaRecorder.stop(), reset(), release()
│
└─ 通知主進程錄制完成,保存文件到 MediaStore
五、環(huán)境配置與依賴
// app/build.gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 34
defaultConfig {
applicationId "com.example.screenrecorder"
minSdk 23
targetSdk 34
}
buildFeatures { viewBinding true }
}
dependencies {
implementation "androidx.appcompat:appcompat:1.6.1"
implementation "androidx.core:core-ktx:1.10.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
}六、完整代碼整合
// =======================================================
// 文件:AndroidManifest.xml
// 描述:權限申請與 Service 聲明
// =======================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.screenrecorder">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<application
android:name=".App"
android:theme="@style/Theme.ScreenRecorder">
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name=".ScreenRecordService"
android:exported="false"/>
</application>
</manifest>
// =======================================================
// 文件:App.kt
// 描述:Application,用于初始化通知渠道
// =======================================================
package com.example.screenrecorder
import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.os.Build
class App : Application() {
companion object { const val CHANNEL_ID = "screen_recorder_channel" }
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager::class.java
.getSystemService(NotificationManager::class.java)
.createNotificationChannel(
NotificationChannel(
CHANNEL_ID,
"Screen Recorder Service",
NotificationManager.IMPORTANCE_LOW
)
)
}
}
}
// =======================================================
// 文件:res/values/themes.xml
// 描述:應用主題
// =======================================================
<resources>
<style name="Theme.ScreenRecorder" parent="Theme.MaterialComponents.DayNight.NoActionBar"/>
</resources>
// =======================================================
// 文件:res/layout/activity_main.xml
// 描述:主界面,控制錄制與播放
// =======================================================
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:padding="24dp">
<LinearLayout
android:layout_width="match_parent" android:layout_height="wrap_content"
android:orientation="vertical" android:gravity="center">
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="開始錄制"/>
<Button
android:id="@+id/btnStop"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="停止錄制"
android:layout_marginTop="16dp"
android:enabled="false"/>
<TextView
android:id="@+id/tvPath"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="錄制路徑:"/>
</LinearLayout>
</FrameLayout>
// =======================================================
// 文件:MainActivity.kt
// 描述:申請權限、發(fā)起錄屏授權、啟動/停止 Service
// =======================================================
package com.example.screenrecorder
import android.app.Activity
import android.content.Intent
import android.media.projection.MediaProjectionManager
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.example.screenrecorder.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var b: ActivityMainBinding
private lateinit var projectionManager: MediaProjectionManager
private var resultCode = Activity.RESULT_CANCELED
private var resultData: Intent? = null
private val reqStorage = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (!granted) finish()
}
private val reqScreen = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { res ->
if (res.resultCode == Activity.RESULT_OK) {
resultCode = res.resultCode
resultData = res.data
startService()
}
}
override fun onCreate(s: Bundle?) {
super.onCreate(s)
b = ActivityMainBinding.inflate(layoutInflater)
setContentView(b.root)
projectionManager = getSystemService(MEDIA_PROJECTION_SERVICE)
as MediaProjectionManager
reqStorage.launch(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
b.btnStart.setOnClickListener {
reqScreen.launch(projectionManager.createScreenCaptureIntent())
}
b.btnStop.setOnClickListener {
stopService(Intent(this, ScreenRecordService::class.java))
}
}
private fun startService() {
val intent = Intent(this, ScreenRecordService::class.java).apply {
putExtra("code", resultCode)
putExtra("data", resultData)
}
startService(intent)
b.btnStart.isEnabled = false
b.btnStop.isEnabled = true
}
}
// =======================================================
// 文件:ScreenRecordService.kt
// 描述:前臺 Service,管理 MediaRecorder 與 MediaProjection
// =======================================================
package com.example.screenrecorder
import android.app.Notification
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.hardware.display.DisplayManager
import android.hardware.display.MediaProjection
import android.hardware.display.MediaProjectionManager
import android.hardware.display.VirtualDisplay
import android.media.MediaRecorder
import android.os.*
import android.provider.MediaStore
import androidx.core.app.NotificationCompat
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
class ScreenRecordService : Service() {
private lateinit var recorder: MediaRecorder
private var projection: MediaProjection? = null
private var virtualDisplay: VirtualDisplay? = null
private lateinit var projectionManager: MediaProjectionManager
private lateinit var outputFile: String
override fun onCreate() {
super.onCreate()
projectionManager = getSystemService(MEDIA_PROJECTION_SERVICE)
as MediaProjectionManager
recorder = MediaRecorder()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val code = intent?.getIntExtra("code", -1) ?: -1
val data = intent?.getParcelableExtra<Intent>("data")
if (code != -1 && data != null) {
initRecorder()
projection = projectionManager.getMediaProjection(code, data)
virtualDisplay = projection?.createVirtualDisplay(
"ScreenRecorder",
recorder.videoWidth, recorder.videoHeight, resources.displayMetrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
recorder.surface, null, null
)
recorder.start()
startForeground(1, buildNotification())
}
return START_NOT_STICKY
}
private fun initRecorder() {
val metrics = resources.displayMetrics
val width = metrics.widthPixels
val height = metrics.heightPixels
recorder.apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
outputFile = createOutputFile()
setOutputFile(outputFile)
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
setVideoSize(width, height)
setVideoFrameRate(30)
setVideoEncodingBitRate(8_000_000)
setOrientationHint(0)
prepare()
}
}
private fun createOutputFile(): String {
val sd = File(getExternalFilesDir(null), "RecordVideos")
if (!sd.exists()) sd.mkdirs()
val name = "SCR_${SimpleDateFormat("yyyyMMdd_HHmmss",
Locale.US).format(Date())}.mp4"
return File(sd, name).absolutePath
}
private fun buildNotification(): Notification {
val pi = PendingIntent.getActivity(
this,0,
Intent(this, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE
)
return NotificationCompat.Builder(this, App.CHANNEL_ID)
.setContentTitle("正在錄屏")
.setSmallIcon(R.drawable.ic_record)
.setContentIntent(pi)
.setOngoing(true)
.build()
}
override fun onDestroy() {
super.onDestroy()
recorder.stop()
recorder.reset()
virtualDisplay?.release()
projection?.stop()
// 通知系統(tǒng)圖庫更新文件
sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).apply {
data = android.net.Uri.fromFile(File(outputFile))
})
}
override fun onBind(intent: Intent?) = null
}
// =======================================================
// 文件:res/drawable/ic_record.xml
// 描述:錄制狀態(tài)圖標(示例可用系統(tǒng)資源)
// =======================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp" android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#F44336"
android:pathData="M12,3C7.03,3 3,7.03 3,12s4.03,9 9,9 9,-4.03 9,-9S16.97,3 12,3zM12,19c-3.86,0 -7,-3.14 -7,-7s3.14,-7 7,-7 7,3.14 7,7 -3.14,7 -7,7z"/>
<circle
android:fillColor="#F44336"
android:cx="12" android:cy="12" android:r="5"/>
</vector>七、代碼解讀
1.MainActivity
- 通過 MediaProjectionManager 發(fā)起錄屏授權,使用 ActivityResultContracts.StartActivityForResult 回調獲取授權 Intent 與結果碼;
- 請求存儲與錄音權限,授權后調用 startService() 啟動 ScreenRecordService;
2.ScreenRecordService
- 在 onStartCommand() 中根據(jù)授權信息獲取 MediaProjection;
- 調用 MediaRecorder 完成音視頻同步編碼配置,并通過 MediaProjection.createVirtualDisplay() 將屏幕內容輸送到 MediaRecorder.getSurface();
- 使用 前臺 Service(startForeground())提高存活優(yōu)先級,在通知欄顯示錄制狀態(tài);
- 在 onDestroy() 中停止錄制并釋放資源,并發(fā)送廣播刷新系統(tǒng)圖庫。
3.MediaRecorder 配置
- setVideoSource(MediaRecorder.VideoSource.SURFACE):將虛擬屏幕 Surface 作為視頻幀輸入;
- 音頻源使用麥克風 AudioSource.MIC;
- 視頻編碼器 H.264,音頻編碼器 AAC;
- 分辨率動態(tài)獲取當前屏幕分辨率,幀率 30 fps,碼率 8 Mbps;
4.文件存儲
- 保存到應用私有外部存儲 getExternalFilesDir("RecordVideos"),Android 10+ 可換成 MediaStore 接口;
- 錄制完成后廣播 ACTION_MEDIA_SCANNER_SCAN_FILE,讓系統(tǒng)圖庫立即識別新文件。
5.通知與圖標
- 使用 ic_record.xml 簡單繪制錄制狀態(tài)圖標,也可替換為自己的矢量資源;
- 通知必須傳入前臺服務渠道 CHANNEL_ID,并設置 setOngoing(true) 鎖定通知。
八、性能優(yōu)化與兼容性考慮
分辨率與碼率自適應:在高端設備上可錄制 1080p 或更高分辨率;在低端或后臺時可降級到 720p 以降低 CPU 負載;
動態(tài)暫停與恢復:通過 MediaRecorder.pause() / resume()(API 24+)實現(xiàn)錄制的中斷與續(xù)錄,防止錄屏過長文件過大;
存儲位置選擇:根據(jù) Android 10+ Scoped Storage 模式改用 MediaStore.Video.Media 插入,可讓用戶在公共相冊中看到錄制視頻;
錄制時長限制:在 Service 中使用 Handler 配合定時邏輯自動停止錄制,避免用戶忘記關閉;
屏幕方向與旋轉處理:使用 setOrientationHint() 傳入正確的旋轉角度,保證錄制后視頻方向正確;可動態(tài)讀取 Display.getRotation() 值;
多路屏幕錄制:如果需要同時錄制應用內部視圖和全屏,可結合 SurfaceView + MediaCodec 自定義編碼合流;
九、擴展功能
預覽與分享:在錄制完成后,跳轉到 播放頁面 預覽視頻,并提供分享到微信、QQ、抖音等鏈接;
水印與濾鏡:在 VirtualDisplay 或 MediaCodec 編碼前,使用 OpenGL ES 在錄制幀上疊加文字與圖片水印,或添加濾鏡效果;
畫中畫:在錄制主屏幕的同時,疊加前置攝像頭小窗口,實現(xiàn)游戲解說或教學場景;
軌跡標記:支持用戶在錄制時繪制屏幕內容,如手繪箭頭、圈注關鍵點,適用于教學與演示;
云端同步:錄制完成后自動上傳到 OSS/S3/騰訊云等對象存儲,結合短視頻處理平臺進行后續(xù)剪輯與分發(fā)。
十、項目總結與落地建議
本文基于 MediaProjection + MediaRecorder 實現(xiàn)了一個完整、可擴展的 Android 錄屏功能,涵蓋:
系統(tǒng)授權與運行時權限管理
前臺 Service 保證長期錄制不中斷
高質量音視頻同步編碼與文件保存
錄制狀態(tài)通知與系統(tǒng)相冊更新
在實際項目中,可結合應用業(yè)務場景,增添更多人性化功能與企業(yè)級需求,如錄制回放剪輯、云端上傳、畫中畫、手勢標注等,打造更具競爭力的錄屏產(chǎn)品。
十一、常見問題解答(FAQ)
Q1:為什么錄制內容全黑或黑屏?
需在 MediaRecorder 配置好 setVideoSource(MediaRecorder.VideoSource.SURFACE) 后再調用 prepare()。
檢查是否正確調用 projection.createVirtualDisplay() 并傳入 recorder.getSurface()。
Q2:錄制文件巨大怎么辦?
降低視頻碼率,或增加壓縮率。使用高效編碼器(H.265)需自行集成 MediaCodec + MediaMuxer。
Q3:如何實現(xiàn)暫停和續(xù)錄?
Android N(API 24)以上,MediaRecorder.pause() 與 resume() 可控制錄制中斷;
舊版本則需停止當前錄制并啟動下一段分片,后期合并分片文件。
Q4:錄制靜音或無聲?
檢查麥克風是否被 setAudioSource() 正確調用,應用是否擁有 RECORD_AUDIO 權限;
部分設備后臺錄音需在 AudioAttributes 中指定前臺模式。
Q5:錄制過程中怎樣顯示時長?
在 Service 中啟動定時器(Handler.postDelayed()),定期通過通知或廣播更新時長到 UI。
到此這篇關于Android實現(xiàn)簡單的錄屏功能(附源碼)的文章就介紹到這了,更多相關Android錄屏內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android SharedPreferences實現(xiàn)記住密碼和自動登錄
這篇文章主要為大家詳細介紹了Android SharedPreferences實現(xiàn)記住密碼和自動登錄,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-05-05
Android Studio 3.x版本 的輸入法遇到的坑及解決方案
前些天把AndroidStudio從2.3.3升級到3.0,遇到了不少坑,其中一個巨坑就是輸入中文不提示的問題,下面給大家分享Android Studio 3.x版本的輸入法遇到的坑及解決方案,一起看看吧2017-11-11
Android?PickerScrollView滑動選擇控件使用方法詳解
這篇文章主要為大家詳細介紹了Android?PickerScrollView滑動選擇控件,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04
Android中TextureView與SurfaceView用法區(qū)別總結
TextureView和SurfaceView都是繼承自View類的,TextureView在Andriod4.0之后才引入的,SurfaceView不能加上動畫、平移、縮放,TextureView可以但有1-3幀的延遲2018-04-04
Android獲取LinearLayout的寬度和高度示例代碼
這篇文章主要介紹了android獲取LinearLayout的寬度和高度,如果想直接獲取在布局文件中定義的組件的寬度和高度,可以直接使用View.getLayoutParams().width和View.getLayoutParams().height,本文結合示例代碼介紹的非常詳細,需要的朋友可以參考下2023-08-08
Android 使用Vitamio打造自己的萬能播放器(9)—— 在線播放 (在線電視)
本文主要介紹Android 使用Vitamio開發(fā)播放器,實現(xiàn)在線電視播放,這里提供效果圖和實例代碼以便大家參考,2016-07-07
Android的WebView與H5前端JS代碼交互的實例代碼
本篇文章主要介紹了Android的WebView與H5前端JS代碼交互的實例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-07-07
Android漲姿勢知識點之你沒用過的BadgeDrawable
現(xiàn)在Android中有許多的應用仿蘋果的在應用圖標上顯示小紅點,下面這篇文章主要給大家介紹了關于Android漲姿勢知識點之你沒用過的BadgeDrawable的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2022-09-09

