首页 > 其他分享 >android录屏开发总结

android录屏开发总结

时间:2024-06-15 23:31:59浏览次数:25  
标签:总结 mediaRecorder IBinder service 录屏 Intent android public

开始之前先说明一下关键几个类的作用:
  • MediaProjectionManager:录屏主要管理类,发出截屏意图提醒、创建录屏等
  • IBinder:IBinder是Android中的一个接口,它定义了一组用于进程间通信的方法。通过IBinder,我们可以在不同的进程之间传递数据和调用方法,实现进程间的交互。在Android系统中,每个进程都有一个唯一的Binder对象,用于管理该进程中的Binder通信。
  • ServiceConnection:用于管理应用程序和服务之间的连接
  • MediaRecorder:Android sdk提供的一个专门用于音视频录制,一般利用手机麦克风采集音频,摄像头采集图片信息,设置参数等。
  • VirtualDisplay:创建虚拟屏幕
1、注册service组件

录屏需要后台运行服务支持,需要在mannifest中注册service组件

 <service android:name=".ScreenService"
          android:enabled="true"
          android:exported="true"
          android:foregroundServiceType="mediaProjection"/>
2、activity中通过Intent绑定服务
Intent intent = new Intent(this, ScreenService.class);
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
  • 首先,绑定开始之后Android系统调用service的onBind()方法,它返回一个用来与service交互的IBinder对象,但是绑定是异步的,他需要通过mServiceConnection里面的方法来传递(后面讲)
//首先,客户端调用bindService绑定的时候会执行service中的onBind这个方法
@Override
public IBinder onBind(Intent intent) {
    return new RecordBinder();
}
//service里面的内部类
 public class RecordBinder extends Binder {
 //里面的getRecordService方法,待会activity会调用他
    public ScreenService getRecordService() {
        return ScreenService.this;
    }
}

  • 这里干了一件事,绑定服务的时候返回一个Binder,这个binder中有一个getRecordService方法,待会客户端activity那边调用这个方法可以直接返回当前的service

  • 刚才讲了绑定的时候传递了一个mServiceConnection对象,上面返回的IBinder对象会在这个类的方法中返回,拿到返回的这个IBinder对象我们就可以调用该对象的方法(这个方法是我们自己写的,返回的是当前的service对象,这样我们就在客户端的activity中拿到了后台运行的service对象)

//这个mServiceConnection对象就是绑定服务的时候传递进去的
 private ServiceConnection mServiceConnection = new ServiceConnection() {
    // 当与service的连接建立后被调用  
    @Override
    public void onServiceConnected(ComponentName className, IBinder myBinder) {
        //ScreenService.RecordBinder是service中的内部类,binder就是我们返回的IBinder对象
        ScreenService.RecordBinder binder = (ScreenService.RecordBinder) myBinder;
        //通过返回的这个IBinder对象去调用他里面的getRecordService方法,的到的时候service对象
        ScreenService recordService = binder.getRecordService();
    
        recordService.setConfig(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
    }
    // 当与service的连接意外断开时被调用 注意是意外断开的时候调用)
    @Override
        public void onServiceDisconnected(ComponentName arg0) {
    }
};
3. 录制屏幕弹窗提示申请
  • 开始检查权限(存储权限、录音权限,权限不是本文的重点,略过)
  • 权限都在的时候开始申请屏幕录制
//通过MediaProjectionManager这个类来启动,这个是录屏之前的一个保护弹窗,询问你是不是需要开始录屏
//点击同意之后系统会回调到onActivityResult 通过这个REQUEST_CODE_RECORD参数来标识
private MediaProjectionManager projectionManager =  (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);

Intent captureIntent = projectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, REQUEST_CODE_RECORD);
//点击同意之后如下
 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            if (requestCode == REQUEST_CODE_RECORD)//上述传递的标识参数
             {
                //录屏逻辑
                Intent service = new Intent(this, ScreenService.class);
                service.putExtra("resultCode", resultCode);
                service.putExtra("requestCode", requestCode);
                service.putExtra("data", data);
                //启动service服务,启动服务之后会执行service中的onStartCommand,可以在onStartCommand中做逻辑操作
                startForegroundService(service);
//                handler.postDelayed(runnable, 10*1000);//最多录制10s
            }
        }
    }

