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

Flutter Release APK安装后显示空白屏问题求助

Flutter Release APK 空白屏问题

我开发的第一个Flutter应用在模拟器Debug模式下运行完全正常,但打包成Release APK安装到手机后只显示空白屏。尝试过把每个row和column放入Expanded组件,没用;VSCode调试控制台也没报错。

调试控制台输出

Launching lib/main.dart on Android SDK built for x86 in debug mode...
main.dart:1
✓  Built build/app/outputs/flutter-apk/app-debug.apk.
Connecting to VM Service at ws://127.0.0.1:33885/Z0RCbF0ZTYY=/ws
D/EGL_emulation(19403): eglMakeCurrent: 0xa043e6e0: ver 2 0 (tinfo 0x91544a50)
Application finished.
Exited (sigterm)

问题原因及解决方法

1. HTTP明文请求被拦截(Android 9+)

你的API请求使用http协议,而Android 9及以上系统默认禁止明文网络请求。Debug模式下模拟器配置宽松不受限制,但Release模式会直接拦截请求,导致fetchRates()失败,后续逻辑崩溃引发空白屏。

解决:

  • 优先改用HTTPS地址:将API链接改为https://api.exchangeratesapi.io/v1/latest?access_key=$API_KEY
  • 若必须用HTTP,在android/app/src/main/AndroidManifest.xml的<application>标签内添加:android:usesCleartextTraffic="true"(不推荐生产环境使用)

2. FutureBuilder未处理错误状态

当前FutureBuilder仅判断了连接状态,未处理请求失败的情况。如果API请求出错,snapshot.data会为null,调用snapshot.data!.body会直接触发空指针异常,导致页面空白。

修改HomePage中的FutureBuilder代码:

return FutureBuilder(
    future: fetchRates(),
    builder: (ctx, snapshot) {
      if (snapshot.connectionState != ConnectionState.done) {
        return const Scaffold(
          body: Center(
            child: CircularProgressIndicator(),
          ),
        );
      } else if (snapshot.hasError) {
        // 显示错误提示,避免空白
        return Scaffold(
          body: Center(
            child: Text('加载失败: ${snapshot.error}'),
          ),
        );
      } else {
        currency_rates_json = json.decode(snapshot.data!.body);
        // 原有页面返回逻辑...
      }
    });

3. 全局变量的风险

全局变量currency_rates_json在Release模式下可能因初始化时机问题引发空指针。建议将其移至_HomePageState的状态变量中,避免全局状态污染。

修改HomePage代码:

class _HomePageState extends State<HomePage> {
  late var currency_rates_json; // 移至状态内

  @override
  Widget build(BuildContext context) {
    // 原有代码...
  }
}

4. CurrencyInput中的空指针隐患

setText方法中的assert(tmp != null)在Release模式下会被移除,若double.tryParse(value)返回null,tmp!会触发空指针异常。

修改CurrencyInput的setText方法:

widget.setText = (String value) {
  setState(
    () {
      double? tmp = double.tryParse(value);
      if (tmp != null) {
        widget.textEditingController.text =
            double.parse(tmp.toStringAsFixed(4)).toString();
      }
      // 可添加else逻辑提示输入无效
    },
  );
};

我的代码文件

main.dart

import 'dart:convert';

import 'package:currency_converter/CurrencyInput.dart';
import 'package:currency_picker/currency_picker.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

const String API_KEY = "51a7c9772f10302ec8ec08ce77cf73e3";
late var currency_rates_json;
void main() {
  runApp(
    const MaterialApp(
      title: 'Currency Converter',
      home: HomePage(),
    ),
  );
}

Future<http.Response> fetchRates() {
  return http.get(
    Uri.parse('http://api.exchangeratesapi.io/v1/latest?access_key=$API_KEY'),
  );
}

