这里使用Java语言编写实现,完整代码如下:
文件 AndroidMainfest.xml 的主要配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.demoapp">
<!-- for 屏幕录制 -->
<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.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true">
<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>
<activity
android:name=".ScreenRecordingActivity"
android:exported="false"
android:launchMode="singleTask"></activity>
<service
android:name=".ScreenRecordingService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="mediaProjection" />
</application>
</manifest>
文件activity_screen_recording.xml的内容
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity"
tools:ignore="MissingConstraints">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingTop="0dp">
<!-- 注: SurfaceView不能设置background属性 -->
<!-- <SurfaceView
android:id="@+id/surface_view"
android:layout_width="fill_parent"
android:layout_height="80dp" /> -->
<TextView
android:id="@+id/textview_recording_info"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_marginTop="20dp"
android:layout_gravity="center"
android:textColor="#333333"
android:textSize="20sp"
android:textStyle="bold"
android:text="--"
android:maxLines="5" />
<Button
android:id="@+id/btn_recording_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="开始录制" />
<Button
android:id="@+id/btn_recording_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="停止录制" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
文件ScreenRecordingActivity.java的完整代码
package com.example.demoapp;
import android.Manifest;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.example.myandroidnfcapp.R;
/**
* @Function: 屏幕录制视频。
* @Author: ChengJh。
* @Date: 2023/09/06。
* @Description: https://blog.csdn.net/qq_46546793/article/details/123279152 和 https://blog.csdn.net/weixin_42602900/article/details/128340037 。
*/
public class ScreenRecordingActivity extends AppCompatActivity {
private static final String TAG = ScreenRecordingActivity.class.getSimpleName();
//录屏服务
private ScreenRecordingService mService;
private int mServiceStatus = ScreenRecordingService.statusInit;
private boolean clickedStart = false;
private ActivityResultLauncher activityLauncher;
private TextView textViewInfo;
private boolean prepared = false;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen_recording);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle("录屏管理页面");
}
textViewInfo = findViewById(R.id.textview_recording_info);
Button btnStart = (Button) findViewById(R.id.btn_recording_start);
btnStart.setOnClickListener(view -> {
startRecord();
});
Button btnStop = (Button) findViewById(R.id.btn_recording_stop);
btnStop.setOnClickListener(view -> {
stopRecord();
});
//注: registerForActivityResult()方法, 只能在onCreate()中注册, onStart()之后就不能注册了。。
activityLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), o -> {
System.out.println(TAG + "_registerForActivityResult_callback_" + o.getResultCode());
if (o.getResultCode() == Activity.RESULT_OK) {
actionAfterConfirmAgreed(o.getResultCode(), o.getData());
}
});
}
@Override
public void onStart() {
super.onStart();
}
@Override
public void onResume() {
super.onResume();
/** 在Activity的六个核心回调 onCreate()、onStart()、onResume()、onPause()、onStop() 和 onDestroy() 中,
* onStart()、onResume()、onPause()、onStop() 会在 onRestart() 之后再次触发。
* 因此, 有的任务如果放在其中某个函数内触发, 可能需要加个标记变量控制一下。
*/
if (!prepared) {
prepared = true;
checkPermission();
}
}
@Override
public void onDestroy() {
/** 页面销毁的时候, 可以不停止录屏 */
// stopRecord();
if (mServiceStatus >= ScreenRecordingService.statusServiceConnected) {
unbindService(serviceConnection);
}
super.onDestroy();
}
// 标题栏返回按钮事件。
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
//this.finish();
this.onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
// @Override
// //返回方法, 获取返回的信息。
// protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
// super.onActivityResult(requestCode, resultCode, data);
// System.out.println(TAG + "_onActivityResult");
// //首先判断请求码是否一致, 结果是否ok 。
// if (requestCode == ScreenRecordingService.requestCode && resultCode == RESULT_OK) {
// actionAfterUserAgree(resultCode, data);
// }
// }
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
System.out.println(TAG + "_onRequestPermissionsResult");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && requestCode == ScreenRecordingService.requestCodeForPermisssion) {
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
return;
}
}
connectService();
}
}
/**
* 权限申请
*/
private void checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String[] permissions = new String[]{
Manifest.permission.RECORD_AUDIO,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
};
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, ScreenRecordingService.requestCodeForPermisssion);
return;
}
}
}
connectService();
}
//连接服务。
public void connectService() {
System.out.println(TAG + "_connectService");
//通过intent为中介绑定Service, 会自动创建。
Intent intent = new Intent(this, ScreenRecordingService.class);
//绑定过程连接, 选择绑定模式。
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
//连接服务成功与否, 具体连接过程。
//调用连接接口, 实现连接, 回调连接结果。
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
System.out.println(TAG + "_ServiceConnection_onServiceConnected");
//服务连接成功, 需要通过Binder获取服务, 达到Activity和Service通信的目的
ScreenRecordingService.ScreenRecordBinder binder = (ScreenRecordingService.ScreenRecordBinder) iBinder;
//通过Binder获取Service
mService = binder.getScreenRecordService();
mService.setListener(new ScreenRecordingService.ScreenRecordingServiceListener() {
@Override
public void onPrepareComplete() {
if (clickedStart && mService.startRecord()) {
textViewInfo.setText("正在录屏");
}
}
@Override
public void onRecordingFinish(String videoPath) {
textViewInfo.setText("视频文件路径: " + videoPath);
}
});
if (mService.isRunning()) {
System.out.println(TAG + "_ServiceConnection_RunAlready");
mServiceStatus = ScreenRecordingService.statusConfirmAgreed;
textViewInfo.setText("正在录屏");
if (clickedStart) {
Toast.makeText(ScreenRecordingActivity.this, "已在录屏服务中", Toast.LENGTH_SHORT).show();
}
return;
}
mServiceStatus = ScreenRecordingService.statusServiceConnected;
if (clickedStart) {
actionAfterServiceConnected();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
//连接失败。
Toast.makeText(ScreenRecordingActivity.this, "录屏服务未连接成功", Toast.LENGTH_SHORT).show();
}
};
private void actionAfterConfirmAgreed(int resultCode, @Nullable Intent data) {
mServiceStatus = ScreenRecordingService.statusConfirmAgreed;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (mService != null) {
//获取录屏屏幕范围参数。
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
mService.setConfig(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
Intent intent = new Intent(this, ScreenRecordingService.class);
intent.putExtra("code", resultCode);
intent.putExtra("data", data);
startForegroundService(intent);
} else {
System.out.println(TAG + "_onActivityResult_exception");
}
}
}
private void actionAfterServiceConnected() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//获取到服务, 初始化录屏管理者。
MediaProjectionManager projectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
//通过管理者, 创建录屏请求, 通过Intent。
Intent captureIntent = projectionManager.createScreenCaptureIntent();
//将请求码作为标识一起发送, 调用该接口, 需有返回方法。
//startActivityForResult(captureIntent, ScreenRecordingService.requestCode);
activityLauncher.launch(captureIntent);
}
}
private void nextActionByStatus() {
switch (mServiceStatus) {
case ScreenRecordingService.statusInit:
//点击请求录屏时, 第一件事, 检查权限。
checkPermission();
break;
case ScreenRecordingService.statusPermissionOK:
connectService();
break;
case ScreenRecordingService.statusServiceConnected:
actionAfterServiceConnected();
break;
default:
if (mService != null) {
if (mService.startRecord()) {
textViewInfo.setText("正在录屏");
}
}
break;
}
}
private void startRecord() {
System.out.println(TAG + "_startRecord_" + (null == mService));
clickedStart = true;
if (mService != null && mService.isRunning()) {
//如果在录制, 弹出提示。
Toast.makeText(ScreenRecordingActivity.this, "当前正在录屏, 请不要重复点击哦", Toast.LENGTH_SHORT).show();
} else {
//如果不在录制, 就开启录制。
nextActionByStatus();
}
}
private void stopRecord() {
clickedStart = false;
if (null == mService || !mService.isRunning()) {
//没有录屏, 无需停止, 弹出提示。
Toast.makeText(ScreenRecordingActivity.this, "还没有录屏, 无需停止", Toast.LENGTH_SHORT).show();
} else {
//停止录屏。
mService.stopRecord();
}
}
}
文件 ScreenRecordingService.java 的完整代码
package com.example.demoapp;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
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.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.example.myandroidnfcapp.MainActivity;
import com.example.myandroidnfcapp.R;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
public class ScreenRecordingService extends Service {
private static final String TAG = ScreenRecordingService.class.getSimpleName();
public static final int statusInit = 0;
public static final int statusPermissionOK = 1;
public static final int statusServiceConnected = 2;
public static final int statusConfirmAgreed = 3;
public static final int requestCodeForPermisssion = 110;
public static final int requestCode = 111;
private MediaProjectionManager mediaProjectionManager;
//录屏工具MediaProjection。
private MediaProjection mediaProjection;
//录像机MediaRecorder。
private MediaRecorder mediaRecorder;
//用于录屏的虚拟屏幕。
private VirtualDisplay virtualDisplay;
//录制屏幕的宽高像素。
private int mWidth = 720;
private int mHeight = 1280;
private int mDpi = 1;
//视频存储路径
private String mVideoPath = "";
//标志, 判断是否正在录屏
private boolean running = false;
//回调接口, 以及接口中要做的事。
public interface ScreenRecordingServiceListener {
void onPrepareComplete();
void onRecordingFinish(String videoPath);
}
private ScreenRecordingServiceListener listener;
@Override
public void onCreate() {
super.onCreate();
// HandlerThread serviceThread = new HandlerThread("service_thread", android.os.Process.THREAD_PRIORITY_BACKGROUND);
// serviceThread.start();
// running = false;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 注: mediaProjection的生成 必须在Service中进行。
createNotificationChannel();
int resultCode = intent.getIntExtra("code", -1);
Intent resultData = intent.getParcelableExtra("data");
Log.i(TAG, "onStartCommand_resultCode=" + resultCode);
Log.i(TAG, "onStartCommand_resultData=" + resultData);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (null == mediaProjectionManager) {
mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
}
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, Objects.requireNonNull(resultData));
//mediaProjection = ((MediaProjectionManager) Objects.requireNonNull(getSystemService(Context.MEDIA_PROJECTION_SERVICE))).getMediaProjection(resultCode, resultData);
Log.i(TAG, "mediaProjection_created");
}
if (listener != null) {
listener.onPrepareComplete();
}
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 ScreenRecordingService getScreenRecordService() {
return ScreenRecordingService.this;
}
}
@Nullable
@Override
//返回一个Binder用于通信, 需要一个获取Service的方法。
public IBinder onBind(Intent intent) {
return new ScreenRecordBinder();
}
//设置需要录制的屏幕参数。
public void setConfig(int width, int height, int dpi) {
mWidth = width;
mHeight = height;
mDpi = dpi;
}
public void setMediaProjectionManager(MediaProjectionManager projectionManager) {
mediaProjectionManager = projectionManager;
}
public void setListener(ScreenRecordingServiceListener callback) {
listener = callback;
}
//返回判断, 判断其是否在录屏。
public boolean isRunning() {
return running;
}
//服务的两个主要逻辑之一 ~ 开始录屏。
public boolean startRecord() {
Log.i(TAG, "startRecord");
//首先判断是否有录屏工具以及是否在录屏
if (null == mediaProjection || running) {
return false;
}
//初始化录像机, 录音机Recorder。
createRecorder();
//根据获取的屏幕参数创建虚拟的录屏屏幕。
createVirtualDisplay();
//本来不加异常也可以, 但是这样就不知道是否start成功。
//万一start没有成功, 但是running置为true了, 就产生了错误也无提示。
//提示开始录屏了, 但是并没有工作。
try {
//准备工作都完成了, 可以开始录屏了。
mediaRecorder.start();
//标志位改为正在录屏。
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() {
Log.i(TAG, "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();
if (listener != null) {
listener.onRecordingFinish(mVideoPath);
}
return true;
}
//初始化Recorder录像机。
public void createRecorder() {
Log.i(TAG, "createRecorder");
//创建Recorder。
mediaRecorder = new MediaRecorder();
//设置音频来源。
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//设置视频来源。
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
//设置视频格式为mp4。
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//设置视频存储地址。
mVideoPath = getOutputFile().getAbsolutePath();
//保存在该位置。
mediaRecorder.setOutputFile(mVideoPath);
//设置视频大小, 清晰度。
mediaRecorder.setVideoSize(mWidth, mHeight);
//设置视频编码为H264。
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
//设置音频编码。
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
//设置视频码率。
mediaRecorder.setVideoEncodingBitRate(2 * mWidth * mHeight);
mediaRecorder.setVideoFrameRate(16);
//初始化完成, 进入准备阶段, 准备被使用。
try {
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
//异常提示
Toast.makeText(this, "Recorder录像机准备失败", Toast.LENGTH_SHORT).show();
}
}
public void createVirtualDisplay() {
//虚拟屏幕通过MediaProjection获取, 传入一系列传过来的参数。
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
virtualDisplay = mediaProjection.createVirtualDisplay("VirtualScreen", mWidth, mHeight, mDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder.getSurface(), null, null);
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "virtualDisplay创建异常", Toast.LENGTH_SHORT).show();
}
}
//获取输出存储文件夹的位置。
public static File getOutputDirectory() {
String directoryFilePathName = Environment.
getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.getAbsolutePath()
+ "/ScreenRecording/";
//创建该文件夹。
File directoryFile = new File(directoryFilePathName);
if (!directoryFile.exists()) {
//如果该文件夹不存在。
if (!directoryFile.mkdirs()) {
//如果没有创建成功。
return null;
}
}
//创建成功了, 返回该目录。
return directoryFile;
}
private File getOutputFile() {
File directoryFile = getOutputDirectory();
File file = new File(directoryFile, "SR" + System.currentTimeMillis() + ".mp4");
// if (!file.exists()) {
// try {
// file.createNewFile();
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
Log.i(TAG, "filePath_" + file.getAbsolutePath());
return file;
}
private void createNotificationChannel() {
Notification.Builder builder = new Notification.Builder(this.getApplicationContext()); //获取一个Notification构造器。
Intent nIntent = new Intent(this, MainActivity.class); //点击后跳转的界面, 可以设置跳转数据。
builder.setContentIntent(PendingIntent.getActivity(this, 0, nIntent, 0))
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher)) // 设置下拉列表中的图标(大图标)
//.setContentTitle("ScreenRecording") // 设置下拉列表里的标题
.setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏内的小图标
.setContentText("is running......") // 设置上下文内容
.setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
/** 以下是对Android 8.0的适配 */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
final String channelId = "MyChannelId";
final String channelName = "MyChannelName";
//普通notification适配。
builder.setChannelId(channelId);
//前台服务notification适配。
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(channel);
}
Notification notification = builder.build(); // 获取构建好的Notification对象。
notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音。
startForeground(requestCode, notification);
}
}
特别鸣谢下面链接:
(0) https://blog.csdn.net/qq_46546793/article/details/123279152文章来源:https://www.toymoban.com/news/detail-701783.html
(0) https://blog.csdn.net/weixin_42602900/article/details/128340037文章来源地址https://www.toymoban.com/news/detail-701783.html
到了这里,关于Android屏幕录制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!