Android屏幕录制

这里使用Java语言编写实现,完整代码如下:

文件 AndroidMainfest.xml 的主要配置




    
    
    
    
    

    
        
            
                
                
            
        


        

        
    

文件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;

  @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("录屏管理页面");
    }

    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());
          }
        });
  }

  @Override
  public void onStart() {
    super.onStart();
    checkPermission();
  }

  @Override
  public void onResume() {
    super.onResume();
    // 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.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();
      }
    }

    @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()) {
            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;

  @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必须在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);
  }

  @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 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

你可能感兴趣的:(IT_前端开发_Android,android,java)