double calculate(double amount, Currency from, Currency to) {
  return currency_rates_json["rates"][to.code.toUpperCase()] *
      amount /
      currency_rates_json["rates"][from.code.toUpperCase()];
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    CurrencyInput input1 = CurrencyInput(),
        input2 = CurrencyInput(
          isEnabled: false,
        );
    return FutureBuilder(
        future: fetchRates(),
        builder: (ctx, snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Scaffold(
              body: Center(
                child: CircularProgressIndicator(),
              ),
            );
          } else {
            currency_rates_json = json.decode(snapshot.data!.body);
            return Scaffold(
              resizeToAvoidBottomInset: false,
              body: Padding(
                padding: const EdgeInsets.only(top: 80, left: 20, right: 20),
                child: Column(
                  mainAxisSize: MainAxisSize.max,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      "Convert",
                      style: TextStyle(
                        fontSize: 50,
                        fontWeight: FontWeight.bold,
                        color: Theme.of(context).primaryColor,
                      ),
                    ),
                    const SizedBox(
                      height: 70,
                    ),
                    input1,
                    const SizedBox(
                      height: 40,
                    ),
                    input2,
                    Padding(
                      padding: const EdgeInsets.symmetric(
                        vertical: 50,
                        horizontal: 30,
                      ),
                      child: Align(
                        alignment: Alignment.bottomRight,
                        child: ElevatedButton(
                          style: ElevatedButton.styleFrom(
                              padding: const EdgeInsets.symmetric(
                                  horizontal: 23, vertical: 10)),
                          onPressed: () {
                            double? val = double.tryParse(input1.getText);
                            if (val != null) {
                              input2.setText(
                                calculate(
                                  val,
                                  input1.getCurrency,
                                  input2.getCurrency,
                                ).toString(),
                              );
                            }
                          },
                          child: const Text(
                            "Calculate",
                            style: TextStyle(
                              fontSize: 25,
                            ),
                          ),
                        ),
                      ),
                    )
                  ],
                ),
              ),
            );
          }
        });
  }
}

CurrencyInput.dart

import 'package:currency_converter/FlagCurrencyButton.dart';
import 'package:currency_picker/currency_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class CurrencyInput extends StatefulWidget {
  TextEditingController textEditingController = TextEditingController();
  bool isEnabled;
  Currency currency = CurrencyService().findByCode('egp')!;
  String get getText => textEditingController.text;
  Currency get getCurrency => currency;
  late void Function(String) setText;
  late void Function(String) setCurrency;
  CurrencyInput({
    this.isEnabled = true,
    super.key,
  });

  @override
  State<CurrencyInput> createState() => _CurrencyInputState();
}

class _CurrencyInputState extends State<CurrencyInput> {
  @override
  Widget build(BuildContext context) {
    widget.setText = (String value) {
      setState(
        () {
          double? tmp = double.tryParse(value);
          assert(tmp != null);
          widget.textEditingController.text =
              double.parse(tmp!.toStringAsFixed(4)).toString();
        },
      );
    };

    return Container(
      decoration: BoxDecoration(
        border: Border.all(
          color: Colors.black,
        ),
        borderRadius: const BorderRadius.all(
          Radius.circular(20),
        ),
      ),
      width: double.infinity,
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
        child: Row(
          children: [
            FlagCurrencyButton(
              CurrentCurrency: widget.currency,
              CurrencyChanged: (Currency cur) {
                setState(() {
                  widget.currency = cur;
                });
              },
            ),
            const SizedBox(
              width: 4,
            ),
            Flexible(
              child: TextFormField(
                controller: widget.textEditingController,
                enabled: widget.isEnabled,
                keyboardType: TextInputType.number,
                inputFormatters: [
                  // FilteringTextInputFormatter.allow(RegExp(r"[0-9.]")),
                  TextInputFormatter.withFunction(
                    (oldValue, newValue) {
                      return newValue.text.isEmpty
                          ? newValue
                          : double.tryParse(newValue.text) == null
                              ? oldValue
                              : newValue;
                    },
                  ),
                ],
                style: const TextStyle(
                  fontSize: 22,
                  color: Colors.black,
                ),
              ),
            ),
            const SizedBox(
              width: 12,
            ),
            Text(
              widget.currency.symbol,
              style: const TextStyle(
                fontSize: 20,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

FlagCurrencyButton.dart

import 'package:country_flags/country_flags.dart';
import 'package:currency_picker/currency_picker.dart';
import 'package:flutter/material.dart';

class FlagCurrencyButton extends StatelessWidget {
  final Currency CurrentCurrency;
  final Function(Currency) CurrencyChanged;
  const FlagCurrencyButton({
    required this.CurrentCurrency,
    required this.CurrencyChanged,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return TextButton(
      style: TextButton.styleFrom(
        foregroundColor: Colors.black,
      ),
      onPressed: () {
        showCurrencyPicker(
          context: context,
          showFlag: true,
          showSearchField: true,
          showCurrencyName: true,
          showCurrencyCode: true,
          favorite: [],
          onSelect: (Currency currency) {
            CurrencyChanged(currency);
          },
        );
      },
      child: Row(
        children: [
          CountryFlag.fromCountryCode(
            CurrentCurrency.code.substring(0, 2),
            height: 37,
            width: 50,
            borderRadius: 8,
          ),
          const SizedBox(
            width: 10,
          ),
          Text(
            CurrentCurrency.code.toUpperCase(),
            style: const TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
          const Icon(Icons.keyboard_arrow_down),
        ],
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者Omar_Hafez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 01:54:54