先说个可能有点反常识的结论:Android上做屏幕录制,技术选型基本没有悬念,官方提供的MediaProjection API是唯一值得认真考虑的路线,其它方案都存在这样那样的硬伤。我这次用Android Studio和Java完整实现了一款屏幕录制器,从权限申请、悬浮控制到视频落盘,所有代码全部开源可跑通。这篇文章就把整个项目的拆解过程、核心源码和踩坑记录完整分享出来,适合正在学习Android开发、想做一个实用工具项目、或者准备用MediaProjection做录屏功能的朋友直接参考。
整个项目最终实现的功能很清晰:点击悬浮按钮开始录制,再次点击停止录制并保存视频文件到相册,全程无需Root,支持自定义分辨率选择。听起来简单,但把每个环节抠到能稳定运行,涉及的细节比想象中多不少——尤其是不同Android版本的权限差异、旋转屏幕的方向处理、还有国产ROM的后台限制,都会让一个"看似简单"的录屏功能变得异常难缠。如果你正准备上手这类项目,这篇文章能帮你省下至少一周的排查时间。
1. 选型复盘:为什么最终锁定MediaProjection官方API
做屏幕录制器之前,我认真比较过市面上几种可行路线。很多人第一个想到的是Root后直接读帧缓冲,这种方式确实能拿到最底层的画面数据,但问题同样明显:不仅要求设备已经Root,而且不同机型和内核版本的实现差异极大,代码写出来基本没有可移植性,仅适合在极少数设备上自己折腾。
还有一种思路是使用AccessibilityService无障碍服务。无障碍服务能够监听屏幕内容变化,但它的设计初衷是辅助功能,并非为了截屏或录屏,加之它需要用户在系统设置中手动开启服务,权限门槛不低,而且从无障碍服务中获取画面内容的方式非常间接,性能开销也相当大。用来做录屏主链路,属于绕远路,不建议。
MediaProjection是Android 5.0(API 21)起官方提供的屏幕捕捉方案,API设计上就是为录屏和投屏这类场景服务的。它由系统弹出授权对话框,用户明确同意后,开发者的App就能拿到屏幕内容的投影,再通过创建VirtualDisplay把画面输出到MediaRecorder或SurfaceView等目标。这条链路从系统层面打通了"录屏"这件事,稳定性和兼容性都远超社区方案。
这个方案还有一个天然优势:虚拟显示是系统级的合成通道,录制的画面不只是当前Activity的内容,而是整个屏幕。也就是说,用户切到桌面、打开其它App、甚至弹出系统对话框,录制内容都会如实记录。这对一个通用录屏工具来说是必备能力,也是MediaProjection被选为主技术路线的最直接理由。
// MediaProjectionManager是系统提供的能力入口 MediaProjectionManager mpManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); Intent captureIntent = mpManager.createScreenCaptureIntent(); startActivityForResult(captureIntent, REQUEST_MEDIA_PROJECTION);上面这段是获取屏幕捕捉权限的第一步,createScreenCaptureIntent()会创建一个系统级的权限请求Intent,弹出的对话框会明确告知用户"此应用将截取屏幕上显示的所有内容"。用户选择允许后,系统会通过onActivityResult把结果交还给App,后续的MediaProjection实例就从这里来。
我在选型时也考虑过第三方的开源录屏库,但它们大多是封装了MediaProjection的壳,或者引入了大量不必要的依赖,项目结构复杂不说,遇到问题时反而更难定位。自己用官方API从零实现,代码量并不大,核心调用控制在几个关键类里,出问题时也更容易排查。对想深入学习Android多媒体开发的朋友来说,走一遍原始API比直接套用封装库更有价值。
1.1 屏幕录制器的整体功能规划
动手写代码之前,我先明确了这个小工具需要具备的基本能力。录制核心是MediaProjection + VirtualDisplay + MediaRecorder这三件套,围绕它们展开的功能有:开始录制、停止录制、保存视频到系统相册、录制中的状态提示。
界面上我没有做过度的设计,一个简洁的主页面加上一个可拖动的悬浮窗按钮就够了。悬浮窗是录屏工具类App的标配交互,因为用户开始录制后大概率会切到其它应用,悬浮窗能保证录制控制始终可见。功能规划中还有一个容易忽略的点:录制开始前要检查存储权限,Android 10及以上版本还需要处理分区存储的适配,这个我在后面单独展开讲。
项目包结构划分成几个清晰的部分:MainActivity负责权限申请和Page跳转,RecordService负责实际录制流程并持有MediaProjection和MediaRecorder实例,FloatView负责悬浮窗的显示与拖动,最后还有一个用于保存视频到相册的工具类。所有逻辑加起来不到1000行Java代码,但对一个入门到进阶的Android项目来说,覆盖的知识点已经非常全面。
1.2 为什么用Java而不是Kotlin
这个项目我刻意选择了Java语言。不是说Kotlin不好——实际上Kotlin在现代Android开发中已经是主流,但如果查过Android面试题的话,会发现Java核心知识仍然是考察重点。对于想夯实基础的开发者,用Java写一个完整项目能同时练习Activity生命周期、Service运行机制、AIDL通信思想、多媒体API调用这些核心概念,这些内容在Java语境下讲得最清楚。
另一个现实原因是,Java写出来的录屏代码在网上能参考的资料更多,很多系统源码层面的讨论也都是基于Java展开的。遇到问题时,Stack Overflow上的Java片段大概率比Kotlin版本更接近底层调用逻辑,排查起来更顺手。当然,如果你已经对Android开发比较熟练,用Kotlin改写这套逻辑并不难,核心API完全一致。
2. 抢在录屏开始前:权限申请与前台服务的完整套路
屏幕录制器的权限体系比普通App复杂,因为它同时涉及多组运行时权限,而且每组权限在不同Android版本上的表现还不一样。我在这部分吃过不少亏,先把完整逻辑理清楚。
第一组是悬浮窗权限。Android 6.0以上悬浮窗不再属于常规运行时权限,而是需要跳转到系统设置页面单独开启。判断是否已授权的方法是Settings.canDrawOverlays(),如果返回false,就通过Settings.ACTION_MANAGE_OVERLAY_PERMISSION跳转到授权页面。这个权限不申请的话,悬浮窗根本显示不出来,而很多初学者会误以为它属于普通危险权限,结果在那里设置运行时权限申请,白白浪费时间。
第二组是存储权限。Android 9及以下需要申请WRITE_EXTERNAL_STORAGE,用于把录好的视频文件写入公共存储目录。Android 10及以上启用了分区存储,非系统应用不能再随意访问公共目录,此时需要用MediaStore.insert()把视频插入到系统媒体库中,录制完成后的文件由系统统一管理。
第三组才是MediaProjection本身的屏幕捕捉权限,它通过startActivityForResult弹出系统对话框,不是普通的运行时权限申请流程。Android 14(API 34)对这块有更严格的要求:用户每授权一次,MediaProjection实例只能使用一次,如果录制过程中需要重新创建MediaProjection,必须再次发起权限申请。这个变化对录屏工具影响很大,代码逻辑上就要做好多次授权的准备。
2.1 悬浮窗权限的申请细节
悬浮窗权限在录屏工具里其实是"控制面板"的显示前提,它和录屏本身没有直接关系,但没它,用户就没法在录屏过程中控制开始和停止。我在MainActivity里用一个checkOverlayPermission()方法统一处理这个逻辑:
private boolean checkOverlayPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); startActivity(intent); return false; } } return true; }这段代码的关键在于跳转的Uri格式。如果不带package参数,有的机型会跳到"所有应用列表",用户还得手动翻找,体验很差;带上"package:你的包名",多数手机会直接定位到对应应用,减少操作步骤。另外,从系统设置返回后不能立刻认为权限已开启,最好在onResume()里重新检查一次,因为用户可能授权也可能拒绝,代码要做双重校验才稳妥。
2.2 MediaProjection权限申请与Android 14的变化
MediaProjection的权限申请虽然不是常规运行时权限,但在流程上类似"请求授权-回调结果"的模式。我用了一个常量REQUEST_MEDIA_PROJECTION区分不同的startActivityForResult请求:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_MEDIA_PROJECTION) { if (resultCode == RESULT_OK && data != null) { // 保存用户授权结果,后续创建MediaProjection实例 mProjectionData = data; mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); initRecorder(); } else { Toast.makeText(this, "未获取屏幕录制权限", Toast.LENGTH_SHORT).show(); } } }到这里很多教程就结束了,但实际项目中这个data还有大用。MediaProjection实例的创建必须依赖这个Intent数据,而且——这是重点——这个IntentData不能直接持久化到文件,它是一个跨进程传递的Binder令牌,保存状态时如果序列化不当会导致应用崩溃。Android 14之后,每次授权得到的Intent只能用于创建一次MediaProjection,如果中途MediaProjection失效,需要重新发起授权流程。这个变化让录屏功能的代码不能默认"授权一次,终生使用"。
2.3 前台服务:录屏不被系统回收的前提
从Android 8.0(API 26)开始,应用在后台运行时会被系统限制启动Service。而录屏这种操作天然需要应用退到后台之后继续执行,这就必须使用前台服务(Foreground Service),通过Service.startForeground()在通知栏显示一条常驻通知,告知系统"这个任务正在进行中"。
前台服务除了要传入通知对象,还需要指定服务类型。Android 10及以上建议使用foregroundServiceType="mediaProjection",并在运行时通过ServiceCompat.startForeground()传递FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION。从Android 14开始,这个服务类型变成了强制要求,如果漏掉,运行时直接抛ForegroundServiceStartNotAllowedException,录制还没开始就崩了。
通知的写法也要注意:除了设置标题和文字,最好加上一个"停止录制"的Action按钮,方便用户不打开App也能直接结束录制。这个设计既是交互细节,也能避免用户找不到停止入口而强制杀进程导致视频损坏。
3. MediaRecorder与VirtualDisplay:录制的核心搭档
拿到MediaProjection权限之后,真正干活的就是MediaRecorder和VirtualDisplay了。MediaRecorder负责把视频帧编码并写入文件,VirtualDisplay则作为屏幕画面的输出目的地,把系统合成的显示内容投射到MediaRecorder的Surface上。两者之间的关系可以理解为:VirtualDisplay是水龙头,MediaRecorder是水池,水从屏幕流向文件,VirtualDisplay是通道,MediaRecorder是处理终端。
构建VirtualDisplay时有一个绕不开的问题:它需要一个Surface作为目的地。这个Surface从哪里来?MediaRecorder提供的getSurface()就是答案。这也就决定了创建顺序必须是先配置并prepare好MediaRecorder,才能创建VirtualDisplay,不能反过来。
3.1 关键参数配置:分辨率、帧率、码率怎么选
MediaRecorder的参数配置直接影响录制文件的质量和尺寸。分辨率方面我参考了屏幕实际尺寸,再用DisplayMetrics获取真实像素和密度,确保录出来的画面和屏幕显示比例一致。一个常见的错误是盲目使用1080p甚至4K分辨率,结果录出的文件巨大,帧率也不稳定。我默认使用屏幕原始分辨率,但提供"1080p/720p/原始分辨率"三档让用户选择,兼顾清晰度和文件大小。
private void configRecorder(int width, int height) { mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mMediaRecorder.setVideoSize(width, height); mMediaRecorder.setVideoFrameRate(30); mMediaRecorder.setVideoEncodingBitRate(10 * 1024 * 1024); mMediaRecorder.setAudioEncodingBitRate(128 * 1024); mMediaRecorder.setAudioSamplingRate(44100); mMediaRecorder.setOutputFile(mFilePath); mMediaRecorder.prepare(); }码率的设置也有讲究。10Mbps对大多数移动设备来说是个比较均衡的数值,画面细节保留足够多,文件体积也能接受。如果追求更小的文件,可以降到5Mbps,但动态画面会出现明显的马赛克。另一种思路是参考屏幕分辨率和帧率动态计算码率,比如width * height * 3作为基准,但实测下来对入门项目来说,固定10Mbps更省事,也更容易排查问题。
音频源的选择也需要提前明确。这里我使用的是MediaRecorder.AudioSource.MIC,即麦克风。这套方案能录到外部环境音,但录不到系统内部声音。想录制系统内音,比如游戏背景音乐或视频播放声音,需要Android 10以上的AudioPlaybackCapture方案,实现复杂度明显增加,代码量至少多出一倍,后面我会讲它的取舍。
3.2 VirtualDisplay的构建与参数详解
MediaRecorder准备完成后,就轮到VirtualDisplay上场了。它从MediaProjection实例创建,核心参数包括宽高、屏幕密度、显示标志和Surface:
mVirtualDisplay = mMediaProjection.createVirtualDisplay( "ScreenRecorderDisplay", width, height, densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mMediaRecorder.getSurface(), null, null );VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR这个标志的含义是把主屏幕内容自动镜像到虚拟显示上,这是录屏最常见的需求。如果没有这个标志,虚拟显示会是一个空白的独立屏幕,需要额外投递内容——这意味着录下来的画面将是黑的。我看到过不少新手在这里困惑"为什么录出来是黑屏",排查半天发现就是少了这个FLAG。
densityDpi参数也很关键,它决定了虚拟显示的逻辑密度。如果传0,系统会使用默认密度,可能导致录出的画面文字偏大或布局异常。正确做法是从WindowManager的DisplayMetrics中获取真实densityDpi再传入,这样录出来的画面和实际看到的完全一致。
3.3 音频采集的取舍:MIC与内录的现实选择
音频问题是我在这个项目里纠结最久的部分,市面上绝大多数录屏App在安卓上都存在"录不到内部声音"的困境。麦克风方案的优点是简单可靠,API老、兼容性好,几乎所有机型都能正常运行;缺点也很明显,录不到手机本身发出的声音,比如微信语音、游戏背景音乐。
Android 10引入的AudioPlaybackCapture提供了官方内录方案,但使用限制不算少:只能捕获允许被录制的音频,很多应用会在代码里设置不允许被录制;如果用户正在通话或使用其它录音应用,内录会失败。最麻烦的是,它是通过AudioRecord配合AudioPlaybackCaptureConfiguration实现的,和MediaRecorder的音频源不能直接用同一套逻辑,需要额外写一条音频采集管道,复杂度提升不少。
考虑到文章的定位是"把核心功能跑通",我最终选择了麦克风方案。这个选择能让整个项目保持主链路清晰,用户理解起来也更顺畅。如果你的需求确实需要内录,可以在本项目基础上扩展AudioPlaybackCapture模块,架构不受影响。
4. 录制的完整生命周期:从启动到保存的代码链路
录屏App的难点不只是"开始录"和"停止录"这两个动作,还包括整个生命周期中各种状态的管理:初始化检查、服务启动、MediaRecorder准备、VirtualDisplay创建、停止录制、资源释放、文件保存,每一步都要有对应的兜底逻辑。
我把实际录制的逻辑放在一个前台Service中,MainActivity只负责权限申请和启动Service。这样设计的好处是:即使Activity被系统回收,录制进程依然能正常运行。这也是录屏工具类App的通用架构,值得沿用到其它多媒体项目中。
4.1 启动录制:三个对象之间的顺序关系
点击悬浮窗上的"开始录制"按钮后,程序的执行顺序大致是这样的:先检查MediaProjection令牌是否存在,存在则创建MediaProjection实例;接着配置MediaRecorder并prepare;最后用MediaProjection创建VirtualDisplay。三个对象之间存在严格的先后依赖,顺序颠倒任何一个,代码都会直接崩溃或在录制的某一步静默失败。
private void startRecord() { if (mProjectionData == null) { Toast.makeText(this, "尚未获取屏幕录制权限", Toast.LENGTH_SHORT).show(); return; } mMediaProjection = mProjectionManager.getMediaProjection(RESULT_OK, mProjectionData); if (mMediaProjection == null) { Toast.makeText(this, "MediaProjection创建失败", Toast.LENGTH_SHORT).show(); return; } prepareRecorder(); createVirtualDisplay(); mMediaRecorder.start(); isRecording = true; updateFloatViewState(); }注意最后一个调用顺序:createVirtualDisplay()之后才是mMediaRecorder.start()。原因是MediaRecorder在start()之后才会真正开始接收Surface上的帧数据,如果先start()再创建VirtualDisplay,可能会出现短暂的黑屏或花屏。代码注释里我特别标出了这一点,实际测试中这个顺序确实会影响开头几帧的画面质量。
4.2 停止录制与资源释放
停止录制比启动录制更容易出问题。很多入门级的写法是直接调用mMediaRecorder.stop(),但如果在录制尚未真正开始时就调用stop(),或者录制过程中出现异常导致MediaRecorder状态不对,stop()会抛RuntimeException,直接导致App崩溃。
正确做法是先判断状态再执行停止,并用try-catch包裹stop()调用:
private void stopRecord() { if (!isRecording) return; try { mMediaRecorder.stop(); } catch (RuntimeException e) { // 录制时间过短或状态异常,stop会失败,释放资源即可 e.printStackTrace(); } finally { releaseRecorder(); saveVideoToGallery(); isRecording = false; updateFloatViewState(); } } private void releaseRecorder() { if (mVirtualDisplay != null) { mVirtualDisplay.release(); mVirtualDisplay = null; } if (mMediaProjection != null) { mMediaProjection.stop(); mMediaProjection = null; } if (mMediaRecorder != null) { mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; } }资源释放的顺序也有讲究:先释放VirtualDisplay,再停掉MediaProjection,最后释放MediaRecorder资源。这个顺序能最大程度避免底层图形缓冲区的泄漏。
4.3 适配分区存储:Android 10以上的保存方式
视频文件录完后的保存逻辑,在Android 10前后差别巨大。Android 9及以下可以直接写公共外部存储路径,例如Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) + "/ScreenRecorder/",但Android 10及以上的分区存储政策下,这条路走不通了,直接往公共目录写文件会报权限异常。
新的方式是通过MediaStore把视频插入系统媒体库,指定文件名为"ScreenRecord_时间戳.mp4",MIME类型为"video/mp4":
private void saveVideoToGallery() { ContentValues values = new ContentValues(); values.put(MediaStore.Video.Media.DISPLAY_NAME, "ScreenRecord_" + System.currentTimeMillis() + ".mp4"); values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4"); values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES + "/ScreenRecorder"); values.put(MediaStore.Video.Media.IS_PENDING, 1); Uri collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); Uri item = getContentResolver().insert(collection, values); try (OutputStream out = getContentResolver().openOutputStream(item)) { FileInputStream in = new FileInputStream(mFilePath); byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer)) > 0) { out.write(buffer, 0, len); } in.close(); } catch (IOException e) { e.printStackTrace(); } values.clear(); values.put(MediaStore.Video.Media.IS_PENDING, 0); getContentResolver().update(item, values, null, null); }IS_PENDING字段是MediaStore提供的一个标记位,表示文件还在写入中。写入完成后必须把它更新为0,否则系统图库不会立刻显示这个文件。这段代码我是在真机上反复测过的,一次踩坑就是因为忘掉更新IS_PENDING,导致视频文件存在但图库里死活不显示。
5. 实测中最容易翻车的几个场景
这个项目从写完到稳定运行,我大概花了三个晚上集中排查问题。如果把踩过的坑列个清单,以下几类最值得拿出来说说,因为它们都是那种不跑到真机上根本发现不了的隐藏问题。
5.1 屏幕旋转的"死亡陷阱":录出的视频方向错了
第一次完整录制成功时,我拿着手机从竖屏旋转到横屏,结果录出来的视频在播放器里是歪的。原因是VirtualDisplay创建时绑定的是当时的屏幕宽高,旋转之后屏幕的物理宽高对调了,但VirtualDisplay不会自动重新创建。这导致画面矩形和真实屏幕方向不一致,录出来的内容自然不对。
解决思路有两个:一是锁死录制方向的屏幕方向,在录制过程中保持当前方向不变,这是很多轻量录屏工具的做法,实现简单但体验受限;二是监听OrientationEventListener变化,在方向变化时销毁旧VirtualDisplay并用新尺寸重新创建,同时要让MediaRecorder保持延续状态。第二种方案我在主版本中实现了,要点是监听回调里不能直接重建VirtualDisplay,需要先暂停MediaRecorder,更新宽高,再重新prepare,整套流程串行执行,否则会出现闪退。
5.2 MediaRecorder的start()偶发异常
多台设备测试下来,偶发概率最高的问题是start()抛IllegalStateException或prepare()时的IOException。排除权限和文件路径问题后,最常见的原因是MediaRecorder实例被复用了。MediaRecorder这个类比较特殊,一次生命周期只能完成一次录制,如果第一次录制结束后没有调用reset()和release(),第二次创建新MediaRecorder时就会出现各种奇怪状态。
我的做法是每次录制都new一个全新的MediaRecorder实例,录制结束后立即释放旧实例,绝不复用。这个习惯延伸到项目中所有带底层资源的对象,比如Camera、MediaPlayer,都是一样的原则:一次一用,用完释放。
5.3 国产ROM的后台限制
代码跑在Pixel模拟器上完全没问题,一上国产手机就各种"意外"——悬浮窗消失、录制被系统中断、通知不显示。这不是代码逻辑的错,而是国产ROM自家的后台管理策略在"帮忙"省电。以MIUI、ColorOS、HarmonyOS为代表的系统,默认会限制应用的后台活动,尤其对"用户不常打开"的应用下手更狠。
应对办法包括:引导用户将App加入电池优化的白名单、在应用内申请"后台弹出界面"权限、提醒用户锁定最近任务卡片。这些操作无法用代码强制执行,只能在App里添加一个"使用引导"页面,把每一步截图说明清楚。这个功能虽然不算技术难点,但对最终用户体验的影响比任何一行代码都大。
5.4 服务类型未声明导致的前台服务崩溃
还有一个小坑:Android 10开始,前台服务如果运行在后台时没有正确设置服务类型,系统会直接抛ForegroundServiceStartNotAllowedException。MediaProjection对应的类型是mediaProjection,清单文件里需要声明:
<service android:name=".RecordService" android:enabled="true" android:exported="false" android:foregroundServiceType="mediaProjection" />同时,在代码中启动前台服务时也要带上类型。Android 14(targetSdk 34)下,这个约束变成了强制项,没有声明就启动,崩溃概率接近百分百。这类问题报错信息往往出现在系统日志里,新手很难从堆栈中找到头绪,所以特意拿出来说一下。
6. 完整项目结构总结
整个项目的目录结构并不复杂,但每部分承担的职责非常清晰:
app/src/main/java/com/example/screenrecorder/ ├── MainActivity.java // 权限申请、页面交互、启动服务 ├── RecordService.java // 前台服务,录制核心逻辑 ├── FloatViewManager.java // 悬浮窗的创建、拖动、状态更新 └── MediaStoreHelper.java // 视频保存到系统相册的工具类MainActivity只做权限相关的检查和跳转,把录制任务交给RecordService。FloatViewManager负责悬浮窗的显示和拖动,以及把点击事件回调给Service层。这样的分层逻辑让代码的可读性很高,后期想要增加功能,比如内录、暂停录制,都能在对应模块里完成扩展,而不需要大改主流程。
这个项目用到的核心知识点包括:MediaProjection API的授权流程与实例创建、VirtualDisplay的参数与构建逻辑、MediaRecorder的音视频编码配置、前台服务与服务类型的适配、分区存储下的MediaStore写入、悬浮窗权限的申请与窗口管理。任何一个点单独拎出来都是Android面试题中的常见考点,组合在一个完整项目里理解,比死记硬背八股文要深刻得多。如果你正在准备面试,我建议照着这个思路从头写一遍,比刷一百道面试题都管用。
最后说一个我自己的体会:录屏工具看起来是个小项目,但它几乎覆盖了Android多媒体、系统服务、权限机制、存储适配的所有核心知识点,非常适合作为从入门迈向进阶的练手项目。我在实际开发中还遇到过一个很奇怪的现象——有些机型在录屏过程中如果用户插拔耳机,MediaRecorder的音频源会突然丢失,导致录制文件没有声音轨。这类问题只能靠大量真机测试去发现和规避,也侧面说明录屏这种系统级能力,代码之外的设备适配经验同样重要。