当前位置: 首页 > news >正文

【安卓15】分析录屏应用与原生投放和屏幕共享的联系,停止共享后录屏结束

一、问题概要

假如你有一个录屏应用,开启录屏后,安卓原生状态栏会显示一个红色的计时按钮,点击这个按钮弹弹出一个窗口可选择关闭或者停止共享,停止共享后录屏实际上已经停止输出数据了,你的录屏应用需要监听到这个事件进行自己的逻辑处理,除了这个按钮,在下拉状态栏的tile里面也有一个投放开关,点击关闭投放后也会结束录屏。

二、原因分析

录屏应用开启时,会先创建一个虚拟显示,然后利用录像机MediaRecorder开始录制,这里的虚拟显示场景需要用到两个关键的“人物”,一个是MediaProjection,它提供了权限管理和屏幕内容捕获的功能,一个是VirtualDisplay ,它一个虚拟的显示设备,可以将屏幕内容输出到指定的 Surface.一个简单的虚拟显示创建方法如下:

// 创建 MediaProjection 实例
MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);// 创建 VirtualDisplay
virtualDisplay = mediaProjection.createVirtualDisplay("ScreenCapture",screenWidth,screenHeight,screenDensity,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,surface, // Surface 对象null,null
);

虚拟显示创建了之后,就需要一个“录像机”去记录数据,MediaRecorder的使用一般如下

MediaRecorder mediaRecorder = new MediaRecorder();
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); // 使用 Surface 作为视频源
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); // 设置输出格式
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); // 设置视频编码器
mediaRecorder.setVideoSize(screenWidth, screenHeight); // 设置视频分辨率
mediaRecorder.setVideoFrameRate(30); // 设置帧率
mediaRecorder.setOutputFile(outputFilePath); // 设置输出文件路径
mediaRecorder.prepare(); // 准备录制
mediaRecorder.start(); // 开始录制

这个录像机需要一个输入源,所以MediaRecorder 使用 Surface 作为输入源,VirtualDisplay 的输出会被绑定到这个 Surface 上,

// 创建 Surface 并绑定到 MediaRecorder
Surface surface = mediaRecorder.getSurface();// 将 Surface 传递给 VirtualDisplay
virtualDisplay = mediaProjection.createVirtualDisplay("ScreenCapture",screenWidth,screenHeight,screenDensity,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,surface,null,null
);

停止录制时需要调用 stop() 方法,并释放资源

mediaRecorder.stop();
mediaRecorder.release();
virtualDisplay.release();
mediaProjection.stop();

在了解了这个录制基本原理之后,就可以分析原因了,分步骤来讲大概有如下几点原因

1Surface 的依赖关系
MediaRecorder 使用 Surface 作为输入源(通过 setVideoSource(MediaRecorder.VideoSource.SURFACE) 配置)。
VirtualDisplay 的输出被绑定到这个 Surface 上。
当 VirtualDisplay 被释放或停止时,它不再向 Surface 输出数据,导致 MediaRecorder 没有新的帧数据可以录制。
关键点:
MediaRecorder 不会主动从其他地方获取数据,它完全依赖于绑定的 Surface 提供的帧数据。
如果 Surface 停止接收数据,MediaRecorder 就会“无米下锅”
2. VirtualDisplay 的生命周期
VirtualDisplay 的生命周期与 MediaProjection 紧密相关。
当调用 virtualDisplay.release() 或 mediaProjection.stop() 时,VirtualDisplay 会被销毁,停止向 Surface 输出数据。
此时,即使 MediaRecorder 仍在运行,也无法继续录制内容。
3. MediaRecorder 的行为
MediaRecorder 在录制过程中,会持续从绑定的 Surface 中拉取帧数据。
如果 Surface 没有新数据提供,MediaRecorder 会等待一段时间后抛出异常或直接停止录制。
这是因为 MediaRecorder 内部没有缓存机制来处理长时间无数据的情况。

知道原因之后就大概知道怎么改了,最简单的办法就是在停止共享后发送广播通知应用结束录屏。

三、修改源码

1、录屏应用本身添加虚拟显示回调

