不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵!
参考博客:Android实现录屏MediaProjection以及相关异常解决
参考GitHub:github中最容易理解的Android录屏的版本
Android
录屏功能实现有很多种方式,这里记录最常见的一种,用Android
自带的apk
来实现,即使用MediaProjection
来实现。
实现Android
的录屏功能,需要用到一些工具类:MediaProjection
,MediaProjectionManager
,MediaRecoder
,VirtualDisplay
,DisplayMetrics
等。
MediaProjection
用于屏幕采集。
MediaProjectionManager
用于创建MediaProjection
。
MediaRecoder
用于屏幕录制。
VirtualDisplay
用于创建虚拟屏幕。
DisplayMetrics
用于获取屏幕参数。
需要使用录屏功能,必须请求读写和录像录音权限。
Manifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.RECORD_AUDIO"/>
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
<service android:name=".ScreenRecordService"/>
//连接服务
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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_start_recorder"
android:layout_width="200dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:layout_marginTop="200dp"
android:background="@drawable/btn_start_recorder"
android:text="Start Recorder"
android:textSize="25dp" />
<Button
android:id="@+id/btn_stop_recorder"
android:layout_width="200dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:layout_marginTop="50dp"
android:background="@drawable/btn_stop_recorder"
android:text="Stop Recorder"
android:textSize="25dp" />
</LinearLayout>
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;
}
}
}
文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别
文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具
文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量
文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置
文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖
文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...
文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序
文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码
文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型
文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件
文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令
文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线