Flutter中FutureBuilder调试正常但APK构建后抛出SocketException
我来帮你排查并解决这个问题,你遇到的调试正常但打包APK后抛出SocketException的情况,主要有几个核心原因,结合你的代码我们一步步修复:
1. 优先检查Android网络权限(最常见的根源)
Flutter调试模式下会默认赋予网络权限,但正式打包APK时,必须手动在Android配置文件中声明网络权限。
打开android/app/src/main/AndroidManifest.xml,在<manifest>标签内(注意不是<application>标签里)添加以下权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
没有这个权限的话,应用在正式环境下完全无法发起网络请求,直接触发SocketException。
2. 修复代码中的逻辑错误
你的代码里有几处明显的逻辑问题,不仅会导致异常,还会让UI显示混乱:
(1)FutureBuilder的状态判断完全颠倒
你当前的代码把hasData和错误显示搞反了,正确的逻辑应该是:请求完成后,先判断是否有错误,再判断是否有数据。修改这部分代码:
case ConnectionState.done: // 先处理错误情况 if (asyncSnapshot.hasError) { return Center( child: Text('加载失败: ${asyncSnapshot.error.toString()}'), ); } // 处理无数据情况 if (!asyncSnapshot.hasData || asyncSnapshot.data.isEmpty) { return Center(child: Text('暂无分类数据')); } // 有数据时显示列表 return ListView.builder( itemCount: asyncSnapshot.data.length, itemBuilder: (context, int index) { return ListTile( title: Text(asyncSnapshot.data[index]['name']), ); }, );
(2)避免重复发起网络请求
你在initState里调用了getCategories(),同时FutureBuilder的future参数又直接调用了一次,这会导致两次重复请求,还可能引发状态不一致。正确的做法是在State中保存Future对象:
// 在_State类中添加变量 Future<List> _categoriesFuture; @override void initState() { super.initState(); // 只初始化一次请求 _categoriesFuture = getCategories(); }
然后在FutureBuilder中使用这个变量:
future: _categoriesFuture,
(3)优化URL的处理方式
现在Flutter推荐使用Uri.parse()来处理网络地址,替代旧的Uri.encodeFull(),修改getCategories()方法:
Future<List> getCategories() async { var url = Uri.parse('https://opentdb.com/api_category.php'); var response = await http.get(url, headers: {"Accept": "application/json"}); var jsonResponse = convert.jsonDecode(response.body); List categories = jsonResponse["trivia_categories"]; return categories; }
3. 额外排查:HTTPS证书问题(概率较低)
如果添加权限、修复代码后还是有问题,且你的测试设备是Android 7.0以上,需要确认目标网站的HTTPS证书是否合法。不过opentdb.com是正规公开的API服务,这个情况大概率不会出现;如果是你自己的服务器,可能需要配置Android网络安全配置文件来允许自定义证书。
修复后的完整代码
import 'package:flutter/material.dart'; import 'dart:async'; import 'package:http/http.dart' as http; import 'dart:convert' as convert; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Quize It Up'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { late Future<List> _categoriesFuture; @override void initState() { super.initState(); _categoriesFuture = getCategories(); } Future<List> getCategories() async { var url = Uri.parse('https://opentdb.com/api_category.php'); var response = await http.get(url, headers: {"Accept": "application/json"}); var jsonResponse = convert.jsonDecode(response.body); List categories = jsonResponse["trivia_categories"]; return categories; } Widget mainGui() { return Container( child: FutureBuilder( future: _categoriesFuture, builder: (BuildContext context, AsyncSnapshot<List> asyncSnapshot) { switch (asyncSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.waiting: return const Center( child: CircularProgressIndicator(), ); case ConnectionState.none: return ElevatedButton( onPressed: () { setState(() { _categoriesFuture = getCategories(); }); }, child: const Text('重新加载'), ); case ConnectionState.done: if (asyncSnapshot.hasError) { return Center( child: Text('加载失败: ${asyncSnapshot.error.toString()}'), ); } if (!asyncSnapshot.hasData || asyncSnapshot.data.isEmpty) { return const Center(child: Text('暂无分类数据')); } return ListView.builder( itemCount: asyncSnapshot.data.length, itemBuilder: (context, int index) { return ListTile( title: Text(asyncSnapshot.data[index]['name']), ); }, ); } })); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Container( child: mainGui(), ), ); } }
内容的提问来源于stack exchange,提问作者GeekyShiva

