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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 16:42:01