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
相关产品推荐
相关产品推荐

