如何在Capacitor插件的Service中使用createScreenCaptureIntent实现录屏?
解决Capacitor插件中无UI情况下实现MediaProjection录屏的问题
核心痛点很明确:startActivityForResult必须依附Activity上下文才能调用,而Service本身无法直接处理带返回结果的Intent请求。我们可以用无UI的透明Activity做权限中转——它只负责唤起系统录屏授权界面、接收授权结果,然后把关键的MediaProjection实例传递给后台Service执行录屏,全程不会干扰用户体验。
步骤1:创建透明权限中转Activity
这个Activity完全没有UI,仅作为权限请求的中转站。
- 先在
AndroidManifest.xml中注册该Activity,设置透明主题并隐藏最近任务:
<activity android:name=".TransparentScreenCaptureActivity" android:theme="@style/Theme.Transparent" android:excludeFromRecents="true" android:launchMode="singleInstance"/>
然后在res/values/styles.xml定义透明主题:
<style name="Theme.Transparent" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowContentOverlay">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:windowIsFloating">true</item> <item name="android:backgroundDimEnabled">false</item> </style>
- 实现Activity的核心逻辑,通过静态回调传递授权结果:
public class TransparentScreenCaptureActivity extends AppCompatActivity { private static final int REQUEST_MEDIA_PROJECTION = 100; private MediaProjectionManager mMediaProjectionManager; // 静态回调,用于将授权结果传递给插件或Service public static OnMediaProjectionResultListener resultListener; public interface OnMediaProjectionResultListener { void onResult(MediaProjection mediaProjection); void onError(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE); // 唤起系统录屏授权界面 Intent captureIntent = mMediaProjectionManager.createScreenCaptureIntent(); startActivityForResult(captureIntent, REQUEST_MEDIA_PROJECTION); } @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 mediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data); if (mediaProjection != null && resultListener != null) { resultListener.onResult(mediaProjection); } else { if (resultListener != null) resultListener.onError(); } } else { if (resultListener != null) resultListener.onError(); } // 处理完结果立即销毁Activity,避免残留 finish(); } } @Override protected void onDestroy() { super.onDestroy(); // 清空回调,防止内存泄漏 resultListener = null; } }
步骤2:在Capacitor插件中触发权限请求并接收结果
在你的Capacitor插件类里,启动透明Activity并监听授权结果,拿到MediaProjection后启动后台Service:
@CapacitorPlugin(name = "ScreenRecorder") public class ScreenRecorderPlugin extends Plugin { @PluginMethod public void startRecording(PluginCall call) { // 设置授权结果回调 TransparentScreenCaptureActivity.resultListener = new TransparentScreenCaptureActivity.OnMediaProjectionResultListener() { @Override public void onResult(MediaProjection mediaProjection) { // 启动后台录屏Service,传递MediaProjection实例 Intent serviceIntent = new Intent(getContext(), ScreenRecorderService.class); serviceIntent.putExtra("mediaProjection", mediaProjection); // Android O及以上需用前台Service if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { getContext().startForegroundService(serviceIntent); } else { getContext().startService(serviceIntent); } call.resolve(); } @Override public void onError() { call.reject("Failed to get screen recording permission"); } }; // 启动透明Activity(非Activity上下文需加NEW_TASK flag) Intent intent = new Intent(getContext(), TransparentScreenCaptureActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getContext().startActivity(intent); } @PluginMethod public void stopRecording(PluginCall call) { // 停止录屏Service Intent serviceIntent = new Intent(getContext(), ScreenRecorderService.class); getContext().stopService(serviceIntent); call.resolve(); } }
步骤3:实现后台录屏Service
在Service中接收MediaProjection,完成录屏逻辑的初始化与执行:
public class ScreenRecorderService extends Service { private MediaProjection mMediaProjection; private VirtualDisplay mVirtualDisplay; private MediaRecorder mMediaRecorder; @Override public void onCreate() { super.onCreate(); // Android O及以上必须启动前台Service,避免被系统回收 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel("screen_recorder", "Screen Recorder", NotificationManager.IMPORTANCE_LOW); NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); manager.createNotificationChannel(channel); Notification notification = new NotificationCompat.Builder(this, "screen_recorder") .setContentTitle("Screen Recording") .setContentText("Recording in progress") .setSmallIcon(R.drawable.ic_notification) .build(); startForeground(1, notification); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null) { mMediaProjection = (MediaProjection) intent.getParcelableExtra("mediaProjection"); if (mMediaProjection != null) { startCapturing(); } } return START_STICKY; } private void startCapturing() { // 初始化MediaRecorder,参考你提到的项目逻辑 DisplayMetrics metrics = getResources().getDisplayMetrics(); int width = metrics.widthPixels; int height = metrics.heightPixels; int dpi = metrics.densityDpi; mMediaRecorder = new MediaRecorder(); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mMediaRecorder.setVideoEncodingBitRate(5 * 1024 * 1024); mMediaRecorder.setVideoFrameRate(30); mMediaRecorder.setVideoSize(width, height); mMediaRecorder.setOutputFile(getOutputFilePath()); try { mMediaRecorder.prepare(); } catch (IOException e) { e.printStackTrace(); stopSelf(); return; } // 创建VirtualDisplay关联MediaProjection与MediaRecorder Surface surface = mMediaRecorder.getSurface(); mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenRecorder", width, height, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null); mMediaRecorder.start(); } private String getOutputFilePath() { // 返回录屏文件存储路径 File dir = getExternalFilesDir(Environment.DIRECTORY_MOVIES); return new File(dir, "screen_record_" + System.currentTimeMillis() + ".mp4").getAbsolutePath(); } @Override public void onDestroy() { super.onDestroy(); // 释放所有资源 if (mMediaRecorder != null) { try { mMediaRecorder.stop(); mMediaRecorder.release(); } catch (Exception e) { e.printStackTrace(); } } if (mVirtualDisplay != null) { mVirtualDisplay.release(); } if (mMediaProjection != null) { mMediaProjection.stop(); } } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
关键注意事项
- 透明Activity的生命周期:处理完结果立即调用
finish(),配合excludeFromRecents确保不会留在后台或最近任务列表。 - 前台Service要求:Android O及以上版本,后台Service极易被系统回收,必须转为前台Service并显示低优先级通知。
- 内存泄漏防范:透明Activity销毁时清空静态回调,避免持有插件或Service的引用。
- 权限提前申请:确保已申请
WRITE_EXTERNAL_STORAGE(存储录屏文件)、RECORD_AUDIO(如需录声音)等权限,Capacitor插件中也要做好权限请求逻辑。
内容的提问来源于stack exchange,提问作者Datenshi
相关产品推荐
相关产品推荐

