You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 12:47:02