【Android】录屏功能实现 您所在的位置:网站首页 android实现录屏功能 【Android】录屏功能实现

【Android】录屏功能实现

2023-08-18 04:10| 来源: 网络整理| 查看: 265

概述Android录屏功能实现有很多种方式,这里记录最常见的一种,用Android自带的apk来实现,即使用MediaProjection来实现。

工具类实现Android的录屏功能,需要用到一些工具类:MediaProjection,MediaProjectionManager,MediaRecoder,VirtualDisplay,DisplayMetrics等。

作用MediaProjection用于屏幕采集。

MediaProjectionManager用于创建MediaProjection。

MediaRecoder用于屏幕录制。

VirtualDisplay用于创建虚拟屏幕。

DisplayMetrics用于获取屏幕参数。

过程权限申请需要使用录屏功能,必须请求读写和录像录音权限。

静态权限申请——Manifest.xml

Android 6.0以上还需要动态申请权

//权限检查,连接录屏服务 public void checkPermission() { //调用检查权限接口进行权限检查 if ((ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) != 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(MainActivity.this,"请同意必须的应用权限,否则无法正常使用该功能!", Toast.LENGTH_SHORT).show(); }else if(grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED){ //如果结果都存在,两个权限都申请成功,连接服务,启动录屏 connectService(); } } } 连接服务

注意点:记住一定要在Manifest中声明自定义的Service,不然后面得到的Service对象都是NULL。

Manifest.xml

 

 

连接服务

//连接服务 public void connectService(){ //通过intent为中介绑定Service,会自动create Intent intent = new Intent(this,ScreenRecordService.class); //绑定过程连接,选择绑定模式 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(); //获取到服务,初始化录屏管理者 mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE); //通过管理者,创建录屏请求,通过Intent Intent captureIntent = mediaProjectionManager.createScreenCaptureIntent(); //将请求码作为标识一起发送,调用该接口,需有返回方法 startActivityForResult(captureIntent,REQUEST_CODE); } @Override public void onServiceDisconnected(ComponentName componentName) { //连接失败 Toast.makeText(MainActivity.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录屏 //从发送获得的数据和结果中获取该工具 mediaProjection = mediaProjectionManager.getMediaProjection(resultCode,data); //将该工具给Service,并一起传过去需要录制的屏幕范围的参数 if(screenRecordService != null){ screenRecordService.setMediaProjection(mediaProjection); screenRecordService.setConfig(metrics.widthPixels,metrics.heightPixels,metrics.densityDpi); } } } 开始录制

获得了返回的信息,将参数都发送过去后,开始录制

  //服务的两个主要逻辑 //开始录屏 public boolean startRecord() { //首先判断是否有录屏工具以及是否在录屏 if (mediaProjection == null || running) { return false; } //有录屏工具,没有在录屏,就进行录屏 //初始化录像机,录音机Recorder initRecorder(); //根据获取的屏幕参数创建虚拟的录屏屏幕 createVirtualDisplay(); //本来不加异常也可以,但是这样就不知道是否start成功 //万一start没有成功,但是running置为true了,就产生了错误也无提示 //提示开始录屏了,但是并没有工作 try{ //准备工作都完成了,可以开始录屏了 mediaRecorder.start(); //标志位改为正在录屏 running = true; return true; }catch (Exception e){ e.printStackTrace(); //有异常,start出错,没有开始录屏,弹出提示 Toast.makeText(this,"开启失败,没有开始录屏",Toast.LENGTH_SHORT).show(); //标志位变回没有录屏的状态 running = false; return false; } }

 

录制需要录像机

//初始化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(18); //初始化完成,进入准备阶段,准备被使用 //截获异常,处理 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 = Environment.getExternalStorageDirectory() .getAbsolutePath() + "/" + "录屏文件" + "/"; //创建该文件夹 File file = new File(rootDir); if (!file.exists()) { //如果该文件夹不存在 if (!file.mkdirs()) { //如果没有创建成功 return null; } } //创建成功了,返回该目录 return rootDir; } else { //不是音视频文件,不保存,无路径 return null; } } 停止录制

停止录制,一切设备还原

      //停止录屏 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; } //无异常,保存成功 Toast.makeText(this,"录屏结束,保存成功!",Toast.LENGTH_SHORT).show(); return true; } 总结

总的来说,只有两个文件,一个Activity,一个Service,一个负责给用户操作,一个负责后台实现逻辑。

MainActivity

package com.example.screencap; 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.os.Bundle; import android.os.IBinder; 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; public class MainActivity extends AppCompatActivity implements View.OnClickListener { //请求码 private final static int REQUEST_CODE = 101; //权限请求码 private final static int PERMISSION_REQUEST_CODE = 1101; //录屏工具 MediaProjectionManager mediaProjectionManager; MediaProjection mediaProjection; //开始按钮,停止按钮 Button btn_start_recorder; Button btn_stop_recorder; //获取录屏范围参数 DisplayMetrics metrics; //录屏服务 ScreenRecordService screenRecordService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //实例化按钮 btn_start_recorder = findViewById(R.id.btn_start_recorder); btn_stop_recorder = findViewById(R.id.btn_stop_recorder); //点击按钮,请求录屏 btn_start_recorder.setOnClickListener(this); btn_stop_recorder.setOnClickListener(this); } //权限检查,连接录屏服务 public void checkPermission() { //调用检查权限接口进行权限检查 if ((ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) != 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(MainActivity.this,"请同意必须的应用权限,否则无法正常使用该功能!", Toast.LENGTH_SHORT).show(); }else if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED){ //如果结果都存在,两个权限都申请成功,连接服务,启动录屏 Toast.makeText(MainActivity.this,"权限申请成功,用户同意!",Toast.LENGTH_SHORT).show(); connectService(); } } } //连接服务 public void connectService(){ //通过intent为中介绑定Service,会自动create Intent intent = new Intent(this,ScreenRecordService.class); //绑定过程连接,选择绑定模式 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(); //获取到服务,初始化录屏管理者 mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE); //通过管理者,创建录屏请求,通过Intent Intent captureIntent = mediaProjectionManager.createScreenCaptureIntent(); //将请求码作为标识一起发送,调用该接口,需有返回方法 startActivityForResult(captureIntent,REQUEST_CODE); } @Override public void onServiceDisconnected(ComponentName componentName) { //连接失败 Toast.makeText(MainActivity.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录屏 //从发送获得的数据和结果中获取该工具 mediaProjection = mediaProjectionManager.getMediaProjection(resultCode,data); //将该工具给Service,并一起传过去需要录制的屏幕范围的参数 if(screenRecordService != null){ screenRecordService.setMediaProjection(mediaProjection); //获取录屏屏幕范围参数 metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); screenRecordService.setConfig(metrics.widthPixels,metrics.heightPixels,metrics.densityDpi); } } } @Override //点击事件 public void onClick(View view) { switch (view.getId()) { case R.id.btn_start_recorder: //点击请求录屏后,第一件事,检查权限 checkPermission(); //参数传过去以后,如果在录制,提示 if(screenRecordService != null && screenRecordService.isRunning()){ Toast.makeText(MainActivity.this,"当前正在录屏,请不要重复点击哦!",Toast.LENGTH_SHORT).show(); } else if(screenRecordService != null && !screenRecordService.isRunning()){ //没有录制,就开始录制,弹出提示,返回主界面开始录制 screenRecordService.startRecord(); //返回主界面开始录制 setToBackground(); } else if(screenRecordService == null){ connectService(); } break; case R.id.btn_stop_recorder: if(screenRecordService != null && !screenRecordService.isRunning()){ //没有在录屏,无法停止,弹出提示 Toast.makeText(MainActivity.this,"您还没有录屏,无法停止,请先开始录屏吧!",Toast.LENGTH_SHORT).show(); }else if(screenRecordService != null && screenRecordService.isRunning()){ //正在录屏,点击停止,停止录屏 screenRecordService.stopRecord(); } break; } } //返回主界面开始录屏,相当于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); } }

activity_main

  

 

ScreenRecordService

package com.example.screencap; import android.app.Service; 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.os.Binder; import android.os.Environment; import android.os.HandlerThread; import android.os.IBinder; import android.widget.Toast; import androidx.annotation.Nullable; import java.io.File; import java.io.IOException; public class ScreenRecordService extends Service { //录屏工具MediaProjection private MediaProjection mediaProjection; //录像机MediaRecorder private MediaRecorder mediaRecorder; //用于录屏的虚拟屏幕 private VirtualDisplay virtualDisplay; //声明录制屏幕的宽高像素 private int width; private int height; // private int width = 720; // private int height = 1080; private int dpi; //标志,判断是否正在录屏 private boolean running; //声明视频存储路径 private String videoPath = ""; // @Override // public void onCreate() { // super.onCreate(); HandlerThread serviceThread = new HandlerThread("service_thread", android.os.Process.THREAD_PRIORITY_BACKGROUND); serviceThread.start(); // running = false; // } @Override public void onCreate() { super.onCreate(); } // @Override // public int onStartCommand(Intent intent, int flags, int startId) { // return START_STICKY; // } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { 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(MediaProjection projection) { mediaProjection = projection; } //设置需要录制的屏幕参数 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; } //有录屏工具,没有在录屏,就进行录屏 //初始化录像机,录音机Recorder initRecorder(); //根据获取的屏幕参数创建虚拟的录屏屏幕 createVirtualDisplay(); //本来不加异常也可以,但是这样就不知道是否start成功 //万一start没有成功,但是running置为true了,就产生了错误也无提示 //提示开始录屏了,但是并没有工作 try{ //准备工作都完成了,可以开始录屏了 mediaRecorder.start(); //标志位改为正在录屏 running = true; 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; } //无异常,保存成功 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(18); //初始化完成,进入准备阶段,准备被使用 //截获异常,处理 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 = Environment.getExternalStorageDirectory() .getAbsolutePath() + "/" + "录屏文件" + "/"; //创建该文件夹 File file = new File(rootDir); if (!file.exists()) { //如果该文件夹不存在 if (!file.mkdirs()) { //如果没有创建成功 return null; } } //创建成功了,返回该目录 return rootDir; } else { //不是音视频文件,不保存,无路径 return null; } } }

 

————————————————版权声明:本文为CSDN博主「小叮当不懒」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_46546793/article/details/123279152



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有