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实现系统级屏幕录制(上)

更多相关文章

  1. 一款常用的 Squid 日志分析工具
  2. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  3. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  4. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  5. android小入门
  6. Android(安卓)Studio 使用Lint检查并去除无用资源文件
  7. Android(安卓)WebView 上传文件支持全解析
  8. Android(安卓)m6.0权限问题调用封装utils类 - Permission in Andr
  9. Android客户端多文件上传

随机推荐

  1. Android studio 032 java Tomcat Servlet
  2. 【ListView】相关文章
  3. android: MapView加载多个 overlay 内存
  4. android中 dialog显示TimePickerDialog
  5. 8、RxJava+Retrofit+okhttp上传多张图片
  6. Android kill app Process 结束进程代码
  7. Android 删除 未接来电 通知
  8. Android播放音乐方法
  9. Android支持单词提示搜索框的网络请求策
  10. SlidingDrawer教程