mapbox / mapbox-maps-flutter

Interactive, thoroughly customizable maps for Flutter powered by Mapbox Maps SDK
https://www.mapbox.com/mobile-maps-sdk
Other
289 stars 119 forks source link

iOS 17 Memory Leak #380

Closed MeintrupStefan closed 9 months ago

MeintrupStefan commented 9 months ago

We experience a memory leak on iOS that eventually leads to the app crashing

Once we display a MapWidget, the Ram consumption increases by 100+ MB. However, this happens only on iOS and not on Android.

The problem is especially prominent, if we render multiple mapWidgets in a ListView. A screen that contains 6 mapWidgets increased the RAM usage by 300MB. Just a couple of clicks are able to crash the app completely because the RAM usage exceeds 2GB.

Widget we display:

    MapWidget(
            key: key,
            resourceOptions: ResourceOptions(
              accessToken:
              "....",
            ),
            cameraOptions: CameraOptions(
              center: Point(coordinates: position + Position(-0.9 * (pow(6.5 - zoom, 2) + 1), 0)).toJson(),
              zoom: zoom,
            ),
            styleUri: "mapbox://styles/.....",
            // Disable taps
            onTapListener: (_) {},
            onMapCreated: (map) => map
              ..scaleBar.updateSettings(ScaleBarSettings(enabled: false))
              ..compass.updateSettings(CompassSettings(enabled: false))
              ..gestures.updateSettings(GesturesSettings(
                quickZoomEnabled: false,
                rotateEnabled: false,
                scrollEnabled: false,
                pitchEnabled: false,
                pinchToZoomEnabled: false,
                doubleTapToZoomInEnabled: false,
                doubleTouchToZoomOutEnabled: false,
                pinchPanEnabled: false,
                pinchToZoomDecelerationEnabled: false,
                rotateDecelerationEnabled: false,
                scrollDecelerationEnabled: false,
                simultaneousRotateAndPinchToZoomEnabled: false,
              ))
              ..logo.updateSettings(LogoSettings(
                marginBottom: 10,
                marginLeft: 10,
              ))
              ..attribution.updateSettings(AttributionSettings(
                position: OrnamentPosition.TOP_RIGHT,
                iconColor: Palette.textSwatch.shade300.value,
                marginTop: 16,
                marginRight: 16,
                clickable: true,
              )),
          );

System details:

Tested on iOS 17.2.1 mapbox_maps_flutter: ^0.5.1 flutter doctor -v:

[✓] Flutter (Channel stable, 3.16.5, on macOS 14.1.2 23B92 darwin-arm64, locale en-DE) • Flutter version 3.16.5 on channel stable at /Users/user/Development/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 78666c8dc5 (5 weeks ago), 2023-12-19 16:14:14 -0800 • Engine revision 3f3e560236 • Dart version 3.2.3 • DevTools version 2.28.4 [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/user/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 15.0.1) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 15A507 • CocoaPods version 1.14.3 [✗] Chrome - develop for the web (Cannot find Chrome executable at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome) ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable. [✓] Android Studio (version 2022.3) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231) [✓] VS Code (version 1.84.2) • VS Code at /Users/user/Downloads/Visual Studio Code.app/Contents • Flutter extension version 3.54.0 [✓] Connected device (2 available) • iPhone of user (mobile) • ............... • ios • iOS 17.2.1 21C66 • macOS (desktop) • macos • darwin-arm64 • macOS 14.1.2 23B92 darwin-arm64 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 1 category.
evil159 commented 9 months ago

Hi @MeintrupStefan, we recommend avoiding displaying multiple maps in a list, for this case consider using Mapbox Static Images API https://docs.mapbox.com/api/maps/static-images/. It is a service that can generate map images from your style on demand. Those images will look like an embedded map, barring interactivity.

MeintrupStefan commented 9 months ago

This is exactly what I need! @evil159 You are my man!

However, there still exists a memory leak on iOS when I only display a singular map.

It behaves interesting. When the MapWidget is build for the first time, approximately 100MB of RAM will be occupied, but changing the location and then rebuilding barely consumes any more RAM. The problem is that if you leave the screen, then Flutter does not free the RAM that was used by the MapWidget completely.

The problem is that iOS never really kills the application, even when the user minimizes the application for a long time. Inevitably leads to the RAM to overflow over time.

Still, you helped me a lot @evil159, because our application actually does not need to interact with the map

evil159 commented 9 months ago

Thanks @MeintrupStefan, thanks for pointing this out. Indeed, it seems the resources associated with map widget weren't released when map widget got removed from the widget tree, it is addressed in this PR https://github.com/mapbox/mapbox-maps-flutter/pull/383 and will be available as a part of 1.0.0-beta.3 release.

MeintrupStefan commented 9 months ago

@evil159 Even though we will use static images from now on, I build from your branch to see if the issue is fixed.

The fix improved the memory leak a LOT! However, there still seems something off

Action/Ram usage RAM
I open my app and show a Screen without MapWidget: 290 MB
I open a Screen that contains one Map Widget 420 MB
I close the Screen that contains the Map Widget 350 MB
I minimize the app 340 MB
I open the app again 350 MB
I open the screen with map widget 420 MB
I close the Screen that contains the Map Widget 370 MB
I open the Screen that contains the Map Widget 420 MB

Now, this seems like the problem is solved, because the max RAM usage is at 420 MB now. And baseline shifts to only 350 RAM usage

However, now I go back into the screen that contains a list of MapWidgets and therefore render multiple MapWidget (6)

Action/Ram usage RAM
Open ListView with MapWidgets 650 MB
Close the Scree that contains the ListView 500 MB
Minimize the App 450 MB
Open the App again 470 MB

I would say that there still exists an issue when you have multiple screens that render MapWidgets since they seem to increase the RAM baseline continuously even though the MapWidgets are disposed

MeintrupStefan commented 9 months ago

I am happy to test any changes that you make and report back

MeintrupStefan commented 9 months ago

I tried the same with my app with images instead of MapWidgets and it seems like Flutter increases the RAM Baseline by use even when the widgets are disposed after. So this might just be normal Flutter behavior.

Before, I was able to crash the app with a couple of clicks. Now, I did not manage to do that even after opening many Screens that contain MapWidgets.

I would consider your PR a fix @evil159, but having MapWidgets in many different screens still seems to increase the RAM baseline continuously. However, I am not sure how realistic this is if you use static images when you can.