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

Flutter应用连接IoT扬声器WiFi后无法发送GET/POST请求求助

问题:Flutter应用内连接IoT扬声器WiFi后无法发送GET/POST请求

我正在开发一款Flutter应用,用于连接带WiFi功能的IoT扬声器设备,需通过GET/POST API与设备通信并上传音频文件。通过安卓系统自带WiFi选项连接扬声器WiFi时,GET/POST请求(包括音频上传)均正常;但使用wiflutter包在应用内连接该WiFi后,完全无法发送任何GET/POST请求。

尝试过的WiFi相关包

  • package:wiflutter/wiflutter.dart:可成功连接WiFi,但连接后无法发送GET/POST请求
  • package:wifi_scan/wifi_scan.dart:仅支持WiFi扫描,无连接功能
  • package:wifi/wifi.dart:已过时,不支持空安全
  • package:wifi_connector/wifi_connector.dart:依赖过时的Kotlin插件版本,无法解决依赖问题

WiFi连接代码

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:nspeak/RecordingPage.dart';
import 'package:wifi_scan/wifi_scan.dart';
import 'package:wiflutter/wiflutter.dart';
import 'package:wiflutter/enums/enterprise_certificate_enum.dart';

// import 'package:wifi_iot/wifi_iot.dart'; -- 不支持空安全,无法使用
// import 'package:wifi_connector/wifi_connector.dart'; -- 使用旧版Kotlin插件,无法使用
// import 'package:wifi_configuration/wifi_configuration.dart'; -- 不支持空安全,无法使用
// import 'package:wifi/wifi.dart'; -- 不支持空安全,无法使用

scanNspeakwifi() async {
  resultcanstarscan = await WiFiScan.instance.canStartScan();
  if (resultcanstarscan == CanStartScan.yes) {
    scansuccess = await WiFiScan.instance.startScan();
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          padding: EdgeInsets.only(bottom: 25),
          duration: Durations.extralong4,
          backgroundColor: Colors.yellow,
          content: Center(
            child: Text('扫描中', style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: Colors.black)),
          ),
        ),
      );
    }
  } else {
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          padding: EdgeInsets.only(bottom: 25, left: 30, right: 30),
          duration: Durations.extralong4,
          backgroundColor: Colors.red,
          content: Center(
            child: Text('无法启动扫描,请检查是否已授予所有必要权限!', style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
          ),
        ),
      );
    }
  }

  cangetresults = await WiFiScan.instance.canGetScannedResults();
  if (cangetresults == CanGetScannedResults.yes) {
    scanresults = await WiFiScan.instance.getScannedResults();
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          padding: EdgeInsets.only(bottom: 25, left: 20, right: 20),
          duration: Durations.extralong4,
          backgroundColor: Colors.green,
          content: Center(
            child: Text('扫描完成!点击「获取结果」查看附近的NSpeak设备', style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
          ),
        ),
      );
    }
  } else {
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          padding: EdgeInsets.only(bottom: 25, left: 20, right: 20),
          duration: Durations.extralong4,
          backgroundColor: Colors.red,
          content: Center(
            child: Text('无扫描结果,请检查是否已授予所有必要权限!', style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
          ),
        ),
      );
    }
  }
}

getNspeakwifi() async {
  var resultsavail = await WiFiScan.instance.onScannedResultsAvailable;
  var checkresult = await resultsavail.isEmpty;
  if (!checkresult) {
    setState(() {
      wifiNetworks = scanresults;
      _shownetworks = true;
    });
  } else {
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          padding: EdgeInsets.only(bottom: 25, left: 20, right: 20),
          duration: Durations.extralong4,
          backgroundColor: Colors.red,
          content: Center(
            child: Text('附近无NSpeak设备!', style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
          ),
        ),
      );
    }
  }
}

