-
Notifications
You must be signed in to change notification settings - Fork 171
Description
Platforms
Android
Version of flutter maplibre_gl
0.24.1
Bug Description
When testing the “Various Sources” example in the MapLibre Android demo app, the application crashes with a fatal SIGSEGV when rapidly switching between different source/style types.
This issue occurs:
• In Android Emulator (emu64_arm64)
• On a real Samsung device (Galaxy A56 series)
The crash is thrown from inside MapLibre native code (libmaplibre.so), specifically during the render loop (mbgl::android::MapRenderer::render).
It is consistently reproducible when switching quickly between:
• Default Style
• GeoJSON Cluster
• Raster Source
• Image Source
• Heatmap
• Vector Tiles
It appears to be a race condition or unsafe access during removal & re-addition of sources/layers while MapLibre’s renderer thread is active.
Android Emulator and Real Device
F/libc (16771): Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x2827b713a7dc00 in tid 16866 (TextureViewRend), pid 16771 (aplibre.example)
Build fingerprint: 'google/sdk_gphone64_arm64/emu64a:16/BE2A.250530.026.D1/13818094:user/release-keys'
Revision: '0'
ABI: 'arm64'
Timestamp: 2025-11-21 16:49:38.659823610+0530
Process uptime: 54s
Cmdline: org.maplibre.example
pid: 16771, tid: 16866, name: TextureViewRend >>> org.maplibre.example <<<
uid: 10215
tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x002827b713a7dc00
x0 0002827b713a7dc1 x1 000000750826ce04 x2 0000000000021020 x3 61702d6c61796572
x4 61702d6c61796572 x5 726579616c2d7061 x6 726579616c2d7061 x7 b400007657d06f39
x8 4498a00044989f00 x9 0000000000000012 x10 000504f6e274fb80 x11 000504f6e274fb80
x12 0000000000000000 x13 0000000000000002 x14 0000000000000002 x15 0000000000000002
x16 0000000000000003 x17 0000000000000003 x18 0000007507b28000 x19 000000750826ce00
x20 b400007747cba960 x21 000000750826e880 x22 0000000000000002 x23 b400007797d0a9d8
x24 b4000077b7cac300 x25 b400007807c84610 x26 b400007847dbad60 x27 b4000076f7d35050
x28 b400007847ddc42c x29 000000750826cc00
lr 00000075153913cc sp 000000750826cbf0 pc 0000007515391414 pst 0000000080001000
32 total frames
backtrace:
#00 pc 00000000006f3414 /data/app/~~n818ZqUt3SG-HdTTGFmlEQ==/org.maplibre.example-SBZBg0xg5-z41V68fTHHKQ==/base.apk!libmaplibre.so (offset 0x3650000) (BuildId: fe71dc38a86d323ec36293c353ff0286b958d603)
#1 pc 00000000007163e4 /data/app/~~n818ZqUt3SG-HdTTGFmlEQ==/org.maplibre.example-SBZBg0xg5-z41V68fTHHKQ==/base.apk!libmaplibre.so (offset 0x3650000) (BuildId: fe71dc38a86d323ec36293c353ff0286b958d603)
#2 pc 0000000000753e0c /data/app/~~n818ZqUt3SG-HdTTGFmlEQ==/org.maplibre.example-SBZBg0xg5-z41V68fTHHKQ==/base.apk!libmaplibre.so (offset 0x3650000) (BuildId: fe71dc38a86d323ec36293c353ff0286b958d603)
#3 pc 0000000000762bfc /data/app/~~n818ZqUt3SG-HdTTGFmlEQ==/org.maplibre.example-SBZBg0xg5-z41V68fTHHKQ==/base.apk!libmaplibre.so (offset 0x3650000) (BuildId: fe71dc38a86d323ec36293c353ff0286b958d603)
#4 pc 000000000076179c /data/app/~~n818ZqUt3SG-HdTTGFmlEQ==/org.maplibre.example-SBZBg0xg5-z41V68fTHHKQ==/base.apk!libmaplibre.so (offset 0x3650000) (BuildId: fe71dc38a86d323ec36293c353ff0286b958d603)
#5 pc 000000000056d924 /data/app/~~n818ZqUt3SG-HdTTGFmlEQ==/org.maplibre.example-SBZBg0xg5-z41V68fTHHKQ==/base.apk!libmaplibre.so (offset 0x3650000) (mbgl::android::MapRenderer::render(_JNIEnv&)+216) (BuildId: fe71dc38a86d323ec36293c353ff0286b958d603)
#6 pc 0000000000570a58 /data/app/~~n818ZqUt3SG-HdTTGFmlEQ==/org.maplibre.example-SBZBg0xg5-z41V68fTHHKQ==/base.apk!libmaplibre.so (offset 0x3650000) (auto auto jni::MakeNativeMethod<auto jni::NativeMethodMaker<void (auto jni::NativePeerMemberFunctionMethod<void (mbgl::android::MapRenderer::)(_JNIEnv&), &mbgl::android::MapRenderer::render(_JNIEnv&)>::operator()<mbgl::android::MapRenderer, mbgl::android::MapRenderer, void>(jni::Field<mbgl::android::MapRenderer, long> const&)::'lambda'(_JNIEnv&, jni::Objectmbgl::android::MapRenderer&)::)(_JNIEnv&, jni::Objectmbgl::android::MapRenderer&) const>::operator()<auto jni::NativePeerMemberFunctionMethod<void (mbgl::android::MapRenderer::)(_JNIEnv&), &mbgl::android::MapRenderer::render(_JNIEnv&)>::operator()<mbgl::android::MapRenderer, mbgl::android::MapRenderer, void>(jni::Field<mbgl::android::MapRenderer, long> const&)::'lambda'(_JNIEnv&, jni::Objectmbgl::android::MapRenderer&)>(char const, auto jni::NativePeerMemberFunctionMethod<void (mbgl::android::MapRenderer::)(_JNIEnv&), &mbgl::android::MapRenderer::render(_JNIEnv&)>::operator()<mbgl::android::MapRenderer, mbgl::android::MapRenderer, void>(jni::Field<mbgl::android::MapRenderer, long> const&)::'lambda'(_JNIEnv&, jni::Objectmbgl::android::MapRenderer&) const&)::'lambda'(_JNIEnv, jni::jobject*)>(char const*, char const*, auto jni::NativePeerMemberFunctionMethod<void (mbgl::android::MapRenderer::)(_JNIEnv&), &mbgl::android::MapRenderer::render(_JNIEnv&)>::operator()<mbgl::android::MapRenderer, mbgl::android::MapRenderer, void>(jni::Field<mbgl::android::MapRenderer, long> const&)::'lambda'(_JNIEnv&, jni::Objectmbgl::android::MapRenderer&) const&, std::__ndk1::enable_if<std::is_class<auto jni::NativePeerMemberFunctionMethod<void (mbgl::android::MapRenderer::)(_JNIEnv&), &mbgl::android::MapRenderer::render(_JNIEnv&)>::operator()<mbgl::android::MapRenderer, mbgl::android::MapRenderer, void>(jni::Field<mbgl::android::MapRenderer, long> const&)::'lambda'(_JNIEnv&, jni::Objectmbgl::android::MapRenderer&)>::value, void>::type*)::'lambda'(_JNIEnv*, auto...)::__invokejni::jobject*(_JNIEnv*, auto...)+44) (BuildId: fe71dc38a86d323ec36293c353ff0286b958d603)
#7 pc 000000000033f500 /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: b229f9d1b6196afaae086f29f029f907)
#8 pc 0000000000328194 /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612) (BuildId: b229f9d1b6196afaae086f29f029f907)
#9 pc 00000000006793d4 /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)+1580) (BuildId: b229f9d1b6196afaae086f29f029f907)
#10 pc 00000000005c84f4 /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp(art::interpreter::SwitchImplContext*)+868) (BuildId: b229f9d1b6196afaae086f29f029f907)
#11 pc 0000000000317a88 /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: b229f9d1b6196afaae086f29f029f907)
#12 pc 00000000000ddb74 anonymous:761926a000 (org.maplibre.android.maps.renderer.MapRenderer.onDrawFrame+0)
#13 pc 00000000002d763c /apex/com.android.art/lib64/libart.so (art::interpreter::Execute(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame&, art::JValue, bool, bool) (.__uniq.112435418011751916792819755956732575238.llvm.11186287395527938019)+332) (BuildId: b229f9d1b6196afaae086f29f029f907)
#14 pc 00000000002d6e70 /apex/com.android.art/lib64/libart.so (artQuickToInterpreterBridge+888) (BuildId: b229f9d1b6196afaae086f29f029f907)
#15 pc 000000000033f638 /apex/com.android.art/lib64/libart.so (art_quick_to_interpreter_bridge+88) (BuildId: b229f9d1b6196afaae086f29f029f907)
#16 pc 000000000055630c [anon_shmem:dalvik-jit-code-cache] (offset 0x2000000) (org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer.onDrawFrame+108)
#17 pc 0000000000560af4 [anon_shmem:dalvik-jit-code-cache] (offset 0x2000000) (org.maplibre.android.maps.renderer.textureview.GLTextureViewRenderThread.run+1204)
#18 pc 00000000003284f0 /apex/com.android.art/lib64/libart.so (art_quick_osr_stub+64) (BuildId: b229f9d1b6196afaae086f29f029f907)
#19 pc 00000000003428a8 /apex/com.android.art/lib64/libart.so (art::jit::Jit::MaybeDoOnStackReplacement(art::Thread*, art::ArtMethod*, unsigned int, int, art::JValue*)+832) (BuildId: b229f9d1b6196afaae086f29f029f907)
#20 pc 00000000005cb390 /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp(art::interpreter::SwitchImplContext*)+12800) (BuildId: b229f9d1b6196afaae086f29f029f907)
#21 pc 0000000000317a88 /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: b229f9d1b6196afaae086f29f029f907)
#22 pc 00000000000e1580 anonymous:761926a000 (org.maplibre.android.maps.renderer.textureview.GLTextureViewRenderThread.run+0)
#23 pc 00000000002d763c /apex/com.android.art/lib64/libart.so (art::interpreter::Execute(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame&, art::JValue, bool, bool) (.__uniq.112435418011751916792819755956732575238.llvm.11186287395527938019)+332) (BuildId: b229f9d1b6196afaae086f29f029f907)
#24 pc 00000000002d6e70 /apex/com.android.art/lib64/libart.so (artQuickToInterpreterBridge+888) (BuildId: b229f9d1b6196afaae086f29f029f907)
#25 pc 000000000033f638 /apex/com.android.art/lib64/libart.so (art_quick_to_interpreter_bridge+88) (BuildId: b229f9d1b6196afaae086f29f029f907)
#26 pc 0000000000328194 /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612) (BuildId: b229f9d1b6196afaae086f29f029f907)
#27 pc 00000000002d9348 /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+216) (BuildId: b229f9d1b6196afaae086f29f029f907)
#28 pc 0000000000421028 /apex/com.android.art/lib64/libart.so (art::Thread::CreateCallback(void*)+932) (BuildId: b229f9d1b6196afaae086f29f029f907)
#29 pc 0000000000420c74 /apex/com.android.art/lib64/libart.so (art::Thread::CreateCallbackWithUffdGc(void*)+8) (BuildId: b229f9d1b6196afaae086f29f029f907)
#30 pc 0000000000080df8 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+232) (BuildId: 2445fc73ede03b9536eef49355aff375)
#31 pc 0000000000073dd8 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: 2445fc73ede03b9536eef49355aff375)
Lost connection to device.
Exited.
Steps to Reproduce
- Open the MapLibre Android Demo
- Go to Various Sources example
- Switch rapidly between multiple source/style options
- Within a few seconds, the app crashes
Expected Results
MapLibre should safely replace/remove/add sources and layers even during rapid changes, without crashing the renderer.
Actual Results
The app crashes with:
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR)
libmaplibre.so → mbgl::android::MapRenderer::render()
Code Sample
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'page.dart';
class StyleInfo {
final String name;
final String baseStyle;
final Future<void> Function(MapLibreMapController) addDetails;
final CameraPosition position;
const StyleInfo(
{required this.name,
required this.baseStyle,
required this.addDetails,
required this.position});
}
class Sources extends ExamplePage {
const Sources({super.key}) : super(const Icon(Icons.map), 'Various Sources');
@override
Widget build(BuildContext context) {
return const FullMap();
}
}
class FullMap extends StatefulWidget {
const FullMap({super.key});
@override
State createState() => FullMapState();
}
class FullMapState extends State<FullMap> {
MapLibreMapController? controller;
final watercolorRasterId = "watercolorRaster";
int selectedStyleId = 0;
void _onMapCreated(MapLibreMapController controller) {
this.controller = controller;
}
static Future<void> addRaster(MapLibreMapController controller) async {
await controller.addSource(
"watercolor",
const RasterSourceProperties(
tiles: [
'https://stamen-tiles.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg'
],
tileSize: 256,
attribution:
'Map tiles by <a target="_top" rel="noopener" href="http://stamen.com">Stamen Design</a>, under <a target="_top" rel="noopener" href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a target="_top" rel="noopener" href="http://openstreetmap.org">OpenStreetMap</a>, under <a target="_top" rel="noopener" href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>'),
);
await controller.addLayer(
"watercolor", "watercolor", const RasterLayerProperties());
}
static Future<void> addGeojsonCluster(
MapLibreMapController controller) async {
await controller.addSource(
"earthquakes",
const GeojsonSourceProperties(
data:
'https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson',
cluster: true,
clusterMaxZoom: 14, // Max zoom to cluster points on
clusterRadius:
50 // Radius of each cluster when clustering points (defaults to 50)
));
await controller.addLayer(
"earthquakes",
"earthquakes-circles",
const CircleLayerProperties(circleColor: [
Expressions.step,
[Expressions.get, 'point_count'],
'#51bbd6',
100,
'#f1f075',
750,
'#f28cb1'
], circleRadius: [
Expressions.step,
[Expressions.get, 'point_count'],
20,
100,
30,
750,
40
]));
await controller.addLayer(
"earthquakes",
"earthquakes-count",
const SymbolLayerProperties(
textField: [Expressions.get, 'point_count_abbreviated'],
textFont: ['Open Sans Semibold'],
textSize: 12,
));
}
static Future<void> addVector(MapLibreMapController controller) async {
await controller.addSource(
"terrain",
const VectorSourceProperties(
url: MapLibreStyles.demo,
));
await controller.addLayer(
"terrain",
"contour",
const LineLayerProperties(
lineColor: "#ff69b4",
lineWidth: 1,
lineCap: "round",
lineJoin: "round",
),
sourceLayer: "countries");
}
static Future<void> addImage(MapLibreMapController controller) async {
await controller.addSource(
"radar",
const ImageSourceProperties(
url: "https://docs.mapbox.com/mapbox-gl-js/assets/radar.gif",
coordinates: [
[-80.425, 46.437],
[-71.516, 46.437],
[-71.516, 37.936],
[-80.425, 37.936]
]));
await controller.addRasterLayer(
"radar",
"radar",
const RasterLayerProperties(rasterFadeDuration: 0),
);
}
static Future<void> addVideo(MapLibreMapController controller) async {
await controller.addSource(
"video",
const VideoSourceProperties(urls: [
'https://static-assets.mapbox.com/mapbox-gl-js/drone.mp4',
'https://static-assets.mapbox.com/mapbox-gl-js/drone.webm'
], coordinates: [
[-122.51596391201019, 37.56238816766053],
[-122.51467645168304, 37.56410183312965],
[-122.51309394836426, 37.563391708549425],
[-122.51423120498657, 37.56161849366671]
]));
await controller.addRasterLayer(
"video",
"video",
const RasterLayerProperties(),
);
}
static Future<void> addHeatMap(MapLibreMapController controller) async {
await controller.addSource(
'earthquakes-heatmap-source',
const GeojsonSourceProperties(
data:
'https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson'));
await controller.addLayer(
'earthquakes-heatmap-source',
'earthquakes-heatmap-layer',
const HeatmapLayerProperties(
// Increase the heatmap weight based on frequency and property magnitude
heatmapWeight: [
Expressions.interpolate,
['linear'],
[Expressions.get, 'mag'],
0,
0,
6,
1,
],
// Increase the heatmap color weight weight by zoom level
// heatmap-intensity is a multiplier on top of heatmap-weight
heatmapIntensity: [
Expressions.interpolate,
['linear'],
[Expressions.zoom],
0,
1,
9,
3
],
// Color ramp for heatmap. Domain is 0 (low) to 1 (high).
// Begin color ramp at 0-stop with a 0-transparancy color
// to create a blur-like effect.
heatmapColor: [
Expressions.interpolate,
['linear'],
['heatmap-density'],
0,
'rgba(33.0, 102.0, 172.0, 0.0)',
0.2,
'rgb(103.0, 169.0, 207.0)',
0.4,
'rgb(209.0, 229.0, 240.0)',
0.6,
'rgb(253.0, 219.0, 119.0)',
0.8,
'rgb(239.0, 138.0, 98.0)',
1,
'rgb(178.0, 24.0, 43.0)',
],
// Adjust the heatmap radius by zoom level
heatmapRadius: [
Expressions.interpolate,
['linear'],
[Expressions.zoom],
0,
2,
9,
20,
],
// Transition from heatmap to circle layer by zoom level
heatmapOpacity: [
Expressions.interpolate,
['linear'],
[Expressions.zoom],
7,
1,
9,
0
],
),
maxzoom: 9,
);
}
static Future<void> addDem(MapLibreMapController controller) async {
// TODO: adapt example?
// await controller.addSource(
// "dem",
// RasterDemSourceProperties(
// url: "mapbox://mapbox.mapbox-terrain-dem-v1"));
// await controller.addLayer(
// "dem",
// "hillshade",
// HillshadeLayerProperties(
// hillshadeExaggeration: 1,
// hillshadeShadowColor: Colors.blue.toHexStringRGB()),
// );
}
final _stylesAndLoaders = [
const StyleInfo(
name: "Vector",
baseStyle: MapLibreStyles.demo,
addDetails: addVector,
position: CameraPosition(target: LatLng(33.3832, -118.4333), zoom: 6),
),
const StyleInfo(
name: "Default style",
// Using the raw github file version of MapLibreStyles.DEMO here, because we need to
// specify a different baseStyle for consecutive elements in this list,
// otherwise the map will not update
baseStyle:
"https://raw.githubusercontent.com/maplibre/demotiles/gh-pages/style.json",
addDetails: addDem,
position: CameraPosition(target: LatLng(33.5, -118.1), zoom: 8),
),
const StyleInfo(
name: "Geojson cluster",
baseStyle: MapLibreStyles.demo,
addDetails: addGeojsonCluster,
position: CameraPosition(target: LatLng(33.5, -118.1), zoom: 5),
),
const StyleInfo(
name: "Raster",
baseStyle:
"https://raw.githubusercontent.com/maplibre/demotiles/gh-pages/style.json",
addDetails: addRaster,
position: CameraPosition(target: LatLng(40, -100), zoom: 3),
),
const StyleInfo(
name: "Image",
baseStyle:
"https://raw.githubusercontent.com/maplibre/demotiles/gh-pages/style.json?",
addDetails: addImage,
position: CameraPosition(target: LatLng(43, -75), zoom: 6),
),
const StyleInfo(
name: "Heatmap",
baseStyle:
"https://raw.githubusercontent.com/maplibre/demotiles/gh-pages/style.json",
addDetails: addHeatMap,
position: CameraPosition(target: LatLng(33.5, -118.1), zoom: 2),
),
//video only supported on web
if (kIsWeb)
const StyleInfo(
name: "Video",
baseStyle:
"https://raw.githubusercontent.com/maplibre/demotiles/gh-pages/style.json",
addDetails: addVideo,
position: CameraPosition(
target: LatLng(37.562984, -122.514426), zoom: 17, bearing: -96),
),
];
Future<void> _onStyleLoadedCallback() async {
final styleInfo = _stylesAndLoaders[selectedStyleId];
styleInfo.addDetails(controller!);
controller!
.animateCamera(CameraUpdate.newCameraPosition(styleInfo.position));
}
@override
Widget build(BuildContext context) {
final styleInfo = _stylesAndLoaders[selectedStyleId];
final nextName =
_stylesAndLoaders[(selectedStyleId + 1) % _stylesAndLoaders.length]
.name;
return Scaffold(
floatingActionButton: Padding(
padding: const EdgeInsets.all(32.0),
child: FloatingActionButton.extended(
icon: const Icon(Icons.swap_horiz),
label: SizedBox(
width: 120, child: Center(child: Text("To $nextName"))),
onPressed: () => setState(
() => selectedStyleId =
(selectedStyleId + 1) % _stylesAndLoaders.length,
),
),
),
body: Stack(
children: [
MapLibreMap(
styleString: styleInfo.baseStyle,
onMapCreated: _onMapCreated,
initialCameraPosition: styleInfo.position,
onStyleLoadedCallback: _onStyleLoadedCallback,
),
Container(
padding: const EdgeInsets.all(8),
alignment: Alignment.topCenter,
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Current source: ${styleInfo.name}",
style: Theme.of(context).textTheme.titleLarge,
),
),
),
),
],
));
}
}