【问题标题】:iOS - Cannot call API when app is running backgroundiOS - 应用程序在后台运行时无法调用 API
【发布时间】:2020-01-09 16:14:55
【问题描述】:

我正在开发一个允许用户输入调查的应用程序。应用程序可以在离线/在线时工作。

概念如下:

  1. 在线情况下:当用户进入调查并点击提交按钮时,应用程序将调用api(使用httpClient)将调查信息发送到服务器。
  2. 离线情况下:当用户进入调查并点击提交按钮时,应用程序会将调查信息保存在本地。移动设备连接网络后,会调用api将调查信息从本地发送到服务器。

工作流程:

  • 对于 android,它适用于以下情况:

    在线时: - 创建调查,调查将发送到服务器。

    离线时:

    1. 创建调查 => 调查保存在本地; 1.1。保持应用程序仍在打开,然后连接网络 => 调查已发送到服务器; 1.2.触摸主页按钮,让应用程序在后台运行 => 连接网络 => 调查已发送到服务器; 1.3.用户强制退出应用 => 连接网络(在这种情况下,调查不会发送到服务器)=> 打开应用(调查将同步到服务器)
  • 对于 iOS,它适用于以下情况:

    在线时:创建调查,调查将同步到服务器(需要等待大约5秒才能关闭应用程序。因为如果立即关闭,调用api时会出错)。

    离线时:

    1. 创建调查 => 调查保存在本地。 1.1。保持应用程序仍在打开,然后连接网络 => 调查已发送到服务器; 1.2.触摸主页按钮,让应用程序在后台运行 => 连接网络(在这种情况下,调查不会发送到服务器)=> 打开应用程序(调查将被发送到服务器); 1.3.用户强制退出应用 => 连接网络(在这种情况下,调查不会发送到服务器)=> 打开应用(调查将发送到服务器)

所以我发现在 Android 上一切正常。 但是,如果应用程序在 iOS 上运行后台,则会出现问题。只有当用户打开应用程序时,调查才会发送到服务器。 对于 iOS,我使用的是静默通知。当应用程序在后台运行时,将每 5 分钟向应用程序推送通知以触发调用 API(将调查发送到服务器)。目前,当应用收到通知时,应用可以调用API,但调用api时出错。

  • 我使用我的 iPhone 并在 Facebook Messenger 上进行了测试,以防我在离线时发送消息,然后触摸主页按钮运行后台(不是强制退出应用程序)。连接网络后,无需打开应用即可发送消息。

这是错误:

NSLocalizedDescription=网络连接丢失。, NSErrorFailingURLStringKey=[URL],NSErrorFailingURLKey=[URL], _kCFStreamErrorDomainKey=4} --- 内部异常堆栈跟踪结束 --- 在 System.Net.Http.NSUrlSessionHandler.SendAsync(System.Net.Http.HttpRequestMessage 请求, System.Threading.CancellationToken cancelToken) 在 :0 在 System.Net.Http.HttpClient.SendAsyncWorker (System.Net.Http.HttpRequestMessage 请求, System.Net.Http.HttpCompletionOption 完成选项, System.Threading.CancellationToken cancelToken) 在 :0 在 SurveyApp.Service.RestClient.PostAsync[T,TP] (System.String url, TP t,System.Nullable 1[T] 令牌) 在 :0 在 SurveyApp.Service.SyncService.Push (System.Collections.Generic.IList 1[T] 同步) 在:0 在 SurveyApp.Service.SyncService.PushSurveys () 在 :0 ","System.Net.Http.HttpRequestException 网络连接是 丢失。在 System.Net.Http.NSUrlSessionHandler.SendAsync (System.Net.Http.HttpRequestMessage 请求, System.Threading.CancellationToken cancelToken) 在 :0 在 System.Net.Http.HttpClient.SendAsyncWorker (System.Net.Http.HttpRequestMessage 请求, System.Net.Http.HttpCompletionOption 完成选项, System.Threading.CancellationToken cancelToken) 在 :0 在 SurveyApp.Service.RestClient.PostAsync[T,TP] (System.String url, TP t,System.Nullable 1[T] 令牌) 在 :0 在 SurveyApp.Service.SyncService.Push (System.Collections.Generic.IList 1[T] 同步) 在:0 在 SurveyApp.Service.SyncService.PushSurveys () 在 :0 Foundation.NSErrorException 错误域=NSURLErrorDomain 代码=-1005 ""网络连接丢失。"" 使用

这是我正在处理的调用 API 的代码(使用 httpClient):

