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

Flutter应用在Android Studio模拟器正常,真机APK无法加载API标题

问题排查与解决方案

核心问题分析

从代码和现象来看,真机无法显示API返回标题主要源于以下几个关键问题:

  1. 输入URL未传递到API请求页面
    用户在FormularioYoutube中输入的Youtube链接存放在_controller里,但点击跳转按钮时,没有将该URL传递给procesarBotonParaDescargarMp3页面,导致API请求没有目标链接,自然无法返回有效数据。模拟器可能因缓存或测试环境的默认数据侥幸显示内容,而真机无此类缓存,因此表现为空。

  2. StatelessWidget中错误声明TextEditingController
    在FormularioYoutube的build方法内声明_controller,会导致组件每次重建时都创建新控制器,输入内容可能丢失,且无法稳定保存用户输入。

  3. Release包缺少网络权限
    Flutter Debug模式会自动添加网络权限,但Release打包的APK需要手动配置权限,否则真机无法发起网络请求。

  4. 未处理API请求失败场景
    FutureBuilder仅处理了加载状态,未处理请求失败(snapshot.hasError)或数据为空的情况,出错时无法直观定位问题。

修复后的代码示例

1. 重构页面逻辑,正确传递URL

void main() => runApp(const HomePage());

class HomePage extends StatelessWidget {
  const HomePage({super.key});
  @override
  Widget build(BuildContext context) {
    const appTitle = 'Youtube MP3';
    return MaterialApp(
      title: appTitle,
      home: Scaffold(
        appBar: AppBar(title: const Text(appTitle)),
        body: const FormularioYoutube(),
      ),
    );
  }
}

// 改为StatefulWidget,稳定保存控制器与输入内容
class FormularioYoutube extends StatefulWidget {
  const FormularioYoutube({super.key});

  @override
  State<FormularioYoutube> createState() => _FormularioYoutubeState();
}

class _FormularioYoutubeState extends State<FormularioYoutube> {
  final _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose(); // 销毁时释放资源
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16),
          child: TextFormField(
            controller: _controller,
            decoration: const InputDecoration(
              border: UnderlineInputBorder(),
              labelText: 'Introduzca Url de Youtube',
            ),
          ),
        ),
        MaterialButton(
          onPressed: () {
            final url = _controller.text.trim();
            if (url.isNotEmpty) {
              // 跳转时传递输入的URL
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => ProcesarBotonParaDescargarMp3(youtubeUrl: url),
                ),
              );
            }
          },
          color: Colors.blue,
          child: const Text('Descargar MP3', style: TextStyle(color: Colors.white)),
        )
      ],
    );
  }
}

// 接收URL并传入API请求
class ProcesarBotonParaDescargarMp3 extends StatelessWidget {
  final String youtubeUrl;

  const ProcesarBotonParaDescargarMp3({super.key, required this.youtubeUrl});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Second Route")),
      body: FutureBuilder<YoutubeMp3?>(
        // 将URL传入API请求方法
        future: ApiRequest().getYoutubeMp3(youtubeUrl),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator.adaptive());
          }
          if (snapshot.hasError) {
            // 处理请求错误
            return Center(child: Text('请求失败: ${snapshot.error}'));
          }
          final YoutubeMp3? yt = snapshot.data;
          if (yt == null || yt.title.isEmpty) {
            // 处理数据为空的情况
            return const Center(child: Text('未获取到视频信息'));
          }
          return ListView.builder(
            itemCount: 1,
            itemBuilder: (context, index) {
              return ListTile(title: Text(yt.title));
            },
          );
        },
      ),
    );
  }
}

// 调整ApiRequest以接收URL参数
class ApiRequest {
  Future<YoutubeMp3?> getYoutubeMp3(String youtubeUrl) async {
    // 实现带URL参数的API请求逻辑,示例如下:
    final response = await http.get(Uri.parse('你的API地址?url=${Uri.encodeComponent(youtubeUrl)}'));
    if (response.statusCode == 200) {
      return YoutubeMp3.fromJson(jsonDecode(response.body));
    }
    return null;
  }
}

class YoutubeMp3 {
  final String title;
  YoutubeMp3({required this.title});
  factory YoutubeMp3.fromJson(Map<String, dynamic> json) {
    return YoutubeMp3(title: json['title'] ?? '');
  }
}

2. 添加网络权限

打开android/app/src/main/AndroidManifest.xml,在<manifest>标签内添加:

<uses-permission android:name="android.permission.INTERNET" />

额外注意事项

  • 若API使用HTTP协议,Android 9及以上设备默认禁止明文请求,需在<application>标签内添加android:usesCleartextTraffic="true",或迁移API到HTTPS。
  • 确保传递给API的Youtube URL格式正确,可通过Uri.encodeComponent编码避免参数解析错误。
  • 使用flutter run --release命令直接在真机测试Release包,便于调试错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 12:09:20