Flutter发布版APK安装后显示灰屏,调试/有线连接发布模式正常
Flutter 发布APK灰屏问题排查与解决建议
问题现象
包含Scaffold的页面在安装构建完成的APK后呈现灰屏状态,无任何错误提示(已配置自定义错误组件);即使隔离该页面单独测试,问题仍存在。但调试模式、通过数据线连接电脑的发布模式下页面可正常显示。项目使用go_router,隔离测试代码如下,灰屏显示效果如下:
// ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:test/shared/widget/buttons/primary_button.dart'; import 'package:test/shared/widget/text/text_title_large.dart'; import 'package:test/shared/widget/text/text_title_medium.dart'; import 'package:test/shared/widget/text/text_title_small.dart'; import 'package:test/shared/widget/ui/helper_sized_box.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; // import 'package:go_router/go_router.dart'; import 'package:sizer/sizer.dart'; import 'package:timeline_tile/timeline_tile.dart'; const timeLineData = [ { "timeLine": "2 weekends", "program": "Boot Camp", "level": "1", }, { "timeLine": "4 weekends", "program": "Market Segmentation/MVP/Customer Discovery", "level": "2", }, { "timeLine": "2 weekends", "program": "Pitch Deck Workshop", "level": "3", }, { "timeLine": "Once a month", "program": "Industrial Connect/Networking", "level": "4", }, { "timeLine": "Once in 3 months", "program": "Seed Funding & Product Showcase", "level": "5", }, { "timeLine": "3 months", "program": "90 Days Acceleration Workshops", "level": "6", }, ]; class ScheduleScreen extends StatelessWidget { const ScheduleScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: SizedBox( height: 100.h, child: Stack( children: [ // Positioned( // child: SizedBox( // width: 100.w, // height: 100.h, // child: Image.asset( // "assets/images/BGs/home_bg.png", // fit: BoxFit.cover, // ), // ), // ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SafeArea( child: Padding( padding: EdgeInsets.only(left: 4.w, top: 2.h), child: TextTitleLarge( "Our unique \nincubation timeline".toUpperCase(), // textColor: colors(context).primary, ), ), ), Divider( thickness: 2, // color: colors(context).secondary, ), Center( child: Padding( padding: const EdgeInsets.all(8.0), child: PrimaryButton( label: "Join Us", width: 60.w, variant: 2, onPressed: () { // context.pushNamed( // Routes.onboarding, // ); }, ), ), ), Divider( thickness: 2, // color: colors(context).secondary, ), Expanded( child: Padding( padding: EdgeInsets.symmetric( horizontal: 2.w, ), child: ListView.builder( itemBuilder: (context, index) { return BootCampScheduleTimeline( isFirst: index == 0, isLast: index == timeLineData.length - 1, level: timeLineData[index]["level"] ?? "", program: timeLineData[index]["program"] ?? "", time: timeLineData[index]["timeLine"] ?? "", index: index, ); }, itemCount: timeLineData.length, ) .animate( delay: const Duration(milliseconds: 500), ) .fadeIn(duration: const Duration(seconds: 1)), ), ), ], ), ], ), ), ); } } class BootCampScheduleTimeline extends StatelessWidget { const BootCampScheduleTimeline({ Key? key, required this.isFirst, required this.isLast, required this.level, required this.program, required this.time, required this.index, }) : super(key: key); final bool isFirst; final bool isLast; final String level; final String program; final String time; final int index; @override Widget build(BuildContext context) { return SizedBox( height: 15.h, child: TimelineTile( isFirst: isFirst, isLast: isLast, beforeLineStyle: LineStyle( // color: colors(context).secondary!, ), alignment: TimelineAlign.manual, lineXY: 0.4, indicatorStyle: IndicatorStyle( width: 42.sp, height: 5.h, // color: colors(context).accentSecondary!, indicatorXY: 0.4, drawGap: true, indicator: TextTitleLarge( "L$level", // textColor: colors(context).primary, ), ), endChild: SizedBox( height: 10.h, // width: 20.w, child: CustomPaint( painter: MyPainter( color: Colors.blue // index % 3 == 0 // ? colors(context).secondary!.withOpacity(0.8) // : index % 2 == 0 // ? colors(context).accentSecondary! // : colors(context).primary!, ), child: Padding( padding: EdgeInsets.symmetric( horizontal: 4.w, ), child: Row( children: [ SizedBoxSeparator( width: 4.w, ), Expanded( child: Center( child: TextTitleSmall( program, alignment: TextAlign.center, // textColor: index % 3 == 0 // ? colors(context).primary // : index % 2 == 0 // ? colors(context).secondary // : colors(context).accentPrimary, ), ), ), ], ), ), ), ), startChild: Container( padding: EdgeInsets.only(right: 2.w), // alignment: Alignment.centerRight, child: TextTitleMedium( time, fontWeight: 600, // textColor: colors(context).primary, ), ), ), ); } } class MyPainter extends CustomPainter { final Color color; MyPainter({ required this.color, }); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = color ..strokeWidth = 2; final path = Path(); path.moveTo(size.width, size.height); path.addPolygon([ Offset(0, size.height / 2), Offset( size.width / 10, size.height * 2 / 5, ), Offset( size.width / 10, size.height * 3 / 5, ), ], true); path.close(); canvas.drawPath(path, paint); final rect = Rect.fromPoints( Offset( size.width / 10, size.height * 0, ), Offset( size.width * 1, size.height * 1, ), ); canvas.clipRect( rect, ); canvas.clipRRect(RRect.fromRectXY(rect, 8.sp, 8.sp)); canvas.drawColor(color, BlendMode.src); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }

排查与解决方向
- 检查CustomPaint绘制逻辑:代码中
MyPainter的paint方法连续使用canvas.clipRect和canvas.clipRRect,发布模式下Flutter的图形渲染优化可能导致裁剪逻辑冲突,引发渲染异常。建议简化绘制路径,或者用ClipRRect组件替换自定义裁剪实现圆角,避免手动操作canvas的兼容性问题。 - 排查第三方依赖兼容性:项目依赖
sizer、flutter_animate、timeline_tile,这些库在发布模式下可能存在初始化或渲染bug。可以先临时移除flutter_animate的动画效果,或者尝试升级/降级依赖版本,验证是否是依赖导致的灰屏。 - 查看发布模式原生日志:通过
adb logcat -s flutter命令过滤Flutter相关日志,能看到前端没显示的底层错误,比如资源加载失败、渲染崩溃等。 - 验证Sizer初始化:
sizer需要在MaterialApp之前完成初始化,发布模式下如果初始化时机错误,会导致尺寸计算异常,进而引发布局崩溃。确认App入口最外层是Sizer组件。 - 临时关闭混淆测试:在
android/app/build.gradle中设置minifyEnabled false,构建APK测试,排查是否是R8混淆导致代码逻辑被破坏。
内容的提问来源于stack exchange,提问作者Arham S C
相关产品推荐
相关产品推荐

