Android 录屏服务使用(源码)
从Android 5.0开始,可以对手机进行录屏,使用场景:如错误场景的视频上传,简单屏幕获取等,下面贴出使用用例和对使用的类一个简单的介绍
- MediaProjection
- MediaRecorder
- VirtualDisplay
- 使用
- 总结
MediaProjection
MediaProjection是一个5.0之后给开发者提供的新的截屏或者录屏的新手段。MediaProjection可以用来捕捉屏幕
这里我们主要用到的一个方法是
public VirtualDisplay createVirtualDisplay(String name, int width, int height, int dpi, int flags, Surface surface, android.hardware.display.VirtualDisplay.Callback callback, Handler handler) {
throw new RuntimeException("Stub!");
}
参数1:实际的流媒体显示实体名字,不能为null;
参数2:实际的流媒体显示实体的宽度,单位为像素,必须大于0;
参数3:实际的流媒体显示实体的高度,单位为像素,必须大于0;
参数4:实际的流媒体显示实体的像素密度,单位为dp,必须大于0;
参数5:实际的流媒体显示实体标志的结合,更多请查看 DisplayManager里头的标志,取值是{VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY,VIRTUAL_DISPLAY_FLAG_PRESENTATION,
VIRTUAL_DISPLAY_FLAG_PUBLIC,
VIRTUAL_DISPLAY_FLAG_SECURE}中的一个
参数6:播放流媒体的surface实例,可为null,如果木有;
参数7:实际的流媒体显示实体状态改变时的回调方法,可能为null;
参数8:调用参数7回调方法的handler;
返回VirtualDisplay实例,具体请查看VirtualDisplay类;
MediaRecorder
增加对录制音视频的支持,Android系统提供了这个类。该类的使用也非常简单
以下是对MediaRecorder的配置
setAudioSource(MediaRecorder.AudioSource.);
设置声音来源,一般传入 MediaRecorder. AudioSource.MIC参数指定录制来自麦克风的声音。(这里如果只录屏可以不设置)
setVideoSource(MediaRecorder.VideoSource.SURFACE);
设置用于录制的视频来源。如屏幕等
setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
设置所录制的音视频文件的格式。
setOutputFile(getsaveDirectory() + temp + “.mp4”);
设置录制的音频文件的保存位置。
setVideoSize(width, height);最高只能设置640x480
设置要拍摄的宽度和视频的高度。
setVideoEncoder(MediaRecorder.VideoEncoder.H264);
设置视频的编码格式
setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
设置音频编码格式
setVideoEncodingBitRate(1024 * 1024);
设置所录制视频的编码位率。
setVideoFrameRate(18);
设置录制视频的捕获帧速率。
setOrientationHint(90);
设置输出的视频播放的方向提示。
setMaxDuration(30*1000);
设置录制会话的最长持续时间(以ms为单位)。
prepare();
准备录制
VirtualDisplay
VirtualDisplay类代表一个虚拟显示器,需要调用DisplayManager 类的 createVirtualDisplay()方法,将虚拟显示器的内容渲染在一个Surface控件上,当进程终止时虚拟显示器会被自动的释放,并且所有的Window都会被强制移除。当不再使用他时,你应该调用release() 方法来释放资源。
这个类中主要有6个主要方法,主要是用来操作显示器,例如获取显示器,设置/得到surface,
注意,有些app例如游戏的每一次操作的录制,如果频繁的创建这个VirtualDisplay,会让手机内存持续消耗,让手机变卡,所以创建的操作放在服务中,系统会自动提用当前创建的VirtualDisplay不会频繁创建和不释放
简单的方法
Surface getSurface ()
获取虚拟显示器的surface。
release ()
释放显示器,并且销毁其所依据的surface
resize (int width, int height, int densityDpi)
运行应用程序使用虚拟现实器去适应改变的条件状态,而不用销毁再重建一个实例。
setSurface (Surface surface)
设置虚拟显示器依靠的surface。移除虚拟显示器所依靠的surface相当于关闭屏幕的操作。调用者///需要手动的销毁surface。
使用
创建一个服务 RecordUtil 在录屏之前启动这个服务,可以在AppApplication 中开启这个服务,具体的录屏界面去绑定这个服务,注意在AndroidManifest.xml中注册这个服务
服务
public class RecordUtil extends Service {
// private static RecordUtil recordUtil = null;
private MediaProjection mediaProjection;
private MediaRecorder mediaRecorder;
private VirtualDisplay virtualDisplay;
private boolean running;
private int width = 320;
private int height = 420;
private int dpi;
private Context context;
public static int current = 0;
public RecordUtil(){
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new RecordBinder();
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread serviceThread = new HandlerThread("service_thread",
android.os.Process.THREAD_PRIORITY_BACKGROUND);
serviceThread.start();
running = false;
mediaRecorder = new MediaRecorder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
public void setMediaProject(MediaProjection project) {
mediaProjection = project;
}
public boolean isRunning() {
return running;
}
public void setConfig(int width, int height, int dpi) {
this.width = width;
this.height = height;
this.dpi = dpi;
}
public boolean startRecord(String temp) {
if(mediaRecorder != null) {
if (mediaProjection == null || running) {
return false;
}
try {
initRecorder(temp);
createVirtualDisplay();
mediaRecorder.start();
running = true;
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
public boolean stopRecord() {
if(mediaRecorder != null) {
if (!running) {
return false;
}
try {
running = false;
mediaRecorder.stop();
mediaRecorder.reset();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
virtualDisplay.release();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mediaProjection.stop();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
public boolean release(){
if(mediaRecorder != null && hasLollipop()) {
if (!running) {
return false;
}
try {
running = false;
mediaRecorder.stop();
mediaRecorder.reset();
virtualDisplay.release();
mediaProjection.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
public static boolean hasLollipop(){
return Build.VERSION.SDK_INT >= 21;
}
private void createVirtualDisplay() {
// if(virtualDisplay == null) {
if(hasLollipop()) {
virtualDisplay = mediaProjection.createVirtualDisplay("MainScreen", width, height, dpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder.getSurface(), null, null);
}
// }
}
private void initRecorder(String temp) {
if(mediaRecorder != null) {
try {
// mediaRecorder.setAudioSource(MediaRecorder.AudioSource.);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mediaRecorder.setOutputFile(getsaveDirectory() + temp + ".mp4");
//mediaRecorder.setVideoSize(width, height);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
// mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
mediaRecorder.setVideoEncodingBitRate(1024 * 1024);
mediaRecorder.setVideoFrameRate(18);
//设置要捕获的视频的宽度和高度
//mSurfaceHolder.setFixedSize(320, 240);//最高只能设置640x480
mediaRecorder.setVideoSize(width, height);//最高只能设置640x480
//mediaRecorder.setOrientationHint(90);
//mediaRecorder.setMaxDuration(30*1000);
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static String getsaveDirectory() {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String rootDir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "ScreenRecord" + "/";
File file = new File(rootDir);
if (!file.exists()) {
if (!file.mkdirs()) {
return null;
}
}
//Toast.makeText(context, rootDir, Toast.LENGTH_SHORT).show();
return rootDir;
} else {
return null;
}
}
/**
* 递归删除文件
* @param file
*/
public static void recursionDeleteFile(File file){
try {
if(file.isFile()){
file.delete();
return;
}
if(file.isDirectory()){
File[] childFile = file.listFiles();
if(childFile == null || childFile.length == 0){
file.delete();
return;
}
for(File f : childFile){
recursionDeleteFile(f);
}
file.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public class RecordBinder extends Binder {
public RecordUtil getRecordService() {
return RecordUtil.this;
}
}
}
AppApplication
startService(new Intent(this, RecordUtil.class));
Activity
public MediaProjectionManager projectionManager;
public MediaProjection mediaProjection;
public RecordUtil recordService;
绑定服务
public void bind(){
Intent intent = new Intent(this, RecordUtil.class);
bindService(intent, connection, BIND_AUTO_CREATE);
}
并在Activity重写
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
try {
Logger.i("----activity-->" + requestCode + "---result--->" + resultCode);
Logger.i("----activity-->" + requestCode + "---result--->" + resultCode);
UMShareAPI.get(this).onActivityResult(requestCode, resultCode, data);
if(DeviceUtils.hasLollipop()) {
if (requestCode == 101 && resultCode == RESULT_OK) {
// RecordUtil recordUtil=new RecordUtil();
if (RecordUtil.hasLollipop()) {
Logger.e("________________________onActivityResult");
mediaProjection = projectionManager.getMediaProjection(resultCode, data);
recordService.setMediaProject(mediaProjection);
/*if (RecordUtil.current > 3) {
RecordUtil.current = 0;
}*/
RecordUtil.current++;
//RecordUtil.getInstance().setOutFile("temp"+RecordUtil.current);
recordService.startRecord("temp" + RecordUtil.current);
}
}
}
}catch (Exception e){
e.printStackTrace();
}
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
RecordUtil.RecordBinder binder = (RecordUtil.RecordBinder) service;
recordService = binder.getRecordService();
recordService.setConfig(320, 420, metrics.densityDpi);
}
@Override
public void onServiceDisconnected(ComponentName arg0) {}
};
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
启动录屏
if(RecordUtil.hasLollipop()) {
if(mediaProjection != null){
if(ecordService.isRunning()){
recordService.stopRecord();
}
}
Logger.e("_________________________录制视频开始");
RecordUtil.current++;
Intent captureIntent =projectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, RECORD_REQUEST_CODE);
if (ContextCompat.checkSelfPermission(mActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(mActivity,
new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_REQUEST_CODE);
}
if (ContextCompat.checkSelfPermission(mActivity, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(mActivity,
new String[] {Manifest.permission.RECORD_AUDIO}, AUDIO_REQUEST_CODE);
}
}
获得录屏文件路径
if (recordService.isRunning()) {
recordService.stopRecord();
}
try {
File file = new File(RecordUtil.getsaveDirectory() + "temp" + RecordUtil.current + ".mp4");
Logger.i("----file exit--->" + file.exists() + "-----file url--->" + file.getAbsolutePath());
if (file.exists()) {
//上传
Logger.e("_________________________存在");
} catch (Exception e) {
e.printStackTrace();
}
}
路还很长,慢慢走