本章教大家实现后台录制手机屏幕和定时截屏的功能。

可以录制App外的屏幕,不限于本App内部喔。

禁止转载,侵权必究!Update2022.05.14

创建plugin项目

命令行执行以下命令:

flutter create --org cn.abilitygame -t plugin -i objc -a java 
 --platforms android,ios ability_media_projection_plugin

build一下项目:

cd ability_media_projection_plugin/example
flutter build apk

打开Android Studio,选择import project,一路选到项目中的example的android目录下,点击Open。

编写Android部分代码

增加主类接口,以实现录屏功能:

public class AbilityMediaProjectionPlugin implements FlutterPlugin, ActivityAware, MethodCallHandler, PluginRegistry.ActivityResultListener {}

先看ActivityAware接口的关键方法实现(要把绑定的activity存下来):

  @Override
  public void onAttachedToActivity(ActivityPluginBinding binding) {
    this.activity = binding.getActivity();
    ...
  }

再看PluginRegistry.ActivityResultListener接口的关键方法实现,要实现onActivityResult方法。因为录屏需要启动一个新的Intent,这个时候需要绑定一个新的Activity并监听它的回调。

  @Override
  public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
    if( requestCode == SCREEN_RECORD_REQUEST_CODE ) {
      if( resultCode == Activity.RESULT_OK ){
...
        _result.success("start media recorder successfully!");
        return true;
      }else {
        _result.error("500", "Activity callback:" + resultCode, "");
      }
    }else{}
    return false;
  }

在onMethodCall方法中实现启动录屏的代码:

if (call.method.equals("startRecordScreen")) {
...
      Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent();
      ActivityCompat.startActivityForResult(activity, permissionIntent, SCREEN_RECORD_REQUEST_CODE, null);
}

按Android规范创建了录屏的Intent并启动一个新的Activity来执行录屏。回到onActivityResult回调方法的实现,我们首先要初始化MediaRecorder,然后关联到MediaProjection投射类,投射到一个VirtualDisplay,然后再启动MediaRecorder。

      if( resultCode == Activity.RESULT_OK ){
        // 初始化
        initMediaRecorder();
        mediaProjection = mediaProjectionManager.getMediaProjection(activity.RESULT_OK,data);
        if (mediaProjection == null){
          _result.error("500","mediaProjection is null","");
        }else{}
        activityCallBack = new ActivityCallBack();
        mediaProjection.registerCallback(activityCallBack, null);
        // start前先把virtualDisplay创建好
        surface = mediaRecorder.getSurface();
        // 创建虚拟显示器
        virtualDisplay = mediaProjection.createVirtualDisplay("MainActivity",
                DISPLAY_WIDTH, DISPLAY_HEIGHT, DPI, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                surface, null, null);
        mediaRecorder.start();

        _result.success("start media recorder successfully!");
        return true;
      }

我们再看下initMediaRecorder方法:

  private void initMediaRecorder() {
    try {
      mediaRecorder = new MediaRecorder();
      mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
      mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
      mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
      mediaRecorder.setOutputFile( storePath + "test.mp4" );
      mediaRecorder.setVideoSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);
      mediaRecorder.setVideoFrameRate(60);
//      mediaRecorder.setOrientationHint(90);
      mediaRecorder.setVideoEncodingBitRate(5*DISPLAY_HEIGHT*DISPLAY_WIDTH);
      mediaRecorder.prepare();
    } catch (Exception e) {
      Log.i( TAG, e.getMessage() );
    }
  }

setVideoEncodingBitRate是提升清晰度的,其他的参数应该非常好理解。

注意:完整的代码实现请看文章最后的源码url。

编写Dart部分

关闭Android Studio。重新打开Android Studio,选择Open an exist Android Studio Project。

定义私有变量,存储录屏操作各个步骤的返回结果:

String _opResult = "";

实现两个方法,分别对应开始录屏和结束录屏:

  void startRecord() {
    AbilityMediaProjectionPlugin.startRecord.then((value) => setState((){
      _opResult = value;
    }));
  }

  void stopRecord() {
    AbilityMediaProjectionPlugin.stopRecord.then((value) => setState((){
      _opResult = value;
    }));
  }

定义UI界面

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('录屏测试'),
        ),
        body: Column(
          children: <Widget>[
            Text("录屏能力验证:"),
            RaisedButton(
              onPressed: () => startRecord(),
              color: Colors.lightBlueAccent,
              child: Text('开始录屏', style: TextStyle(fontSize: 10)),
            ),
            RaisedButton(
              onPressed: () => stopRecord(),
              color: Colors.lightBlueAccent,
              child: Text('结束录屏', style: TextStyle(fontSize: 10)),
            ),
            Text('$_opResult'),
          ],
        ),
      ),
    );
  }

定义MethodChannel交互类

class AbilityMediaProjectionPlugin {
  static const MethodChannel _channel =
      const MethodChannel('ability_media_projection_plugin');

  static Future<String> get startRecord async {
    final String version = await _channel.invokeMethod('startRecordScreen');
    return version;
  }

  static Future<String> get stopRecord async {
    final String version = await _channel.invokeMethod('stopRecordScreen');
    return version;
  }
}

测试自定义插件

需要点击2次开始录屏,因为在代码实现中第一次点击会获取基础权限(比如写入磁盘),第二次点击才是申请录屏权限并开始录屏。点击结束录屏后,我们可以在相册–>其他相册–>DCIM 下找到录屏的mp4文件。

机型适配问题

适用于所有Android5.0以上的机型(基本上包含了所有市面上的机型)

示例源代码

源码地址

截屏参考链接