.NET MAUI Android端HttpRequest偶发ObjectDisposed_Generic异常求助
问题:Android平台.NET MAUI偶发Http请求失败(ObjectDisposedException)
在Android平台使用.NET MAUI开发时,Http请求会偶发失败(同一请求多数情况下可成功),且该问题出现在多个接口上,无法定位原因。
抛出的异常信息
{ "HttpRequestError":0, "Message":"net_http_content_stream_copy_error", "Data": null, "InnerException":{ "ClassName":"System.ObjectDisposedException", "Message":"ObjectDisposed_Generic", "Data":null, "InnerException":null, "HelpURL":null, "StackTraceString":" at Java.Interop.JniPeerMembers.AssertSelf(IJavaPeerable )\n at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeVirtualInt32Method(String , IJavaPeerable , JniArgumentValue* )\n at Java.IO.InputStream.Read(Byte[] , Int32 , Int32 )\n at Android.Runtime.InputStreamInvoker.Read(Byte[] , Int32 , Int32 )\n at System.IO.Stream.<>c.<BeginReadInternal>b__38_0(Object <p0>) at System.Threading.Tasks.Task`1[[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].InnerInvoke() at System.Threading.Tasks.Task.<>c.<.cctor>b__281_0(Object obj) at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )\n--- End of stack trace from previous location --- at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object ) at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& , Thread )\n--- End of stack trace from previous location --- at System.IO.Stream.<CopyToAsync>g__Core|27_0(Stream , Stream , Int32 , CancellationToken ) at System.IO.BufferedStream.CopyToAsyncCore(Stream , Int32 , CancellationToken ) at System.Net.Http.StreamToStreamCopy.<CopyAsync>g__DisposeSourceAsync|1_0(Task , Stream ) at System.Net.Http.HttpContent.LoadIntoBufferAsyncCore(Task , MemoryStream )", "RemoteStackTraceString":null, "RemoteStackIndex":0, "ExceptionMethod":null, "HResult":-2146232798, "Source":"Java.Interop", "WatsonBuckets":null, "ObjectName":"Java.IO.InputStreamInvoker" }, "Source":"System.Net.Http", "HResult":-2146232798, "StackTrace":" at System.Net.Http.HttpContent.LoadIntoBufferAsyncCore(Task , MemoryStream ) at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage , HttpCompletionOption , CancellationTokenSource , Boolean , CancellationTokenSource , CancellationToken ) at MDC_Room.Services.ApiService.<GetRequestAync>d__5`1[[System.Collections.Generic.List`1[[MDC_Room.Model.DailyReport.ChildDailyReportListItemDto, MDC_Room, Version=6.8.0.0, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()" }
相关代码实现
API服务注册
builder.Services.AddSingleton<IApiService, ApiService>();
Get请求实现
public async Task<T?> GetRequestAync<T>(string url) { try { if (Connectivity.NetworkAccess == NetworkAccess.Internet) { var httpClientFactory = _serviceProvider.GetRequiredService<IHttpClientFactory>(); using var httpClient = httpClientFactory.CreateClient(AppConstants.HttpClient); using var response = await httpClient.GetAsync(url); if (response != null && response.IsSuccessStatusCode) { return JsonConvert.DeserializeObject<T?>(await response.Content.ReadAsStringAsync()); } else await ErrorUtility.HandleErrorAsync(response, url); } else await SnackbarUtility.ShowErrorSnackbarAsync("No Internet Connection", "Ok", null); } catch (Exception ex) { ErrorUtility.LogError(ex, $"{nameof(ApiService)}.{nameof(GetRequestAync)}"); } return default; }
Token委托处理程序
public class AuthenticationDelegatingHandler(IApiService _dataService) : DelegatingHandler { private const string TokenScheme = "Bearer"; protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { request.Headers.Authorization = new AuthenticationHeaderValue(TokenScheme, AppUtility.AccessToken ?? await TokenUtility.GetAccessTokenAsync()); var response = await base.SendAsync(request, cancellationToken); if (response.StatusCode == HttpStatusCode.Unauthorized) { var user = await RefreshTokenRequiredAsync(_dataService); if (user == null) return response; request.Headers.Authorization = new AuthenticationHeaderValue(TokenScheme, user.AccessToken); response = await base.SendAsync(request, cancellationToken); } return response; } private static async Task<LoginResponseModel?> RefreshTokenRequiredAsync(IApiService _dataService) { var refreshToken = await TokenUtility.GetRefreshTokenAsync(); if (!string.IsNullOrWhiteSpace(refreshToken)) { var input = new RefreshTokenDto { Token = refreshToken }; var user = await _dataService.PostRequestAync<LoginResponseModel?>(ApiEndPoints.Auth.RefreshToken, input); if (user != null) { await SecureStorage.SetAsync(CacheKeys.User, JsonConvert.SerializeObject(user)); await SecureStorage.SetAsync(CacheKeys.AccessToken, user.AccessToken!); await SecureStorage.SetAsync(CacheKeys.RefreshToken, user.RefreshToken); AppShellEventUtility.SetUser(user); } return user; } return null; } }
Polly重试配置
builder.Services.AddHttpClient(AppConstants.HttpClient, client => { client.BaseAddress = new Uri(BaseUrl); client.DefaultRequestHeaders.Add("Accept", "application/json"); client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs"); }) .AddHttpMessageHandler<AuthenticationDelegatingHandler>() .AddPolicyHandler(GetRetryPolicy); private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(HttpRequestMessage httpContext) { return HttpPolicyExtensions .HandleTransientHttpError() .OrResult(msg => msg.StatusCode == HttpStatusCode.GatewayTimeout) .OrResult(msg => msg.StatusCode == HttpStatusCode.RequestTimeout) .OrResult(msg => msg.StatusCode == HttpStatusCode.ServiceUnavailable) .Or<WebException>() .WaitAndRetryAsync( retryCount: 3, sleepDurationProvider: GetRetryDelay); } private static TimeSpan GetRetryDelay(int retryAttempt) { return retryAttempt switch { 1 => TimeSpan.FromMilliseconds(50), 2 => TimeSpan.FromMilliseconds(100), _ => TimeSpan.FromMilliseconds(250), }; }
排查建议与解决方案
1. 修复HttpRequestMessage复用问题
在AuthenticationDelegatingHandler的401重试逻辑中,不要复用原HttpRequestMessage。原请求在第一次发送后,内部的内容流可能已被释放,重试时会导致ObjectDisposedException。需要克隆请求再重试:
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var originalRequest = request; // 克隆请求用于重试 var cloneRequest = CloneHttpRequestMessage(originalRequest); cloneRequest.Headers.Authorization = new AuthenticationHeaderValue(TokenScheme, AppUtility.AccessToken ?? await TokenUtility.GetAccessTokenAsync()); var response = await base.SendAsync(cloneRequest, cancellationToken); if (response.StatusCode == HttpStatusCode.Unauthorized) { var user = await RefreshTokenRequiredAsync(_dataService); if (user == null) return response; // 再次克隆请求,避免流已释放的问题 var retryRequest = CloneHttpRequestMessage(originalRequest); retryRequest.Headers.Authorization = new AuthenticationHeaderValue(TokenScheme, user.AccessToken); response = await base.SendAsync(retryRequest, cancellationToken); } return response; } // 实现HttpRequestMessage克隆方法 private static HttpRequestMessage CloneHttpRequestMessage(HttpRequestMessage request) { var clone = new HttpRequestMessage(request.Method, request.RequestUri) { Content = request.Content != null ? new StreamContent(await request.Content.ReadAsStreamAsync()) : null, Version = request.Version }; foreach (var header in request.Headers) clone.Headers.TryAddWithoutValidation(header.Key, header.Value); foreach (var header in request.Content?.Headers ?? Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>()) clone.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value); return clone; }
2. 调整Polly策略,加入ObjectDisposedException重试
将ObjectDisposedException纳入Polly的重试范围,因为这类异常属于偶发性流释放问题,重试可能解决:
private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(HttpRequestMessage httpContext) { return HttpPolicyExtensions .HandleTransientHttpError() .OrResult(msg => msg.StatusCode == HttpStatusCode.GatewayTimeout) .OrResult(msg => msg.StatusCode == HttpStatusCode.RequestTimeout) .OrResult(msg => msg.StatusCode == HttpStatusCode.ServiceUnavailable) .Or<WebException>() .Or<ObjectDisposedException>() // 新增捕获该异常 .WaitAndRetryAsync( retryCount: 3, sleepDurationProvider: GetRetryDelay); }
3. 优化响应读取逻辑
在读取响应内容时,确保先将内容完整读入内存再反序列化,避免流被提前释放:
if (response != null && response.IsSuccessStatusCode) { // 先读取为字符串并保存,再反序列化 var contentStr = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<T?>(contentStr); }
4. 配置SocketsHttpHandler替代默认HttpClientHandler
在Android上,SocketsHttpHandler的稳定性更好,可显式配置:
builder.Services.AddHttpClient(AppConstants.HttpClient, client => { client.BaseAddress = new Uri(BaseUrl); client.DefaultRequestHeaders.Add("Accept", "application/json"); client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs"); }) .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler()) // 新增配置 .AddHttpMessageHandler<AuthenticationDelegatingHandler>() .AddPolicyHandler(GetRetryPolicy);
5. 检查Android平台配置
- 确认
AndroidManifest.xml中已添加INTERNET权限:<uses-permission android:name="android.permission.INTERNET" /> - 如果使用HTTP协议(非HTTPS),需在
AndroidManifest.xml的application标签中添加:android:usesCleartextTraffic="true"
6. 更新.NET MAUI版本
该问题可能是.NET MAUI框架的已知bug,尝试升级到最新稳定版,修复底层的流处理问题。
内容的提问来源于stack exchange,提问作者OverMars
相关产品推荐
相关产品推荐