这是第一种最简单的方法就是在创建虚拟显示的时候监听回调,在onStop处理自己的逻辑

        mVirtualDisplay = mediaProjection.createVirtualDisplay(TAG, width, height, mScreenDensity,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder.getSurface(), new VirtualDisplay.Callback() {@Overridepublic void onResumed() {//监听虚拟显示完成后再开启录像机,避免录屏前几帧黑屏super.onResumed();Log.i(TAG, "VirtualDisplay onResumed");mIsVirtualDisplayReady = true;startMediaRecorder();}@Overridepublic void onStopped() {//在这里实现自己的逻辑,比如释放资源保存录屏文件等等super.onStopped();Log.i(TAG, "VirtualDisplay onStopped");mIsVirtualDisplayReady = false;}}, null);

这种方式可以不改安卓framework,好处是实现简单,缺点是如果在多个地方使用了这个虚拟显示,会有意想不到的情况。我这里使用的是第二种方式

2、修改framework在点击停止共享时发送广播通知录屏结束

1、下拉状态栏tile的投屏开关修改如下
在stopCasting方法里面发送广播,源码路径:

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java

    @Overridepublic void stopCasting(CastDevice device) {final boolean isProjection = device.getTag() instanceof MediaProjectionInfo;mLogger.logStopCasting(isProjection);if (isProjection) {final MediaProjectionInfo projection = (MediaProjectionInfo) device.getTag();if (Objects.equals(mProjectionManager.getActiveProjectionInfo(), projection)) {if("你的应用包名".equals(mProjection.getPackageName())){Log.d(TAG, "send broadcast to stop screenrecorder");//send broadcastString RECEIVING_PACKAGE = "包名";Intent intent = new Intent("广播action");intent.setPackage(RECEIVING_PACKAGE);mContext.sendBroadcast(intent);}                mProjectionManager.stopActiveProjection();} else {mLogger.logStopCastingNoProjection(projection);}} else {mLogger.logStopCastingMediaRouter();mMediaRouter.getFallbackRoute().select();}}//别忘了导入
import android.content.Intent;
import android.util.Log;

2、左上角红色的计时按钮结束录屏修改
源码路径:

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt

这个代码时kt代码,跟踪源码时需要懂一点它的设计模式,这里给出具体修改如下

    /** Stops the currently active projection. */private fun stopProjectingFromDialog() {logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested from dialog" })chipTransitionHelper.onActivityStoppedFromDialog()mediaProjectionChipInteractor.stopProjecting()sendStopScreenRecordingBroadcast()}private fun sendStopScreenRecordingBroadcast() {val receivingPackage = "包名"val intent = Intent("广播action").apply {setPackage(receivingPackage)}context.sendBroadcast(intent)Log.d(TAG, "send broadcast to stop screenrecorder")}

追踪源码首先是根据弹窗的按钮文件查找到最终在这里引用
在这里插入图片描述
warpStopAction的实现如下
在这里插入图片描述
里面的stopAction是从model那边模块化注入的
在这里插入图片描述

相关文章:

  • D3路网图技术文档
  • 第三篇:深入 Framer Motion Variants:掌握组件动画编排的艺术
  • 基于单片机的出租车计价系统
  • 驱动-兼容不同设备-container_of
  • GPU服务器声音很响可以怎么处理
  • STM32 HAL库之WDG示例代码
  • Python使用FastMCP开发MCP服务端
  • 构建批量论文格式修改系统:从内容识别到自动化处理
  • 【ARM】MDK烧录提示Error:failed to execute‘ ‘
  • 如何用AI将IPD项目评审效率提升300%?
  • IMX6ULL2025年最新部署方案2在Ubuntu24.04上编译通过Qt5.12.9且部署到IMX6ULL正点原子开发板上
  • MCP(模型上下文协议)、A2A(Agent2Agent)协议和JSON-RPC 2.0的前沿技术解析
  • 网络安全·工具篇1·Nmap的运用
  • LVGL实战训练——计算器实现
  • Linux 命令全解析:从零开始掌握 Linux 命令行
  • 2025第16届蓝桥杯省赛之研究生组F题01串求解
  • (2025-04-12)向老主机箱中安装新买的显卡及固态硬盘
  • 力扣热题——使数组元素互不相同所需的最少操作次数
  • 邻接矩阵与邻接链表:选择哪种图表示方式更合适? [特殊字符]
  • Windows10下Jekyll博客部署全指南|解决GitHub模板运行失败问题
  • 初步结果显示加拿大自由党赢得大选,外交部回应
  • 解放日报头版:人民城市共建共享展新卷
  • 找化学的答案,解人类的命题:巴斯夫的“变革者”成长之道
  • 油电同智,安全超充!从上海车展看中国汽车产业先发优势
  • 清华成立人工智能医院,将构建“AI+医疗+教育+科研”闭环
  • 人民日报:广东全力推动外贸稳量提质