Flutter Material App切换场景调试时渲染报错求助
场景切换时Google Maps页面出现渲染错误分析
我开发了两个包含Google Maps并绘制Polyline的场景,应用运行正常,但调试时会出现大量报错栈信息,错误提示为**"Failed to rasterize a picture: unable to create render target at specified size"**,报错无固定触发条件,在使用Navigator切换场景时出现。请求帮忙分析原因。
调试错误信息
════════ Exception caught by rendering library ═════════════════════════════════ Failed to rasterize a picture: unable to create render target at specified size. The callstack when the image was created was: #0 new Image._.<anonymous closure> (dart:ui/painting.dart:1670:32) #1 new Image._ (dart:ui/painting.dart:1672:6) #2 Scene.toImageSync (dart:ui/compositing.dart:34:18) #3 OffsetLayer.toImageSync layer.dart:1497 #4 _RenderSnapshotWidget._paintAndDetachToImage snapshot_widget.dart:315 #5 _RenderSnapshotWidget.paint snapshot_widget.dart:345 #6 RenderObject._paintWithContext object.dart:2853 #7 PaintingContext.paintChild object.dart:253 #8 RenderProxyBoxMixin.paint #9 _ZoomEnterTransitionPainter.paint page_transitions_theme.dart:866 #10 _RenderSnapshotWidget.paint snapshot_widget.dart:335 #11 RenderObject._paintWithContext object.dart:2853 #12 PaintingContext.paintChild object.dart:253 #13 RenderProxyBoxMixin.paint proxy_box.dart:144 #14 _ZoomExitTransitionPainter.paint page_transitions_theme.dart:938 #15 _RenderSnapshotWidget.paint snapshot_widget.dart:335 #16 RenderObject._paintWithContext object.dart:2853 #17 PaintingContext.paintChild object.dart:253 #18 RenderProxyBoxMixin.paint proxy_box.dart:144 #19 _ZoomEnterTransitionPainter.paint page_transitions_theme.dart:866 #20 _RenderSnapshotWidget.paint snapshot_widget.dart:335 #21 RenderObject._paintWithContext object.dart:2853 #22 PaintingContext.paintChild object.dart:253 #23 RenderProxyBoxMixin.paint proxy_box.dart:144 #24 RenderObject._paintWithContext object.dart:2853 #25 PaintingContext._repaintCompositedChild object.dart:169 #26 PaintingContext.repaintCompositedChild object.dart:112 #27 PipelineOwner.flushPaint object.dart:1137 #28 RendererBinding.drawFrame binding.dart:518 #29 WidgetsBinding.drawFrame binding.dart:865 #30 RendererBinding._handlePersistentFrameCallback binding.dart:381 #31 SchedulerBinding._invokeFrameCallback binding.dart:1289 #32 SchedulerBinding.handleDrawFrame binding.dart:1218 #33 SchedulerBinding._handleDrawFrame binding.dart:1076 #34 _invoke (dart:ui/hooks.dart:145:13) #35 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:338:5) #36 _drawFrame (dart:ui/hooks.dart:112:31) The relevant error-causing widget was MaterialApp
导航页面代码
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_in_flutter/location_service.dart'; import 'package:geolocator/geolocator.dart'; import 'package:label_marker/label_marker.dart'; StreamSubscription<Position>? userStreamlocation; Position? streamResult; class navScreen extends StatefulWidget { final String userOrigin; final String userGoal; navScreen({Key? mykey, required this.userOrigin, required this.userGoal}); @override State<navScreen> createState() => nav_Screen(userOrigin: userOrigin, userGoal: userGoal); } class geostracking { Future<Position> determineLocation() async { bool serviceEnabled; LocationPermission permission; serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { return Future.error('Location Service are Disabled'); } permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { return Future.error('Location Permission Denied'); } } if (permission == LocationPermission.deniedForever) { return Future.error('Permission Denied Forever'); } Position position = await Geolocator.getCurrentPosition(); print(position); return position; } Position? listenToLocation() { Position? userposition; final LocationSettings _locationSettings = LocationSettings(accuracy: LocationAccuracy.high,distanceFilter: 100); userStreamlocation = Geolocator.getPositionStream(locationSettings: _locationSettings).listen( (userposition) { print(userposition == null ? 'Unknown' : '${userposition.latitude.toString()}, ${userposition.longitude.toString()}'); setState(){ streamResult=userposition; } }); return userposition; } } class nav_Screen extends State<navScreen> { StreamSubscription<Position>? streamPosition; @override void dispose() { super.dispose(); streamPosition?.cancel(); } late GoogleMapController mapController; final String userOrigin; final String userGoal; nav_Screen({required this.userOrigin,required this.userGoal, }); final LatLng _kGooglePlex = LatLng(baseLat, baseLong); Set<Marker> _markers = Set<Marker>(); Set<Polyline> _navPolylines =Set<Polyline>(); int _polylineIdCounter=0; int markerNumber=1; int _markerIDCounter = 1; void _setMarker(String ID, var Place) { String markerLabel; if(markerNumber==1) { markerLabel = 'Start'; } else { markerLabel = "Destination"; } setState(() { _markers.addLabelMarker(LabelMarker( backgroundColor: Color.fromARGB(255, 0, 8, 255), label: markerLabel, markerId: MarkerId(ID), position: LatLng(Place['geometry']['location']['lat'], Place['geometry']['location']['lng']),) ); _markerIDCounter++; markerNumber++; }); } void _setPolylines(List<PointLatLng> points) { final String _navPolylinesID = 'polyline_$_polylineIdCounter'; //print('Check : '+points.toString()); _navPolylines.add( Polyline( polylineId: PolylineId(_navPolylinesID), width: 5, color: Color.fromARGB(255, 56, 12, 255), startCap: Cap.roundCap, endCap: Cap.buttCap, visible: true, points: points .map((point) => LatLng(point.latitude, point.longitude)).toList(), ) ); _polylineIdCounter++; } void _onMapCreated(GoogleMapController controller) async { mapController = controller; var direction = await LocationService().getDirectionNoWP(userOrigin, userGoal); print(_polylineIdCounter); _setPolylines(direction['polyline_decoded']); String OplaceID = await LocationService().getPlaceID(userOrigin); var Oplace = await LocationService().getPlace(userOrigin); _setMarker(OplaceID, Oplace); String DplaceID = await LocationService().getPlaceID(userGoal); var Dplace = await LocationService().getPlace(userGoal); _setMarker(DplaceID, Dplace); setState(() {}); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Test')), body: Column( children: [ Expanded( child: GoogleMap( mapType: MapType.normal, initialCameraPosition: CameraPosition(target: _kGooglePlex,zoom: 10), markers: _markers, polylines: _navPolylines, onMapCreated: _onMapCreated, ) ), ] ), floatingActionButton: Container( alignment: Alignment.bottomCenter, child: FloatingActionButton( onPressed: ()async{ geostracking().listenToLocation(); }, child: Icon(Icons.navigation), ), ), ); } }
主页面代码片段
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_in_flutter/current_location.dart'; import 'package:google_maps_in_flutter/customer_profile.dart'; import 'package:google_maps_in_flutter/location_service.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:geolocator/geolocator.dart'; import 'package:label_marker/label_marker.dart'; void main() => runApp(MyApp()); StreamSubscription<Position>? userStreamlocation; Position? streamResult; class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Google Map Test', home: GMap(), //routes: , ); } } class GMap extends StatefulWidget { @override State<GMap> createState() => GMapState(); } class requestTemplateConverter{} class GMapState extends State<GMap> {
错误原因分析及解决方案
核心问题定位
这个错误属于Flutter渲染层的内存/资源分配问题,当Navigator切换场景时,默认的页面过渡动画(这里是ZoomEnter/ExitTransition)需要对页面内容进行快照渲染,但Google Maps组件本身占用大量GPU资源,加上Polyline和自定义LabelMarker的渲染负载,导致系统无法在指定尺寸下创建渲染目标。
具体诱因
- 全局流订阅未正确管理:
userStreamlocation是全局变量,切换页面时未取消旧订阅,多个位置监听流同时运行,持续触发UI重绘,加重渲染负担。 - 页面过渡动画与地图渲染冲突:默认缩放过渡动画需要对整个地图页面进行快照,而地图组件的渲染尺寸可能超过当前GPU的纹理分配限制。
- 资源未及时释放:
nav_Screen仅取消了streamPosition,但全局的userStreamlocation未在页面dispose时清理,导致内存泄漏和不必要的渲染触发。
修复步骤
清理全局流订阅:
在nav_Screen的dispose方法中添加全局流的取消逻辑:@override void dispose() { super.dispose(); streamPosition?.cancel(); userStreamlocation?.cancel(); // 取消全局位置监听流 }同时避免使用全局变量管理流订阅,将
userStreamlocation移到nav_Screen类内部,确保每个页面实例的订阅独立。禁用页面过渡动画或调整动画类型:
切换页面时使用无过渡或轻量级过渡,避免地图快照:Navigator.push(context, PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => navScreen(...), transitionsBuilder: (context, animation, secondaryAnimation, child) { return child; // 直接跳转,无过渡动画 }, ));或者使用更轻量的过渡(如淡入淡出)替代缩放过渡。
优化地图渲染资源:
- 避免在
onMapCreated中一次性添加大量Polyline点,如果路线过长,考虑分段加载或简化路线。 - 检查
label_marker插件的实现,是否存在内存泄漏或过度渲染问题,尝试替换为原生Marker+InfoWindow的组合。
- 避免在
确保位置监听仅在需要时运行:
将geostracking类的listenToLocation方法返回的流订阅交给页面管理,而非全局变量,确保页面销毁时及时取消。
内容的提问来源于stack exchange,提问作者Flutter Beginner
相关产品推荐
相关产品推荐

