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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 05:03:42