这里使用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" /><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"><activityandroid:name=".MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activityandroid:name=".ScreenRecordingActivity"android:exported="false"android:launchMode="singleTask"></activity><serviceandroid:name=".ScreenRecordingService"android:enabled="true"android:exported="true"android:foregroundServiceType="mediaProjection" /></application></manifest>
文件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;/*** @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 textViewStatus;@Overrideprotected 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("录屏管理页面");}textViewStatus = findViewById(R.id.textview_recording_status);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());}});}@Overridepublic void onStart() {super.onStart();checkPermission();}@Overridepublic void onResume() {super.onResume();// checkPermission();}@Overridepublic void onDestroy() {/** 页面销毁的时候, 可以不停止录屏 */// stopRecord();if (mServiceStatus >= ScreenRecordingService.statusServiceConnected) {unbindService(serviceConnection);}super.onDestroy();}// 标题栏返回按钮事件。@Overridepublic 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);// }// }@Overridepublic 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() {@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {System.out.println(TAG + "_ServiceConnection_onServiceConnected");//服务连接成功, 需要通过Binder获取服务, 达到Activity和Service通信的目的ScreenRecordingService.ScreenRecordBinder binder = (ScreenRecordingService.ScreenRecordBinder) iBinder;//通过Binder获取ServicemService = binder.getScreenRecordService();mService.setPrepareCompleteCallback(() -> {if (clickedStart && mService.startRecord()) {textViewStatus.setText("正在录屏");}});if (mService.isRunning()) {System.out.println(TAG + "_ServiceConnection_RunAlready");mServiceStatus = ScreenRecordingService.statusConfirmAgreed;textViewStatus.setText("正在录屏");if (clickedStart) {Toast.makeText(ScreenRecordingActivity.this, "已在录屏服务中", Toast.LENGTH_SHORT).show();}return;}mServiceStatus = ScreenRecordingService.statusServiceConnected;if (clickedStart) {actionAfterServiceConnected();}}@Overridepublic 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()) {textViewStatus.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();textViewStatus.setText("已停止录屏");}}}
文件 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 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 = 1280;private int mHeight = 1080;private int mDpi = 1;//标志, 判断是否正在录屏private boolean running = false;//声明视频存储路径private String videoPath = "";// 回调接口, 以及接口中要做的事。public interface PrepareCompleteCallback {void action();}private PrepareCompleteCallback prepareCompleteCallback;@Overridepublic void onCreate() {super.onCreate();// HandlerThread serviceThread = new HandlerThread("service_thread", android.os.Process.THREAD_PRIORITY_BACKGROUND);// serviceThread.start();// running = false;}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {// 注: 生成mediaProjection必须在services中进行。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 (prepareCompleteCallback != null) {prepareCompleteCallback.action();}return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {super.onDestroy();}@Overridepublic 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 setPrepareCompleteCallback(PrepareCompleteCallback callback) {prepareCompleteCallback = 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();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);//设置视频存储地址。videoPath = getOutputFile().getAbsolutePath();//保存在该位置。mediaRecorder.setOutputFile(videoPath);//设置视频大小, 清晰度。mediaRecorder.setVideoSize(mWidth, mHeight);//设置视频编码为H264。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录像机准备失败", 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();}}//获取输出存储文件夹的位置。private 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
(0) https://blog.csdn.net/weixin_42602900/article/details/128340037