Android实现录屏MediaProjection以及相关异常解决

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android实现录屏MediaProjection以及相关异常解决相关的知识,希望对你有一定的参考价值。

参考技术A 需要实现一个手机的录屏功能,于是从网上找了些相关资料和源码,发现跑不起来,于是开始dubug,发现坑还是很多的,这里记录一下实现过程和一些些遇到的异常以及一个我调整完可以跑的Demo。

首先在androidManifest中静态配置权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
然后在Activity中动态申请

if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(this,
new String[] Manifest.permission.WRITE_EXTERNAL_STORAGE, STORAGE_REQUEST_CODE);


if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(this,
new String[] Manifest.permission.RECORD_AUDIO, AUDIO_REQUEST_CODE);

因为项目中需要用到一个自定义的Application,所以要需要配置一个全局的Application,同样在AndroidManiest中在application添加自定义的类名,如果在里面启动服务了也要一并配置。

<application
android:name=".RecordApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

</application>
然后可以使用封装好的实现其录屏功能的service,这个封装类是网上找的,看很多人在用,我解决了一些异常,并根据自己需求修改了一下。

其中主要异常有:

1.mediaRecorder报空指针,解决方案,在声明的时候声明为静态

private static MediaRecorder mediaRecorder;
2.mediaRecorder.start()方法异常,在每次调用stop时要先调用

mediaRecorder.stop();
mediaRecorder.release();
两个方法,并将

mediaRecorder = null。

mediaRecorder.setAudiosource(MediaRecorder.AudioSource.MIC)异常,这里是设置音频源,可尝试将参数改为
MediaRecorder.AudioSource.DEFAULT
4.stop方法异常,如果是running状态不正常,可能是其状态丢失,需要将声明的running也改为静态的

0.增加需求,在生成视频时大部分人都会根据mediaRecorder.setVideoSize(width, height);方法来定死视频大小,导致一些手机会解析不了,或者是视频比屏幕小,这里提供一种根据屏幕大小动态设置视频大小的方法。

这里就要用到我们之前定义的全局的Application,然后调用getInstance()获取其实例,

然后通过

DisplayMetrics dm = RecordApplication.getInstance().getResources().getDisplayMetrics();
private int width = dm.widthPixels;
private int height = dm.heightPixels;
private int dpi = dm.densityDpi;
来获取屏幕的长、宽和dpi的值,这里不用WindowsManager方法是因为我是在非Activity去获取屏幕长宽的,所以用了getDisplayMetrics();

这样这个功能基本就是实现了。
Demo地址: https://github.com/han103070/Screencap

Android8.0及以上基于系统MediaRecorder实现录屏最基础步骤

1.申请到必须的相关权限

  • xml配置,另外需要动态获取权限才行

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  • notification权限
   private void notificationPermission(Context context) //判断应用的通知权限是否打开,返回Boolean值
        if (!NotificationManagerCompat.from(context).areNotificationsEnabled()) 
            Intent localIntent = new Intent();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) //8.0及以上
                localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
                localIntent.setData(Uri.fromParts("package", context.getPackageName(), null));
             else 
                localIntent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
                localIntent.putExtra("app_package", context.getPackageName());
                localIntent.putExtra("app_uid", context.getApplicationInfo().uid);
            
            context.startActivity(localIntent);
        
    

2.编写录屏前台服务

  • 在绑定时初始化的数据,包括通过传递过来的数据获取到mediaProjection
