一、 .NET MAUI网络请求的日常痛点与高效思路

很多用.NET MAUI写跨平台APP的开发者,都会遇到网络请求的糟心事:比如APP加载慢、弱网下经常失败、用户切换页面后请求还在跑、大文件上传时卡顿。这些问题大多不是技术难度高,而是没注意基础细节,做好这些细节就能让网络请求效率翻几倍。

1.1 常见低效问题解析

新手写网络请求时,最容易犯的几个错:每次发请求就new一个HttpClient——这玩意儿创建和销毁都要耗资源,重复新建会占内存还可能导致端口冲突;请求不设超时,用户等半天没反应,以为APP卡了;遇到偶尔的网络闪断,请求直接失败,只能手动重试;用户快速切换页面,旧页面的请求还在跑,浪费流量还占后台资源。

1.2 高效的核心思路

要解决上面的问题,核心就是几个小方向:把HttpClient做成全局共享的实例,只创建一次;给每个请求设合理超时(10秒左右最合适);给临时错误加自动重试(比如网络波动时重试2-3次);用户离开页面就取消未完成的请求;静态数据(比如分类列表)用缓存,不用每次发网络请求。

二、 具体实现步骤与完整示例

下面的代码都用.NET MAUI原生C#栈,所有示例都加了注释,直接复制就能用。

2.1 复用HttpClient的正确姿势

全局共享HttpClient是最基础的优化,在.NET MAUI里用依赖注入实现,不用自己管理实例生命周期:

using Microsoft.Maui;
using Microsoft.Maui.Hosting;
using System.Net.Http;

namespace MauiNetworkDemo
{
    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder.UseMauiApp<App>();

            // 注册全局HttpClient,设置统一配置
            builder.Services.AddSingleton(new HttpClient(new HttpClientHandler())
            {
                // 设置10秒超时,避免请求无限等待
                Timeout = TimeSpan.FromSeconds(10),
                // 自定义UA,让服务器识别是.NET MAUI客户端,避免被拦截
                DefaultRequestHeaders = { { "User-Agent", ".NET MAUI Demo App" } }
            });

            return builder.Build();
        }
    }
}

2.2 带重试机制的请求封装

遇到临时错误(比如502、网络波动)自动重试,用Polly库,先在Nuget装PollyPolly.Extensions.Http

using Polly;
using Polly.Extensions.Http;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace MauiNetworkDemo.Services
{
    public class ApiService
    {
        private readonly HttpClient _httpClient;

        // 构造函数注入全局HttpClient
        public ApiService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        // 带重试的GET请求,支持取消令牌
        public async Task<string> GetDataWithRetryAsync(string url, CancellationToken ctsToken)
        {
            // 定义重试策略:最多3次,指数退避(第一次1秒、第二次2秒、第三次4秒)
            // 避免短时间发多个请求给服务器,减少压力
            var retryPolicy = HttpPolicyExtensions
                .HandleTransientHttpError() // 只处理临时错误,比如网络错误、5xx错误,4xx错误不重试
                .WaitAndRetryAsync(3, retryAttemp => TimeSpan.FromSeconds(Math.Pow(2, retryAttemp)));

            // 执行带重试的请求,传入取消令牌
            return await retryPolicy.ExecuteAsync(async () =>
            {
                var response = await _httpClient.GetAsync(url, ctsToken);
                // 如果响应状态码不是成功(比如404、500)直接抛异常,触发重试
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsStringAsync();
            }, ctsToken);
        }
    }
}

2.3 请求取消与冗余处理

用户切换页面时,取消当前页面的请求,避免浪费资源:

using Microsoft.Maui.Controls;
using System;
using System.Threading;

namespace MauiNetworkDemo.Pages
{
    public class ListPage : ContentPage
    {
        private CancellationTokenSource _cts;
        private readonly ApiService _apiService;

        public ListPage(ApiService apiService)
        {
            _apiService = apiService;
            LoadListData(); // 页面加载时请求数据
        }

        private async void LoadListData()
        {
            _cts = new CancellationTokenSource();
            try
            {
                // 调用带重试的请求,传入取消令牌
                var data = await _apiService.GetDataWithRetryAsync("https://api.example.com/goods/list", _cts.Token);
                // 把数据显示到列表UI
                Console.WriteLine("加载列表数据:" + data);
            }
            catch (OperationCanceledException)
            {
                // 请求被取消,不用处理,避免报错
                Console.WriteLine("请求已取消,停止加载");
            }
            catch (Exception ex)
            {
                // 处理其他错误,比如网络完全不通
                Console.WriteLine("请求失败:" + ex.Message);
            }
        }

        // 页面消失时(比如跳转到详情页),取消正在进行的请求
        protected override void OnDisappearing()
        {
            base.OnDisappearing();
            _cts?.Cancel();
            _cts?.Dispose();
            _cts = null;
        }
    }
}

三、 不同场景下的高效实践

3.1 列表类接口的请求优化

列表是APP最常见的接口,比如商品列表、消息列表,优化点:用分页加载(每次拉20条),拉到最后一页就停止;重复请求的页用缓存,比如拉过第1页后,下次再进列表直接取缓存,后台悄悄更新新数据。

3.2 文件上传下载的高效处理

传大文件(比如头像、视频)时,别把整个文件转成字节数组,用流式传输,减少内存占用:

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace MauiNetworkDemo.Services
{
    public partial class ApiService
    {
        // 流式上传文件,避免占大量内存
        public async Task<bool> UploadImageAsync(System.IO.Stream imageStream, string fileName, CancellationToken ctsToken)
        {
            using var content = new MultipartFormDataContent();
            // 直接用文件流,不用加载整个文件到内存
            var streamContent = new StreamContent(imageStream);
            streamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
            content.Add(streamContent, "file", fileName);

            var response = await _httpClient.PostAsync("https://api.example.com/upload", content, ctsToken);
            return response.IsSuccessStatusCode;
        }
    }
}

3.3 离线场景的缓存策略

弱网或者无网时,APP能显示已加载的内容,不用报错,用MAUI的Preferences存简单数据(适合小容量数据),IFileSystem.CacheDirectory存文件:

using Microsoft.Maui.Storage;

// 缓存静态数据,比如商品分类
public async Task CacheStaticDataAsync(string key, string data)
{
    await Preferences.SetAsync(key, data);
}

// 获取缓存数据,返回null就是无缓存
public async Task<string> GetCachedDataAsync(string key)
{
    return await Preferences.GetAsync(key, null);
}

四、 技术选型与注意事项

4.1 技术优缺点分析

用原生HttpClient的优点是不用额外依赖,轻量,适合简单请求;缺点是需要自己写重试、超时等逻辑,灵活性差点。用Polly的优点是容错策略灵活,支持重试、熔断、降级等,缺点是要加Nuget包,不过体积可以忽略。

4.2 关键注意事项

别在主线程写网络请求,必须用async/await,否则UI会卡住;iOS要加ATS例外,在Info.plist里允许你的API域名,不然iOS会拦截HTTP请求;Android要加ACCESS_NETWORK_STATE权限(如果需要判断网络状态);请求头别乱加,服务器可能会过滤异常头。

五、 总结

.NET MAUI做网络请求的高效性,全在细节:复用HttpClient减少资源消耗,设超时避免假死,加重试处理临时问题,用取消令牌处理冗余请求,缓存静态数据减少流量。这些小技巧不需要复杂技术,只要做好,就能让APP在弱网、多场景下都流畅,给用户好体验。