Android实现系统级屏幕录制(下)
Android实现系统级屏幕录制
- 录屏服务的启动
- 权限申请
- 服务启动
- 停止录屏
- 过滤悬浮窗
录屏服务的启动
点击播放按钮后就开始实现我们的屏幕录制功能,点击结束按钮后,停止录屏功能,并生成音频文件。
权限申请
我们应该知道,屏幕录制需要申请的权限有读写权限、录屏权限。
framework\base\packages\SystemUI\AndroidManifest.xml
在上一篇的悬浮窗中我们添加了一个播放按钮,对其设置按键监听并启动我们的权限申请专用Activity。
framework\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\FloatScreenRecordedWindow.java
//录屏开启 View.OnClickListener lRecordStartButtonClickListener = new View.OnClickListener() { @Override public void onClick(View view) { floatScreenRecordedWindowHandler.sendEmptyMessage(START_RECORDED); } };
在消息的处理中,我们只调用了一个方法。
case START_RECORDED://开启录制 startScreenRecord(); break;
private void startScreenRecord() { Intent intent = new Intent(mContext, ScreenRecordActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent);}
在startScreenRecord()方法中我们去启动权限申请的ScreenRecordActivity.java这个类。
framework\base\packages\SystemUI\src\com\android\systemui\ScreenRecordActivity.java
package com.android.systemui;import android.Manifest;import android.app.Activity;import android.content.Context;import android.content.Intent;import android.content.pm.PackageManager;import android.content.res.Configuration;import android.media.projection.MediaProjectionManager;import android.os.Bundle;import android.util.DisplayMetrics;import android.util.Log;import android.view.Display;import android.view.Gravity;import android.view.Window;import android.view.WindowManager;import android.widget.Toast;public class ScreenRecordActivity extends Activity { private int mScreenWidth; private int mScreenHeight; private int mScreenDensity; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Window window = getWindow(); window.setGravity(Gravity.LEFT | Gravity.TOP); WindowManager.LayoutParams params = window.getAttributes(); params.x = 0; params.y = 0; params.width = 1; //不让当前权限申请界面过于显目,只点亮1个像素点。 params.height = 1; window.setAttributes(params); startRecord(); } private void startRecord() { getScreenBaseInfo(); MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent(); startActivityForResult(permissionIntent, 1000); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 1000) { if (resultCode == RESULT_OK) { //获得录屏权限,启动Service进行录制 Intent intent = new Intent(this, ScreenRecordService.class); intent.putExtra("resultCode", resultCode); intent.putExtra("resultData", data); intent.putExtra("mScreenWidth", mScreenWidth); intent.putExtra("mScreenHeight", mScreenHeight); intent.putExtra("mScreenDensity", mScreenDensity); startService(intent); } } finish(); } /** * 获取屏幕基本信息 */ private void getScreenBaseInfo() { Display display = getWindowManager().getDefaultDisplay(); DisplayMetrics metrics = new DisplayMetrics(); display.getMetrics(metrics); mScreenWidth = metrics.widthPixels; mScreenHeight = metrics.heightPixels; mScreenDensity = metrics.densityDpi;}}
关于onCreate()中点亮1像素点的原因在于,点击播放开启录屏后,浮现的该弹窗申请页面过于显目,就不让画面过多显示,整体启动也稍微好看点。
屏幕录制的权限申请在Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent(); 我们进入到源码看看。
framework\base\media\java\android\media\projection\MediaProjectionManager.java
/** * Returns an Intent that must passed to startActivityForResult() * in order to start screen capture. The activity will prompt * the user whether to allow screen capture. The result of this * activity should be passed to getMediaProjection. */ public Intent createScreenCaptureIntent() { Intent i = new Intent(); final ComponentName mediaProjectionPermissionDialogComponent = ComponentName.unflattenFromString(mContext.getResources().getString( com.android.internal.R.string .config_mediaProjectionPermissionDialogComponent)); i.setComponent(mediaProjectionPermissionDialogComponent); return i; }
可以看到google对其的解释是通过startActivityForResult()返回intent结果。而这也是我们为什么必须通过activity去申请录屏权限的原因。
服务启动
在ScreenRecordActivity类的onActivityResult()方法内通过返回结果来startService()。
framework\base\packages\SystemUI\ScreenRecordService.java
package com.android.systemui.screenrecord;import android.app.Service;import android.content.ContentResolver;import android.content.ContentValues;import android.content.Context;import android.content.Intent;import android.hardware.display.DisplayManager;import android.hardware.display.VirtualDisplay;import android.media.AudioManager;import android.media.AudioRecord;import android.media.MediaRecorder;import android.media.MediaScannerConnection;import android.media.projection.MediaProjection;import android.media.projection.MediaProjectionManager;import android.net.Uri;import android.os.Environment;import android.os.IBinder;import android.provider.MediaStore;import android.provider.Settings;import android.util.Log;import android.widget.Toast;import com.android.systemui.R;import java.io.File;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;import android.os.Environment;public class ScreenRecordService extends Service { private int mScreenWidth; private int mScreenHeight; private int mScreenDensity; /** * 录屏文件存放路径 */ public static final String SCREEN_RECORD_FILE_PATH = Environment.getExternalStorageDirectory() + "/Pictures/Screenshots/"; private int mResultCode; private Intent mResultData = null; private MediaProjection mMediaProjection = null; private MediaRecorder mMediaRecorder = null; private VirtualDisplay mVirtualDisplay = null; private String filePathName = null; public ScreenRecordService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { try { mResultCode = intent.getIntExtra("resultCode", -1); mResultData = intent.getParcelableExtra("resultData"); mScreenWidth = intent.getIntExtra("mScreenWidth", 0); mScreenHeight = intent.getIntExtra("mScreenHeight", 0); mScreenDensity = intent.getIntExtra("mScreenDensity", 0); mMediaProjection = createMediaProjection(); mMediaRecorder = createMediaRecorder(); mVirtualDisplay = createVirtualDisplay(); //必须在mMediaRecorder.prepare()之后调用,否则会报错"fail to get surface" mMediaRecorder.start(); } catch (Exception e) { e.printStackTrace(); } return Service.START_NOT_STICKY; } private VirtualDisplay createVirtualDisplay() { return mMediaProjection.createVirtualDisplay("mediaProjection", mScreenWidth, mScreenHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mMediaRecorder.getSurface(), null, null); } private MediaRecorder createMediaRecorder() { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); //定义输出视频文件的命名 filePathName = SCREEN_RECORD_FILE_PATH + simpleDateFormat.format(new Date()) + ".mp4"; //创建MediaRecprder对象 MediaRecorder mediaRecorder = new MediaRecorder(); //录制视频来源 mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); //控制视频输出格式,录制后文件是一个3gp文件,支持音频和视频录制 mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); //设置录制的视频编码比特率 mediaRecorder.setVideoEncodingBitRate(5 * mScreenWidth * mScreenHeight); //设置音频编码格式为高级音频编码 mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); //设置视频的编码格式 mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); //设置要捕获的视频的宽度和高度 mediaRecorder.setVideoSize(mScreenWidth, mScreenHeight); //设置视频帧率 mediaRecorder.setVideoFrameRate(60); try { //设置输出文件的路径 mediaRecorder.setOutputFile(filePathName); mediaRecorder.prepare(); } catch (Exception e) { e.printStackTrace(); } return mediaRecorder; } private MediaProjection createMediaProjection() { return ((MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE)).getMediaProjection(mResultCode, mResultData); } @Override public void onDestroy() { super.onDestroy(); if (mVirtualDisplay != null) { mVirtualDisplay.release(); mVirtualDisplay = null; } if (mMediaRecorder != null) { try { mMediaRecorder.setOnErrorListener(null); mMediaRecorder.setOnInfoListener(null); mMediaRecorder.setPreviewDisplay(null); mMediaRecorder.stop(); } catch (IllegalStateException e) { e.printStackTrace(); } mMediaRecorder.release(); mMediaRecorder = null; } if (mMediaProjection != null) { mMediaProjection.stop(); mMediaProjection = null; } if (touchState != 1) { showScreenTouchPositions(0); } Toast.makeText(this, R.string.screen_record_finish, Toast.LENGTH_SHORT).show(); //通知媒体库更新内容,否则一开始录制完的视频文件在相册中是找不到的,只有在文件管理播放过后才能出现在相册页面 MediaScannerConnection.scanFile(getApplicationContext(), new String[]{filePathName}, null, null); }}
在createMediaRecorder() 方法中主要是对视频的输出格式、编码格式、视频格式等进行一个设置。完成后记得在onDestroy()中释放它们。
停止录屏
同开启录屏一样,只需设置一个动作监听并进行相应操作即可。
framework\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\FloatScreenRecordedWindow.java
//停止录屏private void stopScreenRecord() { Intent intent = new Intent(mContext, ScreenRecordService.class); mContext.stopService(intent);}
过滤悬浮窗
需求明确指出,播放录制出来的视频文件,文件内容不包括有悬浮窗。即录制过程中,单独过滤悬浮窗。这点一开始确实是个难题,后面了解到一切都是起于surface绘制。且我们用的是VirtualDisplay(虚显)。Android支持多个屏幕:主显,外显,和虚显。这里不做过多讲解。所以我们需要在native层修改个文件。
在开始修改native层文件之前需要在悬浮窗的创建地方给予它识别id。
framework\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\FloatScreenRecordedWindow.java
在getLayoutParams()方法中添加
lp.setTitle("ScreenRecordedWindow"); //虚显过滤标识判断
再进入到native层文件。
framework\native\services\surfaceflinger\surfaceflinger.cpp
在该文件的rebuildLayerStacks() 方法中添加判断。
void SurfaceFlinger::rebuildLayerStacks() {.....if (!drawRegion.isEmpty()) { //判断是否是虚显if(DisplayDevice::DISPLAY_VIRTUAL == displayDevice->getDisplayType()){string a= layer->getName().string();//过滤ScreenRecordedWindow的窗体string::size_type idx = a.find("ScreenRecordedWindow#0");if(idx == string::npos){layersSortedByZ.add(layer);}}else{layersSortedByZ.add(layer);} }.....}
自此,整体的系统屏幕录制的内容就在这了,剩下的比如事件计时,触摸反馈这些内容相对简单,就不说明了,有兴趣也可以交流探讨。
Android实现系统级屏幕录制(上)
更多相关文章
- 一款常用的 Squid 日志分析工具
- GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
- RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
- Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
- android小入门
- Android(安卓)Studio 使用Lint检查并去除无用资源文件
- Android(安卓)WebView 上传文件支持全解析
- Android(安卓)m6.0权限问题调用封装utils类 - Permission in Andr
- Android客户端多文件上传