Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces an asynchronous screenshot mechanism via a new ScreenshotChannel (flutter/screenshot), deprecating the existing synchronous getBitmap APIs in FlutterJNI and FlutterRenderer. On the Android side, FlutterRenderer now supports getBitmapAsync to request view captures. On the Dart side, RendererBinding handles these requests by converting the root OffsetLayer to an image and returning its raw RGBA bytes. Feedback on these changes suggests specifying the device pixel ratio in toImage to maintain high-DPI image quality, catching OutOfMemoryError during bitmap creation to prevent crashes, and restricting the visibility of the channel field in ScreenshotChannel to private to improve encapsulation.
| return null; | ||
| } | ||
|
|
||
| final ui.Image image = await rootLayer.toImage(bounds); |
There was a problem hiding this comment.
The current implementation of rootLayer.toImage(bounds) does not specify a pixelRatio, which defaults to 1.0. This will cause screenshots to be captured at logical pixel resolution rather than the device's physical pixel resolution. On high-DPI devices (e.g., with a device pixel ratio of 2.0 or 3.0), this results in a significant loss of image quality and resolution compared to the original synchronous getBitmap() API. Specifying pixelRatio: targetView.flutterView.devicePixelRatio ensures the captured image matches the physical screen resolution.
final ui.Image image = await rootLayer.toImage(bounds, pixelRatio: targetView.flutterView.devicePixelRatio);| Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); | ||
| bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(rgbaBytes, 0, (int) expectedBytes)); | ||
| callback.accept(bitmap); | ||
| } catch (RuntimeException e) { |
There was a problem hiding this comment.
Creating a large Bitmap via Bitmap.createBitmap can throw an OutOfMemoryError if the device is low on memory or the screenshot dimensions are extremely large. Since OutOfMemoryError is an Error (not a RuntimeException), it will bypass the current catch (RuntimeException e) block and crash the application. Catching OutOfMemoryError alongside RuntimeException allows the application to fail gracefully and return null to the callback.
| } catch (RuntimeException e) { | |
| } catch (OutOfMemoryError | RuntimeException e) { |
| // Four bytes per pixel for premultiplied RGBA_8888 / ARGB_8888 buffers. | ||
| private static final int BYTES_PER_PIXEL_RGBA_8888 = 4; | ||
|
|
||
| @NonNull public final MethodChannel channel; |
There was a problem hiding this comment.
The channel field is not accessed outside of the ScreenshotChannel class. To improve encapsulation and adhere to standard Java best practices, it should be declared as private instead of public.
| @NonNull public final MethodChannel channel; | |
| @NonNull private final MethodChannel channel; |
References
- Fields should be declared with the most restrictive access level possible to enforce encapsulation.
|
This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
Deprecates the synchronous
FlutterRenderer.getBitmap()andFlutterJNI.getBitmap()APIs in the Android embedder, which block the Android UI thread waiting onRasterizer::Screenshotacross the platform/raster threads, and introduces an asynchronous replacement:FlutterRenderer.getBitmapAsync(...).Summary of Changes
flutter/screenshot):SystemChannels.screenshot(MethodChannel('flutter/screenshot', StandardMethodCodec())) inpackages/flutter/lib/src/services/system_channels.dart._handleScreenshotMethodCallinRendererBinding.initInstances()(packages/flutter/lib/src/rendering/binding.dart) to handleScreenshot.captureView(int viewId):RenderViewmatchingviewIdand reads its compositedOffsetLayer.OffsetLayer.toImage(Offset.zero & targetView.size, pixelRatio: targetView.configuration.devicePixelRatio)and encodes raw RGBA pixels (ui.ImageByteFormat.rawRgba), returning{'width': int, 'height': int, 'bytes': Uint8List}without blocking the Android UI thread.ScreenshotChannel&FlutterRenderer):ScreenshotChannel(io.flutter.embedding.engine.systemchannels.ScreenshotChannel) and wires it inFlutterEngine.@Deprecatedannotations and migration Javadocs toFlutterRenderer.getBitmap()andFlutterJNI.getBitmap().FlutterRenderer.getBitmapAsync(@NonNull Consumer<Bitmap> callback)andFlutterRenderer.getBitmapAsync(long viewId, @NonNull Consumer<Bitmap> callback).packages/flutter/test/rendering/screenshot_channel_test.dart.ScreenshotChannelTest.javaandFlutterRendererTest.java.Fixes #195720
Related RFC: flutter/rfc#41
Pre-launch Checklist
///).