不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵!
参考博客: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;
}
}
}
文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文
文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作 导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释: cwy_init/init_123..._达梦数据库导入导出
文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js
文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...
文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure
文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf