我有一个 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
的代码,看看我如何调用这些方法。再次强调:问题是当应用程序在前台启动时,它不会捕获图像或将图像保存在图片目录中。它只显示通知。
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
);
}
}
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
}
}
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>
我检查了您的代码,似乎
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 目录中。