4. 开始录制,启动服务之后执行service中的onStartCommand方法,然后开始录屏逻辑的实现
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int resultCode = intent.getIntExtra("resultCode", -1);
        int requestCode = intent.getIntExtra("requestCode", -1);
        Intent resultData = intent.getParcelableExtra("data");
        startNotification();
        projectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
        mediaProjection = projectionManager.getMediaProjection(resultCode, resultData);//必须在通知显示之后调用
        //录屏
        try {
            startRecord();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return super.onStartCommand(intent, flags, startId);
    }
    private final String NOTIFICATION_CHANNEL_ID="com.yun.screenrecord.MediaService";
    private final String NOTIFICATION_CHANNEL_NAME="com.yun.screenrecord.channel_name";
    private final String NOTIFICATION_CHANNEL_DESC="com.yun.screenrecord.channel_desc";
    public void startNotification() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //Call Start foreground with notification
            Intent notificationIntent = new Intent(this, ScreenService.class);
            PendingIntent pendingIntent;//适配31及以上的版本隐式启动
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
                pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
            } else {
                pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_ONE_SHOT);
            }
            NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                    .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground))
                    .setSmallIcon(R.drawable.ic_launcher_foreground)
                    .setContentTitle("Starting recording")
                    .setContentText("Starting screen record service")
                    .setContentIntent(pendingIntent);
            Notification notification = notificationBuilder.build();
            NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
            channel.setDescription(NOTIFICATION_CHANNEL_DESC);
            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(channel);
            startForeground(1, notification); //必须使用此方法显示通知,不能使用notificationManager.notify,否则还是会报上面的错误
        }
    }