public async Task<T> PostAsync<T, TP>(string url, TP t, CancellationToken? token = null)
{
    try
    {
        ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
        ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

        var content = JsonConvert.SerializeObject(t);
        HttpContent httpContent = new StringContent(content);
        httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

        using (var httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(5), DefaultRequestHeaders = { ConnectionClose = true } })
        {
            HttpResponseMessage responseMessage;

            if (token != null)
            {
                responseMessage = await httpClient.PostAsync(url, httpContent, token.Value);
            }
            else
            {
                var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(5));
                responseMessage = await httpClient.PostAsync(url, httpContent, cancellationTokenSource.Token);
            }

            if (responseMessage.IsSuccessStatusCode == false)
            {
                throw new Exception("Request has problem");
            }

            var responseContent = await responseMessage.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<T>(responseContent, DefaultSerializerSettings);
        }
    }
    catch (TaskCanceledException)
    {
        await Task.Delay(5000);
        return await PostAsync<T, TP>(url, t, token);
    }
    catch (Exception e)
    {
        _logger.Error(e);
        throw;
    }
}

public async Task<T> PostSurveyPhotoWithDataAsync<T>(string url, string file, SurveyPhoto surveyPhoto)
{
    try
    {
        ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
        ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

        using (var formData = new MultipartFormDataContent())
        {
            using (var httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(10), DefaultRequestHeaders = { ConnectionClose = true } })
            {
                using (var streamReader = new StreamReader(file))
                {
                    formData.Add(new StreamContent(streamReader.BaseStream), "File", file);
                    formData.Add(new StringContent(surveyPhoto.KeyId), "KeyId");
                    formData.Add(new StringContent(surveyPhoto.FileName), "FileName");
                    formData.Add(new StringContent(surveyPhoto.SurveySyncId), "SurveyId");

                    var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(5));
                    var responseMessage = await httpClient.PostAsync(url, formData, cancellationTokenSource.Token);

                    streamReader.Close();

                    if (responseMessage.IsSuccessStatusCode == false)
                    {
                        throw new Exception("Request has problem");
                    }

                    var responseContent = await responseMessage.Content.ReadAsStringAsync();
                    return JsonConvert.DeserializeObject<T>(responseContent, DefaultSerializerSettings);
                }
            }
        }
    }
    catch (TaskCanceledException)
    {
        await Task.Delay(5000);
        return await PostSurveyPhotoWithDataAsync<T>(url, file, surveyPhoto);
    }
    catch (Exception e)
    {
        _logger.Error(e);
        throw;
    }
}

是否有任何解决方案、文档或示例代码可以帮助我解决此问题?

【问题讨论】:

  • 您能否在调用 SyncData() 之前尝试添加一点延迟;如this thread中所述?那里的人正试图做和你一样的事情。还要确保您已启用后台模式(后台获取和远程通知)。
  • 感谢您的回答,杰克。 - 我试过但似乎不起作用。 - 还有一个问题,在我禁用网络并再次启用网络后,应用程序无法接收静默通知。只有当我再次打开应用程序(前台)时它才会收到,然后我进入后台后它也可以收到。你知道为什么吗?
  • 您的应用程序在进入后台后似乎挂起,并且在再次启用网络后没有重新连接。
  • 感谢您的支持,非常感谢,杰克。
  • 没问题,你找到解决办法了吗?

标签: ios xamarin


【解决方案1】:

通读你所写的,我想说你应该看看这些可能的解决方案:

在这种情况下,您提供的代码不是问题。 iOS 及其处理后台的方式是您的问题所在。

【讨论】:

  • 感谢您的回答。我尝试使用后台获取,但我不知道执行此方法的确切时间(作为文档,操作系统将决定)因此我们将忽略此方法。我已经查看了 long-running-task 和 NSUrlSession 但我仍然不知道我应该怎么做才能解决问题。它没有提到应用程序接收静默通知是从服务器推送的(我希望每 5 分钟在设备和服务器之间同步一次数据)。那么你能给我一个例子或具体的文件来解决吗?这是我在应用收到静默通知时处理的代码(我将在第二条评论中发布)
  • CrossFirebasePushNotification.Current.OnNotificationReceived += (s, p) => { SyncData(); }; private void SyncData() { if (Connectivity.NetworkAccess == NetworkAccess.Internet) { var service = FreshIOC.Container.Resolve(); Task.Factory.StartNew(() => { service.SyncSurveys(); }); } }
  • 也许我现在正在实施的解决方案不正确。希望您能给我一个正确的解决方案或一些想法,以便我可以遵循它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-27
  • 2015-04-25
  • 1970-01-01
  • 2019-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多