初始化MediaProjection API时避免应用返回主屏幕的问题咨询
问题描述
我开发了一款自定义启动器应用,集成了基于MediaProjection API的录屏功能。此前功能运行正常,但现在启动录屏时,应用会自动返回主屏幕。我需要实现录屏覆盖当前打开的应用,而非切回主屏幕,求解决方案。
相关代码
//ScreenRecordingService.java package com.Services; import static android.app.Activity.RESULT_OK; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.media.projection.MediaProjection; import android.media.projection.MediaProjectionManager; import android.os.Build; import android.os.IBinder; import android.util.Log; import android.view.Surface; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import com.mdmtesting.hiveliveviewer.R; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ScreenRecordingService extends Service { private static final String VIDEO_MIME_TYPE = "video/avc"; private static final int VIDEO_WIDTH = 1280; private static final int VIDEO_HEIGHT = 720; private static final int VIDEO_BIT_RATE = 5000000; private static final int VIDEO_FRAME_RATE = 30; private static final int VIDEO_I_FRAME_INTERVAL = 1; private static final String TAG = "ScreenRecordingService"; private static final String CHANNEL_ID = "screen_recording_channel"; private MediaProjectionManager mediaProjectionManager; private MediaProjection mediaProjection; private MediaCodec mEncoder; private Surface mInputSurface; private MediaCodec.BufferInfo mBufferInfo; private ScheduledExecutorService scheduler; private WebSocketService webSocketService; private boolean isBound = false; @Override public void onCreate() { super.onCreate(); // Create notification channel for Android O and above if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "Screen Recording Service", NotificationManager.IMPORTANCE_LOW ); NotificationManager manager = getSystemService(NotificationManager.class); if (manager != null) { manager.createNotificationChannel(channel); } } } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "Service started"); // Start the service as a foreground service startForegroundService(); Intent webSocketIntent = new Intent(this, WebSocketService.class); bindService(webSocketIntent, connection, Context.BIND_AUTO_CREATE); if (intent != null && intent.hasExtra("resultData")) { Intent resultData = intent.getParcelableExtra("resultData"); if (resultData != null) { startScreenRecording(resultData); } else { Log.e(TAG, "No resultData in Intent"); stopSelf(); } } else { Log.e(TAG, "Intent does not contain resultData"); stopSelf(); } return START_STICKY; } private void startScreenRecording(Intent resultData) { mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE); mediaProjection = mediaProjectionManager.getMediaProjection(RESULT_OK, resultData); if (mediaProjection == null) { Log.e(TAG, "MediaProjection is null"); stopSelf(); return; } try { setupMediaCodec(); createVirtualDisplay(); } catch (IOException e) { Log.e(TAG, "Error setting up MediaCodec", e); stopSelf(); } } private void setupMediaCodec() throws IOException { mBufferInfo = new MediaCodec.BufferInfo(); mEncoder = MediaCodec.createEncoderByType(VIDEO_MIME_TYPE); MediaFormat format = MediaFormat.createVideoFormat(VIDEO_MIME_TYPE, VIDEO_WIDTH, VIDEO_HEIGHT); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_BIT_RATE, VIDEO_BIT_RATE); format.setInteger(MediaFormat.KEY_FRAME_RATE, VIDEO_FRAME_RATE); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, VIDEO_I_FRAME_INTERVAL); mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mInputSurface = mEncoder.createInputSurface(); mEncoder.start(); } private void createVirtualDisplay() { mediaProjection.createVirtualDisplay( "ScreenRecordingService", VIDEO_WIDTH, VIDEO_HEIGHT, getResources().getDisplayMetrics().densityDpi, android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mInputSurface, null, null); scheduler = Executors.newScheduledThreadPool(1); scheduler.scheduleWithFixedDelay(this::encodeAndSendData, 0, 1, TimeUnit.SECONDS); // Adjusted to 1 second } private void encodeAndSendData() { int outputBufferIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, 10000); // Adjusted timeout to 10 seconds while (outputBufferIndex >= 0) { ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex); if (outputBuffer != null) { byte[] outData = new byte[mBufferInfo.size]; outputBuffer.get(outData); outputBuffer.clear(); if (isH264(outData) && isBound) { //Log.d(TAG, "encodeAndSendData: WebSocketService " + outData.length + " bytes"); webSocketService.sendData(outData); } mEncoder.releaseOutputBuffer(outputBufferIndex, false); } outputBufferIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, 10000); } } private boolean isH264(byte[] data) { if (data.length < 4) return false; return (data[0] == 0 && data[1] == 0 && (data[2] == 1 || (data[2] == 0 && data[3] == 1))); } private void startForegroundService() { // Create a notification NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("Screen Recording") .setContentText("Recording in progress...") .setSmallIcon(R.mipmap.ic_launcher) // Use a proper icon for your notification .setPriority(NotificationCompat.PRIORITY_LOW); // Start the service in the foreground startForeground(1, builder.build()); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { Log.d(TAG, "Service destroyed"); if (mediaProjection != null) { Log.d(TAG, "Stopping MediaProjection"); mediaProjection.stop(); } if (mEncoder != null) { Log.d(TAG, "Stopping MediaCodec"); mEncoder.stop(); mEncoder.release(); } if (scheduler != null) { Log.d(TAG, "Shutting down scheduler"); scheduler.shutdown(); } if (isBound) { unbindService(connection); isBound = false; } super.onDestroy(); } private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { WebSocketService.LocalBinder binder = (WebSocketService.LocalBinder) service; webSocketService = binder.getService(); isBound = true; } @Override public void onServiceDisconnected(ComponentName name) { isBound = false; } }; }
解决方案
1. 修复权限请求后的Activity焦点问题
在发起MediaProjection权限请求的启动器Activity中,重写onActivityResult时,确保当前Activity保持前台状态,不要直接finish:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == SCREEN_RECORD_REQUEST_CODE && resultCode == RESULT_OK) { // 启动录屏服务 Intent serviceIntent = new Intent(this, ScreenRecordingService.class); serviceIntent.putExtra("resultData", data); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent); } else { startService(serviceIntent); } // 强制将当前Activity拉回前台,避免系统切回主屏幕 Intent launcherIntent = new Intent(this, YourLauncherActivity.class); launcherIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(launcherIntent); } }
2. 调整虚拟显示器创建参数
在createVirtualDisplay方法中,添加VIRTUAL_DISPLAY_FLAG_PRESENTATION标志,避免虚拟显示器创建时干扰当前显示焦点:
mediaProjection.createVirtualDisplay( "ScreenRecordingService", VIDEO_WIDTH, VIDEO_HEIGHT, getResources().getDisplayMetrics().densityDpi, android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR | android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, mInputSurface, null, null);
3. 优化通知配置
在startForegroundService方法中,添加setOngoing(true),让录屏通知保持常驻且不抢占窗口焦点:
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("Screen Recording") .setContentText("Recording in progress...") .setSmallIcon(R.mipmap.ic_launcher) .setPriority(NotificationCompat.PRIORITY_LOW) .setOngoing(true); // 常驻通知,避免系统因通知切换界面
4. 检查启动器Activity的Manifest配置
确保启动器Activity的启动模式和属性正确,避免被系统自动切换:
<activity android:name=".YourLauncherActivity" android:launchMode="singleTask" android:clearTaskOnLaunch="false" android:stateNotNeeded="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.HOME" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
内容的提问来源于stack exchange,提问作者maranR
相关产品推荐
相关产品推荐