Widget displayNspeak() {
  var value;
  nspeakdevices = []; /*每次点击「获取结果」都重新初始化列表,避免重复添加WiFi热点*/
  for (int i = 0; i < wifiNetworks.length;) {
    var entry = wifiNetworks[i];
    if (entry.ssid.contains("NSPEAK")) {
      value = entry.ssid;
      print("已加入列表");
      nspeakdevices.add(value);
    }
    i++;
  }

  if (nspeakdevices.isNotEmpty) {
    return ListView.builder(
      itemCount: nspeakdevices.length,
      itemBuilder: (BuildContext context, int index) {
        var nspeakvalue = nspeakdevices[index];
        return Row(
          children: [
            Expanded(child: ListTile(title: Text(nspeakvalue))),
            TextButton(
              onPressed: () async {
                var con = await wiflutter.connect(
                  ssid: nspeakvalue,
                  password: '********',
                );
              },
              child: Text("连接", style: TextStyle(fontSize: 10)),
            ),
            TextButton(
              onPressed: () async {
                var con = await wiflutter.disconnect();
              },
              child: Text("断开", style: TextStyle(fontSize: 10)),
            ),
          ],
        );
      },
    );
  } else {
    return const Text("附近无NSpeak设备");
  }
}

音频文件上传代码

fileupload() async {
  try {
    var headersget = {'Authorization': 'Authorization text'};
    requestget = http.Request('GET', Uri.parse('http://192.168.4.1/getlogin'));
    requestget.headers.addAll(headersget);

    http.StreamedResponse responseget = await requestget.send();
    if (responseget.statusCode == 200) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          padding: EdgeInsets.only(bottom: 50, left: 50),
          duration: Durations.extralong4,
          backgroundColor: Colors.white,
          content: Text('登录成功!', style: TextStyle(fontSize: 15, color: Colors.black, fontWeight: FontWeight.bold)),
        ),
      );
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          padding: EdgeInsets.only(bottom: 50, left: 50),
          duration: Durations.extralong4,
          content: Text('登录失败', style: TextStyle(fontSize: 15, color: Colors.black, fontWeight: FontWeight.bold)),
        ),
      );
    }

    var headerspost = {'Content-Type': 'multipart/form-data;', 'Authorization': 'authorization text'};
    requestpost = http.MultipartRequest('POST', Uri.parse('http://192.168.4.1/upload'));

    requestpost.files.add(await http.MultipartFile.fromPath('mp3', audiofilepath));
    print("文件添加成功");
    requestpost.headers.addAll(headerspost);
    print("请求头添加成功");

    http.StreamedResponse responsepost = await requestpost.send();
    print("POST请求已发送");

    if (responsepost.statusCode == 200 || responsepost.statusCode == 302) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          padding: EdgeInsets.only(bottom: 50, left: 50),
          duration: Durations.extralong4,
          backgroundColor: Colors.white,
          content: Text('文件上传成功!', style: TextStyle(fontSize: 15, color: Colors.black, fontWeight: FontWeight.bold)),
        ),
      );
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          padding: EdgeInsets.only(bottom: 50, left: 50),
          duration: Durations.extralong4,
          content: Text('文件上传失败', style: TextStyle(fontSize: 15, color: Colors.black, fontWeight: FontWeight.bold)),
        ),
      );
    }
  } catch (e) {
    print('音频上传错误: $e');
  }
}

解决方案

1. 检查并补全权限

Android 10+版本下,应用内连接WiFi后需额外权限才能访问本地网络,在AndroidManifest.xml中添加:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- Android 10+ 扫描WiFi必须的位置权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

同时确保应用已动态申请这些权限。

2. 验证连接有效性

wiflutter的connect方法返回成功不代表网络已稳定,添加连接后校验:

var con = await wiflutter.connect(ssid: nspeakvalue, password: '********');
// 检查当前连接的SSID是否匹配
var currentSSID = await wiflutter.getCurrentWifiSSID();
if (currentSSID == nspeakvalue) {
  // 等待网络稳定
  await Future.delayed(const Duration(seconds: 2));
  // ping设备测试连通性
  try {
    final result = await InternetAddress.lookup('192.168.4.1');
    if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
      print('设备可连通');
    }
  } on SocketException catch (_) {
    print('无法连通设备');
  }
}

3. 允许明文HTTP请求

如果IoT设备使用未加密的HTTP,Android 9+默认会拦截,需配置网络安全规则:

  • 在android/app/src/main/res/xml创建network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">192.168.4.1</domain>
    </domain-config>
</network-security-config>
  • 在AndroidManifest.xml的application标签中引用:
<application
    ...
    android:networkSecurityConfig="@xml/network_security_config">

4. 替换WiFi连接包

若第三方包问题无法解决,可直接编写Android原生Kotlin代码实现WiFi连接,通过MethodChannel与Flutter通信,精准控制连接流程。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 21:47:34