You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Capacitor插件的Service中使用createScreenCaptureIntent实现录屏?

解决Capacitor插件中无UI情况下实现MediaProjection录屏的问题

核心痛点很明确:startActivityForResult必须依附Activity上下文才能调用,而Service本身无法直接处理带返回结果的Intent请求。我们可以用无UI的透明Activity做权限中转——它只负责唤起系统录屏授权界面、接收授权结果,然后把关键的MediaProjection实例传递给后台Service执行录屏,全程不会干扰用户体验。

步骤1:创建透明权限中转Activity

这个Activity完全没有UI,仅作为权限请求的中转站。

  1. 先在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>
  1. 实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 06:51:33