文章目錄
- MAUI項目架構設計
- 平臺特定實現
- 接口定義
- Windows平臺實現
- Android平臺實現
- MAUI主界面實現
- 依賴注入配置
- 相關學習資源
- .NET MAUI開發
- 移動端開發
- 平臺特定實現
- 依賴注入與架構
- 移動應用發布
- 跨平臺開發最佳實踐
- 性能優化
- 測試與調試
- 開源項目參考
MAUI項目架構設計
平臺特定實現
接口定義
public interface ISerialPortService
{Task<string[]> GetAvailablePortsAsync();Task<bool> ConnectAsync(string portName, int baudRate);Task DisconnectAsync();Task SendDataAsync(byte[] data);Task SendTextAsync(string text);event EventHandler<SerialDataEventArgs> DataReceived;event EventHandler<bool> ConnectionChanged;bool IsConnected { get; }
}
public class SerialDataEventArgs : EventArgs
{public byte[] Data { get; set; }public string Text { get; set; }public DateTime Timestamp { get; set; }
}
Windows平臺實現
#if WINDOWS
using System.IO.Ports;
public class WindowsSerialPortService : ISerialPortService
{private SerialPort _serialPort;private bool _isConnected;public bool IsConnected => _isConnected;public event EventHandler<SerialDataEventArgs> DataReceived;public event EventHandler<bool> ConnectionChanged;public WindowsSerialPortService(){_serialPort = new SerialPort();_serialPort.DataReceived += OnDataReceived;}public async Task<string[]> GetAvailablePortsAsync(){return await Task.FromResult(SerialPort.GetPortNames());}public async Task<bool> ConnectAsync(string portName, int baudRate){try{if (_isConnected)await DisconnectAsync();_serialPort.PortName = portName;_serialPort.BaudRate = baudRate;_serialPort.DataBits = 8;_serialPort.Parity = Parity.None;_serialPort.StopBits = StopBits.One;_serialPort.Open();_isConnected = true;ConnectionChanged?.Invoke(this, true);return true;}catch (Exception ex){_isConnected = false;ConnectionChanged?.Invoke(this, false);return false;}}public async Task DisconnectAsync(){try{if (_serialPort?.IsOpen == true){_serialPort.Close();}_isConnected = false;ConnectionChanged?.Invoke(this, false);}catch (Exception){}}public async Task SendDataAsync(byte[] data){if (!_isConnected || !_serialPort.IsOpen)throw new InvalidOperationException("串口未連接");await Task.Run(() => _serialPort.Write(data, 0, data.Length));}public async Task SendTextAsync(string text){var data = System.Text.Encoding.UTF8.GetBytes(text);await SendDataAsync(data);}private void OnDataReceived(object sender, SerialDataReceivedEventArgs e){try{var buffer = new byte[_serialPort.BytesToRead];var bytesRead = _serialPort.Read(buffer, 0, buffer.Length);var eventArgs = new SerialDataEventArgs{Data = buffer.Take(bytesRead).ToArray(),Text = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead),Timestamp = DateTime.Now};DataReceived?.Invoke(this, eventArgs);}catch (Exception){}}
}
#endif
Android平臺實現
#if ANDROID
using Android.Hardware.Usb;
using AndroidX.Core.Content;
public class AndroidSerialPortService : ISerialPortService
{private UsbManager _usbManager;private UsbDevice _usbDevice;private UsbDeviceConnection _connection;private UsbInterface _interface;private UsbEndpoint _endpointIn;private UsbEndpoint _endpointOut;private bool _isConnected;private CancellationTokenSource _readCancellation;public bool IsConnected => _isConnected;public event EventHandler<SerialDataEventArgs> DataReceived;public event EventHandler<bool> ConnectionChanged;public AndroidSerialPortService(){var context = Platform.CurrentActivity ?? Android.App.Application.Context;_usbManager = (UsbManager)context.GetSystemService(Android.Content.Context.UsbService);}public async Task<string[]> GetAvailablePortsAsync(){var deviceList = _usbManager.DeviceList;var portNames = new List<string>();foreach (var device in deviceList.Values){if (IsSerialDevice(device)){portNames.Add($"USB-{device.DeviceName}");}}return portNames.ToArray();}public async Task<bool> ConnectAsync(string portName, int baudRate){try{var deviceList = _usbManager.DeviceList;foreach (var device in deviceList.Values){if ($"USB-{device.DeviceName}" == portName){_usbDevice = device;break;}}if (_usbDevice == null)return false;if (!_usbManager.HasPermission(_usbDevice)){return false;}_connection = _usbManager.OpenDevice(_usbDevice);if (_connection == null)return false;_interface = _usbDevice.GetInterface(0);_connection.ClaimInterface(_interface, true);for (int i = 0; i < _interface.EndpointCount; i++){var endpoint = _interface.GetEndpoint(i);if (endpoint.Direction == UsbAddressing.In)_endpointIn = endpoint;else if (endpoint.Direction == UsbAddressing.Out)_endpointOut = endpoint;}_isConnected = true;ConnectionChanged?.Invoke(this, true);StartDataReading();return true;}catch (Exception){_isConnected = false;ConnectionChanged?.Invoke(this, false);return false;}}public async Task DisconnectAsync(){_isConnected = false;_readCancellation?.Cancel();_connection?.ReleaseInterface(_interface);_connection?.Close();ConnectionChanged?.Invoke(this, false);}public async Task SendDataAsync(byte[] data){if (!_isConnected || _connection == null || _endpointOut == null)throw new InvalidOperationException("設備未連接");await Task.Run(() =>{_connection.BulkTransfer(_endpointOut, data, data.Length, 1000);});}public async Task SendTextAsync(string text){var data = System.Text.Encoding.UTF8.GetBytes(text);await SendDataAsync(data);}private void StartDataReading(){_readCancellation = new CancellationTokenSource();Task.Run(async () =>{var buffer = new byte[1024];while (!_readCancellation.Token.IsCancellationRequested && _isConnected){try{var bytesRead = _connection.BulkTransfer(_endpointIn, buffer, buffer.Length, 100);if (bytesRead > 0){var eventArgs = new SerialDataEventArgs{Data = buffer.Take(bytesRead).ToArray(),Text = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead),Timestamp = DateTime.Now};DataReceived?.Invoke(this, eventArgs);}await Task.Delay(10);}catch (Exception){}}});}private bool IsSerialDevice(UsbDevice device){return device.DeviceClass == UsbClass.CdcData || device.DeviceClass == UsbClass.Comm;}
}
#endif
MAUI主界面實現
public partial class MainPage : ContentPage
{private readonly ISerialPortService _serialService;private readonly ObservableCollection<string> _receivedMessages;public MainPage(ISerialPortService serialService){InitializeComponent();_serialService = serialService;_receivedMessages = new ObservableCollection<string>();MessagesCollectionView.ItemsSource = _receivedMessages;_serialService.DataReceived += OnDataReceived;_serialService.ConnectionChanged += OnConnectionChanged;LoadAvailablePorts();}private async void LoadAvailablePorts(){try{var ports = await _serialService.GetAvailablePortsAsync();PortPicker.ItemsSource = ports;if (ports.Length > 0)PortPicker.SelectedIndex = 0;}catch (Exception ex){await DisplayAlert("錯誤", $"加載串口列表失敗: {ex.Message}", "確定");}}private async void OnConnectClicked(object sender, EventArgs e){try{if (_serialService.IsConnected){await _serialService.DisconnectAsync();}else{if (PortPicker.SelectedItem == null){await DisplayAlert("提示", "請選擇串口", "確定");return;}var portName = PortPicker.SelectedItem.ToString();var baudRate = int.Parse(BaudRatePicker.SelectedItem?.ToString() ?? "9600");var success = await _serialService.ConnectAsync(portName, baudRate);if (!success){await DisplayAlert("錯誤", "連接失敗", "確定");}}}catch (Exception ex){await DisplayAlert("錯誤", $"連接操作失敗: {ex.Message}", "確定");}}private async void OnSendClicked(object sender, EventArgs e){try{if (!_serialService.IsConnected){await DisplayAlert("提示", "請先連接串口", "確定");return;}var text = SendEntry.Text;if (string.IsNullOrWhiteSpace(text)){await DisplayAlert("提示", "請輸入要發送的內容", "確定");return;}await _serialService.SendTextAsync(text + "\r\n");SendEntry.Text = string.Empty;_receivedMessages.Add($"[發送] {DateTime.Now:HH:mm:ss} - {text}");}catch (Exception ex){await DisplayAlert("錯誤", $"發送失敗: {ex.Message}", "確定");}}private void OnDataReceived(object sender, SerialDataEventArgs e){MainThread.BeginInvokeOnMainThread(() =>{_receivedMessages.Add($"[接收] {e.Timestamp:HH:mm:ss} - {e.Text.Trim()}");if (_receivedMessages.Count > 0){MessagesCollectionView.ScrollTo(_receivedMessages.Last());}});}private void OnConnectionChanged(object sender, bool isConnected){MainThread.BeginInvokeOnMainThread(() =>{ConnectButton.Text = isConnected ? "斷開" : "連接";StatusLabel.Text = isConnected ? "已連接" : "未連接";StatusLabel.TextColor = isConnected ? Colors.Green : Colors.Red;PortPicker.IsEnabled = !isConnected;BaudRatePicker.IsEnabled = !isConnected;SendButton.IsEnabled = isConnected;SendEntry.IsEnabled = isConnected;});}private async void OnRefreshPortsClicked(object sender, EventArgs e){await LoadAvailablePorts();}
}
依賴注入配置
public static class MauiProgram
{public static MauiApp CreateMauiApp(){var builder = MauiApp.CreateBuilder();builder.UseMauiApp<App>().ConfigureFonts(fonts =>{fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");});
#if WINDOWSbuilder.Services.AddSingleton<ISerialPortService, WindowsSerialPortService>();
#elif ANDROIDbuilder.Services.AddSingleton<ISerialPortService, AndroidSerialPortService>();
#elif IOSbuilder.Services.AddSingleton<ISerialPortService, iOSSerialPortService>();
#elif MACCATALYSTbuilder.Services.AddSingleton<ISerialPortService, MacCatalystSerialPortService>();
#endifbuilder.Services.AddTransient<MainPage>();return builder.Build();}
}
相關學習資源
.NET MAUI開發
- .NET MAUI官方文檔 - 微軟官方MAUI開發指南
- MAUI Community Toolkit - MAUI社區工具包
- .NET MAUI Samples - 官方MAUI示例項目
- MAUI Blazor - MAUI混合應用開發
移動端開發
- Android開發者文檔 - Google官方Android開發指南
- iOS開發文檔 - Apple官方iOS開發文檔
- Xamarin.Forms指南 - Xamarin.Forms開發文檔
- Mobile DevOps - 移動應用DevOps平臺
平臺特定實現
- Android USB Host - Android USB主機模式
- iOS External Accessory - iOS外部配件框架
- Windows Runtime API - Windows Runtime API文檔
- macOS IOKit - macOS硬件訪問框架
依賴注入與架構
- Microsoft.Extensions.DependencyInjection - .NET依賴注入
- MVVM Pattern - MVVM架構模式
- CommunityToolkit.Mvvm - MVVM工具包
- Prism Framework - 企業級MVVM框架
移動應用發布
- Google Play Console - Android應用發布平臺
- App Store Connect - iOS應用發布平臺
- Microsoft Store - Windows應用商店
- App Center Distribution - 應用分發服務
跨平臺開發最佳實踐
- Platform Behaviors - 平臺集成最佳實踐
- Conditional Compilation - 條件編譯指令
性能優化
- MAUI Performance - MAUI性能優化指南
- Memory Management - 內存管理最佳實踐
- Battery Optimization - 電池優化策略
測試與調試
- MAUI Unit Testing - MAUI單元測試
- UI Testing - UI自動化測試
- Remote Debugging - 遠程調試工具
- App Center Analytics - 應用分析服務
開源項目參考
- .NET Podcasts App - 微軟MAUI示例應用
- Weather MAUI App - 天氣應用示例
