1 申请权限
先申请存储权限,注意两种存储权限的区别
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
public void checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
new RxPermissions(this).requestEach(arr).subscribe(new Consumer<Permission>() {
@Override
public void accept(Permission permission) throws Throwable {
if (permission.granted) {
} else if (permission.shouldShowRequestPermissionRationale) {
} else {
// 安卓11及以上 Manifest.permission.MANAGE_EXTERNAL_STORAGE默认是拒绝且不再提醒的
if (permission.name ==Manifest.permission.MANAGE_EXTERNAL_STORAGE ) {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
}
}
});
}else {
//调用检查权限接口进行权限检查
if ((ContextCompat.checkSelfPermission(ScreenRecordActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED)) {
//如果没有权限,获取权限
//调用请求权限接口进行权限申请
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE);
} else {
//有权限,连接录屏服务,进行录屏
//connectService();
}
}
}
初始化获取MediaProjectionManager
通过调用getSystemService方法获取MediaProjectionManager的实例
mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
申请屏幕录制的权限
private void startRecord() {
//没有录制,就开始录制,弹出提示
mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
//通过管理者,创建录屏请求,通过Intent
Intent captureIntent = mediaProjectionManager.createScreenCaptureIntent();
//将请求码作为标识一起发送,调用该接口,需有返回方法
startActivityForResult(captureIntent, REQUEST_CODE);
}
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//首先判断请求码是否一致,结果是否ok
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
//录屏请求成功,使用工具MediaProjection录屏
//从发送获得的数据和结果中获取该工具
if (screenRecordService != null) {
//从发送获得的数据和结果中获取该工具
screenRecordService.setMediaProjection(mediaProjectionManager, resultCode, data);
if (screenRecordService.startRecord()) {
btn_start_recorder.setText("结束");
}
}
}
}
然后在service里 mediaRecorder进行初始化以及start
public boolean startRecord() {
//首先判断是否有录屏工具以及是否在录屏
if (mediaProjection == null || running) {
return false;
}
initRecorder();
//根据获取的屏幕参数创建虚拟的录屏屏幕
createVirtualDisplay();
//提示开始录屏了,但是并没有工作
try{
//准备工作都完成了,可以开始录屏了
mediaRecorder.start();
/* //标志位改为正在录屏
notification = createNotification();
startForeground(1100, notification);*/
running = true;
Toast.makeText(this,"开始录屏",Toast.LENGTH_SHORT).show();
return true;
}catch (Exception e){
e.printStackTrace();
//有异常,start出错,没有开始录屏,弹出提示
Toast.makeText(this,"开启失败,没有开始录屏",Toast.LENGTH_SHORT).show();
//标志位变回没有录屏的状态
running = false;
return false;
}
}
public void initRecorder() {
//新建Recorder
mediaRecorder = new MediaRecorder();
//设置录像机的一系列参数
//设置音频来源
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//设置视频来源
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
//设置视频格式为mp4
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//设置视频存储地址,返回的文件夹下的命名为当前系统事件的文件
videoPath = getSaveDirectory() + System.currentTimeMillis() + ".mp4";
//保存在该位置
mediaRecorder.setOutputFile(videoPath);
//设置视频大小,清晰度
mediaRecorder.setVideoSize(width, height);
//设置视频编码为H.264
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
//设置音频编码
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
//设置视频码率
mediaRecorder.setVideoEncodingBitRate(2 * 1920 * 1080);
mediaRecorder.setVideoFrameRate(30);
//初始化完成,进入准备阶段,准备被使用
//截获异常,处理
try {
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
//异常提示
Toast.makeText(this,
"Recorder录像机prepare失败,无法使用,请重新初始化!",
Toast.LENGTH_SHORT).show();
}
}
public void createVirtualDisplay() {
//虚拟屏幕通过MediaProjection获取,传入一系列传过来的参数
//可能创建时会出错,捕获异常
try {
virtualDisplay = mediaProjection.createVirtualDisplay("VirtualScreen", width, height, dpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder.getSurface(), null, null);
}catch (Exception e){
e.printStackTrace();
Toast.makeText(this,"virtualDisplay创建录屏异常,请退出重试!",Toast.LENGTH_SHORT).show();
}
}
这个简易版的就可以启动屏幕录屏了,下面是两个完整类的代码
ScreenRecordService类,这个类清单文件注意加下前台服务,为的就是适配安卓10以上的通知栏
<service android:name="com.example.myapplication.services.ScreenRecordService" android:enabled="true" android:foregroundServiceType="mediaProjection"/>
package com.example.myapplication.services;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import com.blankj.utilcode.util.ScreenUtils;
import com.example.myapplication.R;
import java.io.File;
import java.io.IOException;
public class ScreenRecordService extends Service {
int SERVICE_ID= 1100;
Context context;
//录屏工具MediaProjection
private MediaProjection mediaProjection;
//录像机MediaRecorder
private MediaRecorder mediaRecorder;
//用于录屏的虚拟屏幕
private VirtualDisplay virtualDisplay;
//声明录制屏幕的宽高像素
private int width = ScreenUtils.getScreenWidth();
private int height = ScreenUtils.getScreenHeight();
// private int width = 720;
// private int height = 1080;
private int dpi=ScreenUtils.getScreenDensityDpi();
//标志,判断是否正在录屏
private boolean running;
//声明视频存储路径
private String videoPath = "";
Notification notification;
@Override
public void onCreate() {
super.onCreate();
context=this;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
// 增加关闭文件
stopRecord();
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
// 增加关闭文件
return super.onUnbind(intent);
}
//返回的Binder
public class ScreenRecordBinder extends Binder {
//返回Service的方法
public ScreenRecordService getScreenRecordService() {
return ScreenRecordService.this;
}
}
@Nullable
@Override
//返回一个Binder用于通信,需要一个获取Service的方法
public IBinder onBind(Intent intent) {
return new ScreenRecordBinder();
}
//设置录屏工具MediaProjection
public void setMediaProjection(MediaProjectionManager mediaProjectionManager, int resultCode,Intent data) {
// 将服务设置为前台服务
startForeground(SERVICE_ID, createNotification());
//获取到服务,初始化录屏管理者
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode,data);
}
//设置需要录制的屏幕参数
public void setConfig(int width, int height, int dpi) {
this.width = width;
this.height = height;
this.dpi = dpi;
}
//返回判断,判断其是否在录屏
public boolean isRunning() {
return running;
}
//服务的两个主要逻辑
//开始录屏
public boolean startRecord() {
//首先判断是否有录屏工具以及是否在录屏
if (mediaProjection == null || running) {
return false;
}
initRecorder();
//根据获取的屏幕参数创建虚拟的录屏屏幕
createVirtualDisplay();
//提示开始录屏了,但是并没有工作
try{
//准备工作都完成了,可以开始录屏了
mediaRecorder.start();
/* //标志位改为正在录屏
notification = createNotification();
startForeground(1100, notification);*/
running = true;
Toast.makeText(this,"开始录屏",Toast.LENGTH_SHORT).show();
return true;
}catch (Exception e){
e.printStackTrace();
//有异常,start出错,没有开始录屏,弹出提示
Toast.makeText(this,"开启失败,没有开始录屏",Toast.LENGTH_SHORT).show();
//标志位变回没有录屏的状态
running = false;
return false;
}
}
//停止录屏
public boolean stopRecord() {
if (!running) {
//没有在录屏,无法停止
return false;
}
//无论设备是否还原或者有异常,但是确实录屏结束,修改标志位为未录屏
running = false;
//本来加不加捕获异常都可以,但是为了用户体验度,加入会更好
try{
//Recorder停止录像,重置还原,以便下一次使用
mediaRecorder.stop();
mediaRecorder.reset();
//释放virtualDisplay的资源
virtualDisplay.release();
}catch (Exception e){
e.printStackTrace();
//有异常,保存失败,弹出提示
Toast.makeText(this,"录屏出现异常,视频保存失败!",Toast.LENGTH_SHORT).show();
return false;
}finally {
stopForeground(true);
}
//无异常,保存成功
Toast.makeText(this,"录屏结束,保存成功!",Toast.LENGTH_SHORT).show();
return true;
}
//初始化Recorder录像机
public void initRecorder() {
//新建Recorder
mediaRecorder = new MediaRecorder();
//设置录像机的一系列参数
//设置音频来源
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//设置视频来源
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
//设置视频格式为mp4
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//设置视频存储地址,返回的文件夹下的命名为当前系统事件的文件
videoPath = getSaveDirectory() + System.currentTimeMillis() + ".mp4";
//保存在该位置
mediaRecorder.setOutputFile(videoPath);
//设置视频大小,清晰度
mediaRecorder.setVideoSize(width, height);
//设置视频编码为H.264
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
//设置音频编码
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
//设置视频码率
mediaRecorder.setVideoEncodingBitRate(2 * 1920 * 1080);
mediaRecorder.setVideoFrameRate(30);
//初始化完成,进入准备阶段,准备被使用
//截获异常,处理
try {
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
//异常提示
Toast.makeText(this,
"Recorder录像机prepare失败,无法使用,请重新初始化!",
Toast.LENGTH_SHORT).show();
}
}
public void createVirtualDisplay() {
//虚拟屏幕通过MediaProjection获取,传入一系列传过来的参数
//可能创建时会出错,捕获异常
try {
virtualDisplay = mediaProjection.createVirtualDisplay("VirtualScreen", width, height, dpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder.getSurface(), null, null);
}catch (Exception e){
e.printStackTrace();
Toast.makeText(this,"virtualDisplay创建录屏异常,请退出重试!",Toast.LENGTH_SHORT).show();
}
}
//获取存储文件夹的位置
public String getSaveDirectory() {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//如果确认为视频类型,设置根目录,绝对路径下的自定义文件夹中
String rootDir = context.getExternalFilesDir(null)
.getAbsolutePath() + "/" + "录屏文件" + "/";
//创建该文件夹
File file = new File(rootDir);
if (!file.exists()) {
//如果该文件夹不存在/storage/emulated/0/Android/data/com.example.myapplication/files/录屏文件/
if (!file.mkdirs()) {
//如果没有创建成功
return null;
}
}
//创建成功了,返回该目录
return rootDir;
} else {
//不是音视频文件,不保存,无路径
return null;
}
}
// 创建前台服务通知
public Notification createNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("121", "录屏", NotificationManager.IMPORTANCE_LOW);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "121")
.setContentTitle("录屏服务")
.setContentText("正在录制屏幕")
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(NotificationCompat.CATEGORY_SERVICE);
return builder.build();
}
}
ScreenRecordActivity 是activity类
package com.example.myapplication.ui; import android.Manifest; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.media.projection.MediaProjection; import android.media.projection.MediaProjectionManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.provider.Settings; import android.util.DisplayMetrics; import android.view.View; import android.widget.Button; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import com.example.myapplication.R; import com.example.myapplication.services.ScreenRecordService; import com.example.myapplication.utils.L; import com.tbruyelle.rxpermissions3.Permission; import com.tbruyelle.rxpermissions3.RxPermissions; import io.reactivex.rxjava3.functions.Consumer; public class ScreenRecordActivity extends AppCompatActivity implements View.OnClickListener { //请求码 //权限请求码 private final static int PERMISSION_REQUEST_CODE = 1101; //开始按钮,停止按钮 Button btn_start_recorder; // Button btn_stop_recorder; //获取录屏范围参数 DisplayMetrics metrics; private final static int REQUEST_CODE = 101; //录屏工具 private MediaProjectionManager mediaProjectionManager; //录屏服务 ScreenRecordService screenRecordService; boolean aBoolean = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_screenrecord); //实例化按钮 btn_start_recorder = findViewById(R.id.test_button); //点击按钮,请求录屏 btn_start_recorder.setOnClickListener(this); // btn_stop_recorder.setOnClickListener(this); checkPermission(); } String arr[] = {Manifest.permission.MANAGE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}; //权限检查,连接录屏服务 public void checkPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { new RxPermissions(this).requestEach(arr).subscribe(new Consumer<Permission>() { @Override public void accept(Permission permission) throws Throwable { if (permission.granted) { } else if (permission.shouldShowRequestPermissionRationale) { } else { // 安卓11及以上 Manifest.permission.MANAGE_EXTERNAL_STORAGE默认是拒绝且不再提醒的 if (permission.name ==Manifest.permission.MANAGE_EXTERNAL_STORAGE ) { Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); intent.setData(Uri.parse("package:" + getPackageName())); startActivity(intent); } } } }); }else { //调用检查权限接口进行权限检查 if ((ContextCompat.checkSelfPermission(ScreenRecordActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) { //如果没有权限,获取权限 //调用请求权限接口进行权限申请 ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE); } else { //有权限,连接录屏服务,进行录屏 //connectService(); } } } //没有权限,去请求权限后,需要判断用户是否同意权限请求 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { //请求码相同 if (grantResults.length > 0 && (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED)) { //如果结果都存在,但是至少一个没请求成功,弹出提示 Toast.makeText(ScreenRecordActivity.this, "请同意必须的应用权限,否则无法正常使用该功能!", Toast.LENGTH_SHORT).show(); } else if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) { //如果结果都存在,两个权限都申请成功,连接服务,启动录屏 Toast.makeText(ScreenRecordActivity.this, "权限申请成功,用户同意!", Toast.LENGTH_SHORT).show(); //connectService(); } } } private void startRecord() { //没有录制,就开始录制,弹出提示 mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE); //通过管理者,创建录屏请求,通过Intent Intent captureIntent = mediaProjectionManager.createScreenCaptureIntent(); //将请求码作为标识一起发送,调用该接口,需有返回方法 startActivityForResult(captureIntent, REQUEST_CODE); } private void stopRecord() { btn_start_recorder.setText("开始"); screenRecordService.stopRecord(); } //连接服务 public void connectService() { //通过intent为中介绑定Service,会自动create Intent intent = new Intent(this, ScreenRecordService.class); //绑定过程连接,选择绑定模式 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android Q及以上版本需要使用startForegroundService启动前台服务 startForegroundService(intent); } // 在Android Q以下版本可以直接使用bindService启动服务 bindService(intent, serviceConnection, BIND_AUTO_CREATE); } //连接服务成功与否,具体连接过程 //调用连接接口,实现连接,回调连接结果 private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { //服务连接成功,需要通过Binder获取服务,达到Activity和Service通信的目的 //获取Binder ScreenRecordService.ScreenRecordBinder binder = (ScreenRecordService.ScreenRecordBinder) iBinder; //通过Binder获取Service screenRecordService = binder.getScreenRecordService(); startRecord(); } @Override public void onServiceDisconnected(ComponentName componentName) { //连接失败 Toast.makeText(ScreenRecordActivity.this, "录屏服务未连接成功,请重试!", Toast.LENGTH_SHORT).show(); } }; @Override //返回方法,获取返回的信息 protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); //首先判断请求码是否一致,结果是否ok if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) { //录屏请求成功,使用工具MediaProjection录屏 //从发送获得的数据和结果中获取该工具 if (screenRecordService != null) { //从发送获得的数据和结果中获取该工具 screenRecordService.setMediaProjection(mediaProjectionManager, resultCode, data); if (screenRecordService.startRecord()) { btn_start_recorder.setText("结束"); } L.save("阿迪斯发"); } } } @Override //点击事件 public void onClick(View view) { if (view.getId() == R.id.test_button) { checkPermission(); if (screenRecordService == null) { connectService(); return; } //参数传过去以后,如果在录制,提示 if (screenRecordService != null && screenRecordService.isRunning()) { //正在录屏,点击停止,停止录屏 stopRecord(); // Toast.makeText(requireContext(),"当前正在录屏,请不要重复点击哦!",Toast.LENGTH_SHORT).show(); } else { //没有录制,就开始录制,弹出提示 startRecord(); } } } //返回主界面开始录屏,相当于home键 private void setToBackground() { //主页面的Intent Intent home = new Intent(Intent.ACTION_MAIN); //设置清除栈顶的启动模式 home.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); //匹配符号 home.addCategory(Intent.CATEGORY_HOME); //转换界面,隐式匹配,显示调用 startActivity(home); } //当应用结束的时候,需要解除绑定服务,防止造成内存泄漏 @Override protected void onDestroy() { super.onDestroy(); unbindService(serviceConnection); } }
在上面代码运行之前有个应用场景是我的app正在语音通话中,启动录屏 MediaRecorder.start failed:-38异常,后面找到原因是硬件上只能单路录音,不支持多路录音,麦克风有其他线程在使用,所以我在那把音频的设置去掉就好了,确定你的需求场景就可以放出来
// 设备不在通话中,可以开始录制 // mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // //设置音频编码 // mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
翻译
搜索
复制