@Override
    public IBinder onBind(Intent intent) 
        //创建前台Notification保护
        createNotificationChannel();
        //初始化MediaProjection
        initMediaProjection(intent);
        return new RecordBinder();
    


    private void initMediaProjection(Intent intent) 
        int mResultCode = intent.getIntExtra("code", -1);
        Intent mResultData = intent.getParcelableExtra("data");
        MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
        mediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, Objects.requireNonNull(mResultData));
    


    private void createNotificationChannel() 
        Notification.Builder builder = new Notification.Builder(this.getApplicationContext());
        Intent nfIntent = new Intent(this, MainActivity.class); //点击后跳转的界面,可以设置跳转数据
        builder.setContentIntent(PendingIntent.getActivity(this, 0, nfIntent, 0)) // 设置PendingIntent
                .setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher)) // 设置下拉列表中的图标(大图标)
                .setContentTitle("屏幕录制") // 设置下拉列表里的标题
                .setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏内的小图标
                .setContentText("当前正在录屏中...") // 设置上下文内容
                .setWhen(System.currentTimeMillis()); // 设置该通知发生的时间

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 
            String notification_id = "notification_screenCap_id";
            String notification_name = "notification_screenCap_name";
            builder.setChannelId(notification_id);
            NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            NotificationChannel channel = new NotificationChannel(notification_id, notification_name, NotificationManager.IMPORTANCE_LOW);
            notificationManager.createNotificationChannel(channel);
        
        Notification notification = builder.build();
        notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
        int notification_id1 = 101;
        startForeground(notification_id1, notification);
    
  • 开启录屏与停止录屏基础方法
 public boolean startRecord() 
        if (mediaProjection == null || running) 
            return false;
         else 
            try 
                initRecorder();
             catch (IOException e) 
                e.printStackTrace();
                running = false;
                return false;
            
            createVirtualDisplay();
            mediaRecorder.start();
            running = true;
        
        return true;
    

    public boolean stopRecord() 
        if (running) 
            mediaRecorder.stop();
            mediaRecorder.release();
            virtualDisplay.release();
            mediaProjection.stop();
            mediaRecorder = null;
            return true;
        
        return false;
    


    private void createVirtualDisplay() 
        virtualDisplay = mediaProjection.createVirtualDisplay("MainScreen", width, height, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder.getSurface(), null, null);
    

    private void initRecorder() throws IOException 
        mediaRecorder = new MediaRecorder();
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        videoName = System.currentTimeMillis() + ".mp4";
        mediaRecorder.setOutputFile(createSaveVideoFile());
        mediaRecorder.setVideoSize(width, height);
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mediaRecorder.setVideoEncodingBitRate(5 * 1024 * 1024);
        mediaRecorder.setVideoFrameRate(30);
        mediaRecorder.prepare();
    

3.申请录屏服务权限

 projectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
 Intent captureIntent = projectionManager.createScreenCaptureIntent();
 startActivityForResult(captureIntent, RECORD_REQUEST_CODE);

4.成功后绑定录屏服务

@Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) 
       super.onActivityResult(requestCode, resultCode, data);
       if (requestCode == RECORD_REQUEST_CODE && resultCode == RESULT_OK) 
           Intent intent = new Intent(this, RecordService.class);
           intent.putExtra("code", resultCode);
           intent.putExtra("data", data);
           bindService(intent, connection, BIND_AUTO_CREATE);
       
   
private final ServiceConnection connection = new ServiceConnection() 
       @Override
       public void onServiceConnected(ComponentName className, IBinder service) 
           RecordService.RecordBinder binder = (RecordService.RecordBinder) service;
           recordService = binder.getRecordService();
           DisplayMetrics metrics = new DisplayMetrics();
           getWindowManager().getDefaultDisplay().getMetrics(metrics);
           //设置录屏分辨率
           recordService.setConfig(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
           recordService.startRecord();
           startBtn.setEnabled(true);
           startBtn.setText(recordService.isRunning() ? "停止录屏" : "开始录屏");
       

       @Override
       public void onServiceDisconnected(ComponentName arg0) 
       
   ;

这样后就可以通过ServiceConnection获取到服务的引用了,下面就可以愉快的进行业务逻辑的交互开发吧。

5.初步演示效果



转载注明:https://blog.csdn.net/u014614038/article/details/117699817

以上是关于Android实现录屏MediaProjection以及相关异常解决的主要内容,如果未能解决你的问题,请参考以下文章

Android实现录屏直播ScreenRecorder的简单分析

Android实现录屏MediaProjection以及相关异常解决

Android 音视频开发 -- Android Mediaprojection 截屏和录屏

Android 5.0+ 屏幕录制实现

Android截屏、录屏工具

Android8.0及以上基于系统MediaRecorder实现录屏最基础步骤