Xamarin中TcpListener启动报NullReferenceException异常如何解决
Xamarin.Forms 中 TcpListener.Start() 抛出 NullReferenceException 排查修复
核心报错原因
- 绑定IP无效导致内部套接字未初始化:你硬编码的
192.168.1.2并非当前安卓设备实际持有的局域网IP,TcpListener在绑定不存在的IP时,内部核心Socket对象初始化失败为null,调用Start()方法时直接触发空引用异常。 - 缺少安卓网络权限:Xamarin.Android项目默认未开启网络访问权限,无权限时所有Socket类API调用都会被系统拦截,对象无法正常初始化。
- 主线程执行阻塞网络操作:你把TCP服务启动、同步等待客户端连接的逻辑全部写在了MainPage构造函数(UI主线程)中,安卓系统从3.0版本开始严格禁止主线程执行网络请求,会直接抛出异常;同时
AcceptTcpClient()是同步阻塞方法,写在构造函数会直接卡死UI线程触发ANR。 - 无空值防护:定时器中直接调用
stream.Read(),如果客户端未连接、流对象为null时会额外触发空引用。
修复步骤
1. 配置安卓必要权限
打开Xamarin.Android项目路径下的Properties/AndroidManifest.xml,在manifest根节点下添加以下权限配置:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
2. 修正TCP服务启动逻辑
- 不要硬编码监听IP,使用
IPAddress.Any监听设备所有网卡的指定端口,避免IP不匹配导致绑定失败。 - 把网络相关逻辑从构造函数移到页面
OnAppearing生命周期方法中,放入后台线程执行,避免阻塞UI线程。 - 所有跨线程UI更新必须切回主线程执行,避免线程访问异常。
- 页面销毁时主动释放TCP相关资源,避免内存泄漏。
3. 修复后可运行代码参考
using System; using Xamarin.Forms; using System.Net.Sockets; using System.Net; using Android.Widget; using System.Text; using System.Collections.Generic; using System.Threading.Tasks; namespace Teplica { public partial class MainPage : ContentPage { private int port = 8545; private bool door = true; TcpListener server; TcpClient client = null; NetworkStream stream = null; private bool TimerEnable = true; private List<string> list = new List<string>(); public MainPage() { InitializeComponent(); } protected override async void OnAppearing() { base.OnAppearing(); // 后台线程启动TCP服务,不阻塞UI await Task.Run(() => { try { // 监听所有网卡对应端口,无需硬编码IP server = new TcpListener(IPAddress.Any, port); server.Start(); // 阻塞等待ESP8266连接 client = server.AcceptTcpClient(); stream = client.GetStream(); // 连接成功后启动数据读取定时器 Device.StartTimer(TimeSpan.FromSeconds(1), TeplicaTimer); Device.BeginInvokeOnMainThread(() => { Toast.MakeText(Android.App.Application.Context, "TCP服务启动成功,等待传感器数据", ToastLength.Short).Show(); }); } catch (Exception ex) { Device.BeginInvokeOnMainThread(() => { Toast.MakeText(Android.App.Application.Context, $"服务启动失败:{ex.Message}", ToastLength.Long).Show(); }); } }); } private bool TeplicaTimer() { // 空值判断避免异常 if (stream == null || !stream.CanRead) { return TimerEnable; } try { byte[] data = new byte[256]; StringBuilder builder = new StringBuilder(); int bytes = stream.Read(data, 0, data.Length); if (bytes > 0) { builder.Append(Encoding.UTF8.GetString(data, 0, bytes)); list.Add(builder.ToString()); // 主线程更新UI Device.BeginInvokeOnMainThread(() => { DataVlazhnost.ItemsSource = null; DataVlazhnost.ItemsSource = list; }); } } catch (Exception ex) { Device.BeginInvokeOnMainThread(() => { Toast.MakeText(Android.App.Application.Context, $"数据读取失败:{ex.Message}", ToastLength.Short).Show(); }); } return TimerEnable; } private void OpenDoor_Clicked(object sender, EventArgs e) { if (door) { Toast.MakeText(Android.App.Application.Context, "Открытие двери", ToastLength.Short).Show(); OpenDoor.Text = "Закрыть"; door = false; } else { Toast.MakeText(Android.App.Application.Context, "Закрытие двери", ToastLength.Short).Show(); OpenDoor.Text = "Открыть"; door = true; } } private void Water_Clicked(object sender, EventArgs e) { Toast.MakeText(Android.App.Application.Context, "Подача воды", ToastLength.Short).Show(); } protected override void OnDisappearing() { base.OnDisappearing(); // 释放资源 TimerEnable = false; stream?.Close(); client?.Close(); server?.Stop(); } } }
额外注意事项
- ESP8266连接时需填写安卓设备当前实际的局域网IP,可在手机WiFi设置的已连接网络详情中查看,确保ESP8266和手机连接同一个WiFi局域网。
- 列表数据建议改用
ObservableCollection<string>代替List<string>,新增数据时会自动触发UI更新,无需每次重置ItemsSource。 - 后续如果需要接入多个传感器设备,需为每个连接的客户端开启独立的后台读写任务,不要使用单客户端逻辑。
内容的提问来源于stack exchange,提问作者user18289280
相关产品推荐
相关产品推荐

