本文主要是介绍Android实现一键录屏功能(附源码),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《Android实现一键录屏功能(附源码)》在Android5.0及以上版本,系统提供了MediaProjectionAPI,允许应用在用户授权下录制屏幕内容并输出到视频文件,所以本文将基于此实现一个...
一、项目介绍
在 android 5.0(API 21)及以上版本,系统提供了 MediaProjection API,允许应用在用户授权下录制屏幕内容并输出到视频文件。基于此,我们可以实现一个 一键录屏 功能,无需 ROOT,也无需系统签名。完整功能包含:
申请录屏权限:使用 MediaProjectionManager 触发系统授权对话框
屏幕采集与编码:结合 MediaProjection、VirtualDisplay 与 MediaRecorder(或 MediaCodec + Muxer)进行音视频同步录制
后台服务管理:将录屏流程封装在 Service 或 Foreground Service 中,确保在应用切后台时仍可录制
用户交互:在应用内通过按钮控制录屏的启动、暂停、停止,并提供录制时长实时显示
文件存储与管理:自动将录制文件保存到外部存储或 MediaStore,支持列表预览与播放
性能与兼容:兼容不同分辨率、适配屏幕方向变化,优化视频码率与帧率,控制录屏对系统性能的影响
二、相关技术与原理
技术 | 功能 |
---|---|
MediaProjection | 提供屏幕内容采集权限,输出到 VirtualDisplay |
VirtualDisplay | 将屏幕内容镜像到指定 Surface(例如 MediaRecorder 的输入) |
MediaRecorder | 原生音视频录制 API,简化同步编码、封装 AV 文件 |
MediaCodec + Muxer | 手动编码与封装,获得更细粒度控制(可选高级方案) |
Service | 后台管理录屏任务,结合前台服务确保录制不中断 |
MediaStore | Android 10+ 存储协议,安全写入公共相册 |
Notification | 前台服务需在通知栏持续显示录制状态 |
API 要求:最早 API 21,建议应用最小支持 API 23 以上,以便简化外部存储和权限管理。
编解码参数:典型分辨率与帧率组合:1080×1920@30fps、720×1280@30fps;视频码率 4–8 Mbps;音频码率 128 kbps;
三、系统权限与用户授权
Manifest 权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.WRjsITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
运行时授权
Android 6.0+ 需请求存储与麦克风权限
通过 MediaProjectionManager.createScreenCaptureIntent() 发起系统录屏授权对话框
四、项目架构与流程
用户点击“开始录制”按钮
│
▼
MainActivity 发起录屏授权 Intent
│
▼
onActivityResult 收到授权 RESULT_OK & data Intent
│
▼
启动 ScreenRecordService(前台服务),传入 data
│
▼
Service 中:
├─ 初始化 MediaRecorder
├─ 获取 MediaProjection
├─ 创建 VirtualDisplay,Surface 指向 MediaRecorder
├─ 调用 MediaRecorder.start()
│
▼
循环录制屏幕与麦克风数据
│
▼
用户点击“停止录制”
│
▼
Service 调用 MediaRecorder.stop(), reset(), release()
│
└─ 通知主进程录制完成,保存文件到 MediaStore
五、环境配置与依赖
// 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> // ==================================================javascript===== // 文件:App.kt // 描述:Application,用于初始化通知渠道 // ======================================================= package com.example.screenrecorder import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager import android.javascriptos.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 // 描述:申请权限、发起录屏授权、启动/停止 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.NotificationCjavascriptompat 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() // 通知系统图库更新文件 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 // 描述:录制状态图标(示例可用系统资源) // ======================================================= <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 发起录屏授权,使用 ActivityResultContracts.StartActivityForResult 回调获取授权 Intent 与结果码;
- 请求存储与录音权限,授权后调用 startService() 启动 ScreenRecordService;
2.ScreenRecordService
- 在 onStartCommand() 中根据授权信息获取 MediaProjection;
- 调用 MediaRecorder 完成音视频同步编码配置,并通过 MediaProjection.createVirtualDisplay() 将屏幕内容输送到 MediaRecorder.getSurface();
- 使用 前台 Service(startForeground())提高存活优先级,在通知栏显示录制状态;
- 在 onDestroy() 中停止录制并释放资源,并发送广播刷新系统图库。
3.MediaRecorder 配置
- setVideoSource(MediaRecorder.VideoSource.SURFACE):将虚拟屏幕 Surface 作为视频帧输入;
- 音频源使用麦克风 AudioSource.MIC;
- 视频编码器 H.264,音频编码器 AAC;
- 分辨率动态获取当前屏幕分辨率,帧率 30 fps,码率 8 Mbps;
4.文件存储
- 保存到应用私有外部存储 getExternalFilesDir("RecordVideos"),Android 10+ 可换成 MediaStore 接口;
- 录制完成后广播 ACTION_MEDIA_SCANNER_SCAN_FILE,让系统图库立即识别新文件。
5.通知与图标
- 使用 ic_record.xml 简单绘制录制状态图标,也可替换为自己的矢量资源;
- 通知必须传入前台服务渠道 CHANNEL_ID,并设置 setOngoing(true) 锁定通知。
八、性能优化与兼容性考虑
分辨率与码率自适应:在高端设备上可录制 1080p 或更高分辨率;在低端或后台时可降级到 720p 以降低 CPU 负载;
动态暂停与恢复:通过 MediaRecorder.pause() / resume()(API 24+)实现录制的中断与续录,防止录屏过长文件过大;
存储位置选择:根据 Android 10+ Scoped Storage 模式改用 MediaStore.Video.Media 插入,可让用户在公共相册中看到录制视频;
录制时长限制:在 Service 中使用 Handler 配合定时逻辑自动停止录制,避免用户忘记关闭;
屏幕方向与旋转处理:使用 setOrientationHint() 传入正确的旋转角度,保证录制后视频方向正确;可动态读取 Display.getRotation() 值;
多路屏幕录制:如果需要同时录制应用内部视图和全屏,可结合 SurfaceView + MediaCodec 自定义编码合流;
九、扩展功能
预览与分享:在录制完成后,跳转到 播放页面 预览视频,并提供分享到微信、QQ、抖音等链接;
水印与滤镜:在 VirtualDisplay 或 MediaCodec 编码前,使用 OpenGL ES 在录制帧上叠加文字与图片水印,或添加滤镜效果;
画中画:在录制主屏幕的同时,叠加前置摄像头小窗口,实现游戏解说或教学场景;
轨迹标记:支持用户在录制时绘制屏幕内容,如手绘箭头、圈注关键点,适用于教学与演示;
云端同步:录制完成后自动上传到 OSS/S3/腾讯云等对象存储,结合短视频处理平台进行后续剪辑与分发。
十、项目总结与落地建议
本文基于 MediaProjection + MediaRecorder 实现了一个完整、可扩展的 Android 录屏功能,涵盖:
系统授权与运行时权限管理
前台 Service 保arfdSipOM证长期录制不中断
高质量音视频同步编码与文件保存
录制状态通知与系统相册更新
在实际项目中,可结合应用业务场景,增添更多人性化功能与企业级需求,如录制回放剪辑、云端上传、画中画、手势标注等,打造更具竞争力的录屏产品。
十一、常见问题解答(FAQ)
Q1:为什么录制内容全黑或黑屏?
需在 MediaRecorder 配置好 setVideoSource(MediaRecorder.VideoSource.SURFACE) 后再调用 prepare()。
检查是否正确调用 projection.createVirtualDisplay() 并传入 recorder.getSurface()。
Q2:录制文件巨大怎么办?
降低视频码率,或增加压缩率。使用高效编码器(H.265)需自行集成 MediaCodec + MediaMuxer。
Q3:如何实现暂停和续录?
Android N(API 24)以上,MediaRecorder.pause() 与 resume() 可控制录制中断;
旧版本则需停止当前录制并启动下一段分片,后期合并分片文件。
Q4:录制静音或无声?
检查麦克风是否被 setAudioSource() 正确调用,应用是否拥有 RECORD_AUDIO 权限;
部分设备后台录音需在 AudioAttributes 中指定前台模式。
Q5:录制过程中怎样显示时长?
在 Service 中启动定时器(Handler.postDelayed()),定期通过通知或广播更新时长到 UI。
到此这篇关于Android实现简单的录屏功能(附源码)的文章就介绍到这了,更多相关Android录屏内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!
这篇关于Android实现一键录屏功能(附源码)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!