将屏幕截图保存到图片目录在 Java 的前台服务中不起作用

问题描述 投票:0回答:1

我有一个 React Native 项目。该应用程序应捕获用户屏幕和前台活动。 React Native无法做到这一点,所以我使用Native Modules来编写java。我制作了两个java文件,分别是

ScreenCaptureModule.java
ScreenCaptureService.java
。在此应用程序中,我到达:当用户打开应用程序时,他将看到“开始屏幕捕获”按钮。当用户按下此按钮时,它将显示一个如下所示的对话框:

 AlertDialog.Builder builder = new AlertDialog.Builder(currentActivity);
            builder.setTitle("Welcome, user!")
                    .setMessage("You are about to use my app, which captures your screen every one second. Are you willing to proceed?")
                    .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Intent serviceIntent = new Intent(currentActivity, ScreenCaptureService.class);
                            currentActivity.startService(serviceIntent);
                            currentActivity.finish();
                        }
                    })
                    .setNegativeButton("No", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            // User declined, do nothing
                        }
                    })
                    .show(); 

如果用户按“是”,应用程序将关闭,并显示如下通知:

 private void showNotification() {
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if (notificationManager == null) {
            return;
        }

        String channelId = "screen_capture_channel";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId, "Screen Capture", NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
                .setContentTitle("Screen Capture")
                .setContentText("Your screen is currently being captured. You can find the captures in the Pictures Directory.")
                .setSmallIcon(R.drawable.ic_notification)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);

        notificationManager.notify(1, builder.build());
    } 

显示通知后,它应该每秒捕获一次屏幕并将其保存在图片目录中。它显示通知,但不会将其保存在图片目录中。这就是问题。现在 我会给你

ScreenCapture.java
ScreenCaptureService.java
React Native
的代码,看看我如何调用这些方法。再次强调:问题是当应用程序在前台启动时,它不会捕获图像或将图像保存在图片目录中。它只显示通知。

ScreenCaptureModule.java:

public class ScreenCaptureModule extends ReactContextBaseJavaModule {
    private static final int REQUEST_MEDIA_PROJECTION = 1;
    private final MediaProjectionManager mediaProjectionManager;
    private ImageReader imageReader;
    private int screenshotCounter = 1;

    private final ReactApplicationContext reactContext;

    public ScreenCaptureModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
        mediaProjectionManager = (MediaProjectionManager) reactContext.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        reactContext.addActivityEventListener(activityEventListener);
    }

    @NonNull
    @Override
    public String getName() {
        return "ScreenCaptureModule";
    }

    @ReactMethod
    public void startScreenCapture() {
        Activity currentActivity = getCurrentActivity();
        if (currentActivity != null) {
           // Here is the show of dialog box when click on start capture button
        }
    }


    private final ActivityEventListener activityEventListener = new BaseActivityEventListener() {
      @Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_MEDIA_PROJECTION && resultCode == Activity.RESULT_OK) {
        MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);

        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager windowManager = (WindowManager) getReactApplicationContext().getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(metrics);
        @SuppressLint("WrongConstant") ImageReader imageReader = ImageReader.newInstance(metrics.widthPixels, metrics.heightPixels, PixelFormat.RGBA_8888, 1);
        VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay(
                "ScreenCapture",
                metrics.widthPixels,
                metrics.heightPixels,
                metrics.densityDpi,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                imageReader.getSurface(),
                null,
                null
        );

        imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Image image = null;
                FileOutputStream fos = null;
                Bitmap bitmap = null;

                try {
                    image = reader.acquireLatestImage();
                    if (image != null) {
                        Image.Plane[] planes = image.getPlanes();
                        ByteBuffer buffer = planes[0].getBuffer();
                        int pixelStride = planes[0].getPixelStride();
                        int rowStride = planes[0].getRowStride();
                        int rowPadding = rowStride - pixelStride * metrics.widthPixels;

                        bitmap = Bitmap.createBitmap(metrics.widthPixels + rowPadding / pixelStride, metrics.heightPixels, Bitmap.Config.ARGB_8888);
                        bitmap.copyPixelsFromBuffer(buffer);

                        File screenshotsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
                        File screenshotFile = new File(screenshotsDir, "screenshot" + screenshotCounter + ".png");
                        screenshotCounter++;
                        fos = new FileOutputStream(screenshotFile);
                        bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
                        fos.flush(); // Add this line to flush the data to the file

                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (fos != null) {
                        try {
                            fos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (bitmap != null) {
                        bitmap.recycle();
                    }
                    if (image != null) {
                        image.close();
                    }
                    if (virtualDisplay != null) {
                        virtualDisplay.release();
                    }
                    if (mediaProjection != null) {
                        mediaProjection.stop();
                    }
                }
            }
        }, null);
    }
}

    };

    @SuppressLint("WrongConstant")
    private VirtualDisplay createVirtualDisplay(MediaProjection mediaProjection) {
        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager windowManager = (WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(metrics);

        imageReader = ImageReader.newInstance(
                metrics.widthPixels,
                metrics.heightPixels,
                PixelFormat.RGBA_8888,
                1
        );

        return mediaProjection.createVirtualDisplay(
                "ScreenCapture",
                metrics.widthPixels,
                metrics.heightPixels,
                metrics.densityDpi,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                imageReader.getSurface(),
                null,
                null
        );
    }
}

ScreenCaptureService.java:


public class ScreenCaptureService extends Service {
    private static final int SCREEN_CAPTURE_INTERVAL = 1000; // 1 second
    private Handler handler;
    private Runnable captureRunnable;
    private ImageReader imageReader;
    private int screenshotCounter = 1;

    private VirtualDisplay virtualDisplay;


    @Override
    public void onCreate() {
        super.onCreate();
        handler = new Handler();
        captureRunnable = new Runnable() {
            @Override
            public void run() {
                captureScreen();
                handler.postDelayed(this, SCREEN_CAPTURE_INTERVAL);
            }
        };
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        handler.post(captureRunnable);
        showNotification();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        handler.removeCallbacks(captureRunnable);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private void captureScreen() {
        imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Image image = null;
                FileOutputStream fos = null;
                Bitmap bitmap = null;

                try {
                    image = reader.acquireLatestImage();
                    if (image != null) {
                        Image.Plane[] planes = image.getPlanes();
                        ByteBuffer buffer = planes[0].getBuffer();
                        int pixelStride = planes[0].getPixelStride();
                        int rowStride = planes[0].getRowStride();
                        int rowPadding = rowStride - pixelStride * image.getWidth();

                        bitmap = Bitmap.createBitmap(image.getWidth() + rowPadding / pixelStride, image.getHeight(), Bitmap.Config.ARGB_8888);
                        bitmap.copyPixelsFromBuffer(buffer);

                        File screenshotsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
                        File screenshotFile = new File(screenshotsDir, "screenshot" + screenshotCounter + ".png");
                        screenshotCounter++;
                        fos = new FileOutputStream(screenshotFile);
                        bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
                        fos.flush();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (fos != null) {
                        try {
                            fos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (bitmap != null) {
                        bitmap.recycle();
                    }
                    if (image != null) {
                        image.close();
                    }
                    if (virtualDisplay != null) {
                        virtualDisplay.release();
                    }
                }
            }
        }, null);
    }

    private void showNotification() {
      Here is the Notification
    }
}

React Native 代码:

const {ScreenCaptureModule} = NativeModules
const App = () => {
  const startScreenCapture = () => {
    ScreenCaptureModule.startScrerenCapture();
  };
  return (
    <View>
      <Button onPress={startScreenCapture} title="Start Screen Capture" />

    </View>
  )
}

export default App

这是我的项目的完整代码。我认为问题在于,虽然通知功能按预期工作,但没有将捕获存储在指定目录中,因为我在 ScreenCaptureService.java 中的 captureScreen 函数中出现问题。

注意:我授予了 WRITE_EXTERNAL_PERMISSION。我会给你我的 Manifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:allowBackup="false"
      android:theme="@style/AppTheme">
      
      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
      </activity>

        <service android:name="com.test.ScreenCaptureService" android:exported="false" />
    </application>
</manifest>

java react-native screen-capture foreground-service react-native-bridge
1个回答
0
投票

我检查了您的代码,似乎

MediaProjection
实例未正确传递到您的
ScreenCaptureService
,因此屏幕捕获无法启动。

在您的

ScreenCaptureModule.java
中,您正在创建
VirtualDisplay
,它启动捕获并在同一范围内释放。因此,您的捕获过程在您选择开始屏幕录制后立即结束。

更好的方法是不要立即启动然后停止

MediaProjection
,而是只要您需要捕获屏幕截图就保持运行:

在您的

ScreenCaptureService.java
中,添加一个静态方法来启动服务并在 Intent 中将
MediaProjection
数据作为额外参数传递:

public class ScreenCaptureService extends Service {
    public static final String EXTRA_RESULT_CODE = "resultCode";
    public static final String EXTRA_RESULT_DATA = "resultData";

    public static void start(Context context, int resultCode, Intent resultData) {
        Intent i = new Intent(context, ScreenCaptureService.class);
        
        i.putExtra(EXTRA_RESULT_CODE, resultCode);
        i.putExtra(EXTRA_RESULT_DATA, resultData);
        
        context.startService(i);
    }
    ...
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE, 0);
        Intent resultData = intent.getParcelableExtra(EXTRA_RESULT_DATA);
        
        // Initialize MediaProjection using data received from ScreenCaptureModule
        MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, resultData);
        
        // Initialize your VirtualDisplay and ImageReader here

        // Don't forget to stop MediaProjection when the Service is destroyed
    }
    ...
}

在您的

ScreenCaptureModule.java
中,将
currentActivity.startService(serviceIntent);
替换为
ScreenCaptureService.start(currentActivity, resultCode, data);:

// ...您现有的对话框代码... .

setPositiveButton("Yes", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        ScreenCaptureService.start(currentActivity, resultCode, data)
        currentActivity.finish();
    }
})

// ... 在您的

module
中,请勿在
MediaProjection
内释放
VirtualDisplay
Image
onImageAvailable
。一旦您的服务停止,捕获就应该停止:

@Override
public void onDestroy() {
    super.onDestroy();
    handler.removeCallbacks(captureRunnable);
    
    // Stop MediaProjection right here:
    mediaProjection.stop();

    // Release VirtualDisplay if it's not already released:
    if (virtualDisplay != null) {
        virtualDisplay.release();
    }
}

按照这些步骤操作应该可以使

MediaProjection
保持活动状态并在
ScreenCaptureService
的生命周期内进行捕获。使用
ScreenCaptureService's
onStartCommand 方法中的正确代码,您应该能够在 Service 实例化后立即开始捕获屏幕截图,并将它们存储在 Pictures 目录中。

© www.soinside.com 2019 - 2024. All rights reserved.