You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Xamarin Android下载重试第二次后停滞问题求助

解决Xamarin Android下载重试失效的问题

我之前在Xamarin Android开发中也碰到过类似的重试中断问题,咱们来一步步排查和解决:

首先,你的Polly异常捕获范围不全!

这是最可能导致第二次重试失败的核心原因。你当前只捕获了NetworkOnMainThreadExceptionJava.Net.UnknownHostExceptionSSLException,但实际网络中断时,下载流程可能抛出其他未被覆盖的异常——比如流读取失败的IOException、请求超时的TaskCanceledException、HttpClient通用请求错误的HttpRequestException。这些异常没被Polly捕获的话,一旦抛出就会直接终止重试流程。

修改你的Polly配置,把这些异常加上,同时添加日志排查每次的异常类型:

await Policy
    .Handle<NetworkOnMainThreadException>()
    .Or<Java.Net.UnknownHostException>()
    .Or<SSLException>()
    .Or<IOException>()
    .Or<TaskCanceledException>()
    .Or<HttpRequestException>()
    .WaitAndRetryAsync(
        new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) },
        (exception, timeSpan, retryCount, context) =>
        {
            // 打印日志,确认每次重试的异常类型,方便排查
            Console.WriteLine($"第{retryCount}次重试,等待{timeSpan.TotalSeconds}秒,异常原因:{exception.Message}");
        })
    .ExecuteAsync(async () => { totalRead = await DownloadFile(url, progress, totalRead, token); });

其次,检查Android 9.0的网络限制

Android 9.0(API 28)默认禁用了明文HTTP请求,如果你的下载URL是http://而不是https://,第一次请求可能因为这个直接失败,重试也会无效。解决方法:
AndroidManifest.xml<application>标签里添加:

android:usesCleartextTraffic="true"

如果是HTTPS请求,可能存在SSL会话缓存问题,可以初始化HttpClient时禁用缓存:

var handler = new Xamarin.Android.Net.AndroidMessageHandler();
handler.DisableCaching = true;
handler.SSLProtocols = System.Security.Authentication.SslProtocols.Tls12;
// 生产环境请不要跳过证书验证,这里仅为测试场景提供
handler.ServerCertificateCustomValidationCallback = (msg, cert, chain, errors) => true;
_client = new HttpClient(handler);

另外,重试时的断点续传逻辑需要完善

你当前的代码是基于totalRead累加来追加文件,但如果服务器不支持断点续传,重试时会从头下载,导致文件内容重复。建议在DownloadFile里先检查服务器是否支持Range请求:

private async Task<long> DownloadFile(string url, IProgress<double> progress, long totalRead, CancellationToken token)
{
    // 先检查服务器是否支持断点续传
    var headRequest = new HttpRequestMessage(HttpMethod.Head, url);
    var headResponse = await _client.SendAsync(headRequest, token);
    bool supportsRange = headResponse.Headers.AcceptRanges.Contains("bytes");

    // 如果不支持断点续传,重置已下载进度并删除现有文件
    if (!supportsRange && totalRead > 0)
    {
        totalRead = 0;
        var fileName = url.Split('/').Last();
        var filePath = Path.Combine(_fileService.GetStorageFolderPath(), fileName);
        if (File.Exists(filePath)) File.Delete(filePath);
    }

    // 构建下载请求,支持断点续传
    var downloadRequest = new HttpRequestMessage(HttpMethod.Get, url);
    if (totalRead > 0 && supportsRange)
    {
        downloadRequest.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(totalRead, null);
    }

    var response = await _client.SendAsync(downloadRequest, HttpCompletionOption.ResponseHeadersRead, token);
    if (!response.IsSuccessStatusCode)
    {
        if (response.StatusCode == System.Net.HttpStatusCode.RequestedRangeNotSatisfiable)
        {
            // 已下载完成,直接返回当前进度
            return totalRead;
        }
        throw new Exception($"请求失败,HTTP状态码:{response.StatusCode}");
    }

    var fileName = url.Split('/').Last();
    var buffer = new byte[bufferSize];
    var totalData = response.Content.Headers.ContentLength.GetValueOrDefault(-1L);
    // 断点续传时,总数据量是已下载部分加上本次响应的长度
    if (supportsRange && totalRead > 0) totalData += totalRead;

    var filePath = Path.Combine(_fileService.GetStorageFolderPath(), fileName);
    using (var fileStream = OpenStream(filePath, totalRead))
    using (var inputStream = await response.Content.ReadAsStreamAsync())
    {
        int bytesRead;
        while ((bytesRead = await inputStream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
        {
            totalRead += bytesRead;
            await fileStream.WriteAsync(buffer, 0, bytesRead, token);
            progress.Report(totalData > 0 ? (totalRead * 100d) / totalData : 0);
        }
        progress.Report(100); // 下载完成后报告100%,而非0
    }
    return totalRead;
}

最后,排查CancellationToken是否被意外取消

检查你的token是否在重试过程中被触发了取消操作(比如用户点击了取消按钮),如果token已取消,Polly会直接终止重试流程。

按上面的步骤调整后,重试流程应该就能正常执行了。

内容的提问来源于stack exchange,提问作者Henkie85

火山引擎 最新活动