5. 配置录屏的相关操作,完善startRecord代码
    public boolean startRecord() throws JSONException {
        if (mediaProjection == null || running) {
            return false;
        }
        initRecorder();
        createVirtualDisplay();
        mediaRecorder.start();
        running = true;
        return true;
    }

     private void initRecorder() throws JSONException {
        CamcorderProfile mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P);
        //判断是不是需要声音
        if (this.dataOption.get("voiceStatus") == "1"){
            mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置声音
        }
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        //判断是不是需要声音
        if (this.dataOption.get("voiceStatus") == "1"){
            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        }
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        
        mediaRecorder.setVideoSize(this.width, this.height);

        mediaRecorder.setVideoFrameRate(30);
        mediaRecorder.setVideoEncodingBitRate(mProfile.videoBitRate);
        //设置文件输出路径
        mediaRecorder.setOutputFile('');
        try {
            mediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
     /**
     * 创建一个录屏 Virtual
     */
    private void createVirtualDisplay() {
        //VirtualDisplay录屏功能,主要通过这个类来实现,VirtualDisplay对应虚拟Display,主要用来进行屏幕录制等相关功能
        virtualDisplay = mediaProjection
                .createVirtualDisplay("mediaprojection", width, height, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder
                        .getSurface(), null, null);
    }

6. 结束录屏
    /**
     * 结束录屏
     */
    public boolean stopRecord() {
        if (!running) {
            return false;
        }
        running = false;
        mediaRecorder.stop();
        mediaRecorder.reset();
        virtualDisplay.release();
        mediaProjection.stop();
        return true;
    }

标签:总结,mediaRecorder,IBinder,service,录屏,Intent,android,public
From: https://blog.csdn.net/j15087159186/article/details/139626883

相关文章

  • flutter AndroidStudio 模拟器无网络连接
    Error:SocketException:Failedhostlookup:''(OSError:Noaddressassociatedwithhostname,errno=7) 命令启动模拟器1.找到你的模拟器名字,默认在C:\users\xx\.android\avd目录下,如图3-3。如果找不到可以在AndroidStudio->AVDManager->showondisk定位到此目录......
  • 英语复习之英语反义词总结(七)
    接下来总结一些英语反义词。单词(英式发音/美式发音)释义(词性)例句(中文翻译)Absent(/'æbsənt//ˈæbsənt/)缺席的(adj)Shewasabsentfromschoolyesterday.(她昨天缺席了学校。)Hisabsencewasnotedbyeveryone.(每个人都注意到了他的缺席。......
  • UE Puerts 在 Android 的调试方法
    配置流程在JsEnv.Build.cs添加WITH_INSPECTOR编译添加这个宏就开启了全平台调试功能看到这里就知道V8Inspector之前仅在三个平台会编译,现在会在任意平台编译了打包的时候会发现存在编译错误(如果没有就是后续Puerts版本修复了这个问题),接下来修复这个错误(没有IOS......
  • webshell获取总结(cms获取方法、非cms获取方法、中间件拿Webshell方法)
    目录前期准备:1、cookices靶场网站搭建: 2、dedecms靶场环境搭建:获取Webshell方法总结:一、CMS获取Webshell方法 二、非CMS获取Webshell方法1、数据库备份获取Webshell例如:2、抓包上传获取Webshell3、Sql命令获取Webshell例如:4、模板修改获取Webshell例如:5、插入......
  • 持续总结中!2024年面试必问 20 道并发编程面试题(七)
    上一篇地址:持续总结中!2024年面试必问20道并发编程面试题(六)-CSDN博客十三、请解释什么是生产者-消费者问题。生产者-消费者问题(Producer-ConsumerProblem)是计算机科学和操作系统中的一个经典同步问题。这个问题描述了两种不同的进程或线程:生产者(Producer)和消费者(Consumer),它......
  • 持续总结中!2024年面试必问 20 道并发编程面试题(八)
    上一篇地址:持续总结中!2024年面试必问20道并发编程面试题(七)-CSDN博客十五、请解释什么是阻塞队列(BlockingQueue)。阻塞队列(BlockingQueue)是一种特殊的队列,它是Java并发集合的一部分,用于在多线程环境中进行线程间通信。当生产者线程(Producer)尝试将元素放入队列时,如果队列已......
  • Studying-代码随想录训练营day9| 151.反转字符串里的单词、卡码网:55.右旋转字符串、28
    第九天,......
  • QT6安装Android SDK出现"Android SDK Command-line Tools run"出错解决办法
    前言以下提供的方案,是在QT6,Androidstudio均有安装的前提下,安装完javaJDK,在设置安卓SDK时出现的问题,具体表现如标题所言本文目的是以做笔记学习,交流为主,推荐参考参考链接参考链接https://blog.csdn.net/yy_xzz/article/details/132135255操作流程确定NDK路径......
  • 2024.6 -> 做题记录与方法总结
    2024/6/151.P4363[九省联考2018]一双木棋chess经典轮廓线dp使用的关键在于发现状态数并不多,用\(n\)进制数来表现轮廓的状态\(dp\)的转移和轮廓线息息相关如图,蓝色轮廓线状态只能转移到含一个紫色的状态因为$1\leqn,m\leq10$用\(11\)进制压缩状态就可......
  • android studio 启动虚拟机长时间无响应,无法启动(二)
    书接上回,MAC更新到11.0需要40GB,但是我的电脑一共120GB,系统内存占了70GB,更新完MAC是剩不下空间了,因此不选择更新版本,重新安装了较低版本的AndroidstudioAndroidstudio官网又出现了新的问题。。。问题PANIC:BrokenAVDsystempath.CheckyourANDROID_SDK_ROOTvalu......