Попытка получить доступ к API Google Адресов с помощью HttpClient в Xamarin.Forms на iOS зависает навсегда

У меня есть ключ API в корпоративной учетной записи со всеми включенными соответствующими API, включая API Карт Google для iOS, Google Адреса и геокодирование / обратное геокодирование.

Ключ API правильный, я дважды и трижды проверял.

Это решение отлично работает на Android. Но в iOS этот код полностью блокируется ... и даже установка тайм-аута не работает, вызов функции GetAsync НИКОГДА не возвращается.

Это проект Xamarin.Forms, и этот код выполняется в основном PCL.

Что здесь происходит?

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading.Tasks;
    using Plugin.Geolocator;
    using System.Net.Http;
    using MyApp.Models;
    using Newtonsoft.Json;
    using MyApp.Localization.Resources;
    using System.Threading;
    using DataTools.Text;
    using Plugin.Geolocator.Abstractions;

    namespace MyApp.Helpers
    {
        public class PlacesHelperConfig
        {
            public Position Position { get; set; }

            public int Pages { get; set; } = 1;

            public bool SuppressErrors { get; set; } = false;

            public string Keyword { get; set; }

            public string PlaceType { get; set; } = "restaurant";

            public double Radius { get; set; } = 1000;

            public bool RankByDistance { get; set; } = false;


        }

        public class PlacesHelper
        {
            private CancellationTokenSource cts = new CancellationTokenSource();




            public void Cancel()
            {
                cts.Cancel();
            }

            public async Task<List<MapPlace>> GetNearbyPlacesAsync(PlacesHelperConfig config)
            {
                return await GetNearbyPlacesAsync(config.Keyword, config.PlaceType, config.Position, config.Radius, config.RankByDistance, config.Pages, config.SuppressErrors);
            }
            public async Task<List<MapPlace>> GetNearbyPlacesAsync(string keyword, string placetype, Position position, double radius, bool byDistance, int pages, bool suppressErrors)
            {
                var places = new List<MapPlace>();
                var url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json";

                string useUrl = null;
                string sJson = null;

                try
                {

                    if (!Plugin.Connectivity.CrossConnectivity.Current.IsConnected)
                    {
                        if (!suppressErrors) Acr.UserDialogs.UserDialogs.Instance.Alert(AppResources.ErrorNoInternet, "", AppResources.OK);
                        return null;
                    }

                    //var locator = CrossGeolocator.Current;
                    HttpResponseMessage resp;

                    if (position == null)
                    {
                        throw new ArgumentNullException("Must provide a position");
                    }

                    var cli = new HttpClient();

                    string reqStr = "";

                    if (!string.IsNullOrEmpty(keyword))
                    {
                        reqStr += "keyword=" + TextTools.UrlEncode(keyword);
                    }

                    if (!string.IsNullOrEmpty(placetype))
                    {
                        if (reqStr != "") reqStr += "&";
                        reqStr += "type=" + TextTools.UrlEncode(placetype);
                    }

                    if (reqStr == "") throw new ArgumentNullException("Must have at least a type or a keyword");

                    if (byDistance) {
                        reqStr += "&fields=photos,formatted_address,name,opening_hours,rating&location={0},{1}&key={2}&rankby=distance";
                        reqStr = string.Format(reqStr, position.Latitude, position.Longitude, ((App)App.Current).MapsApiKey);

                    }
                    else
                    {
                        reqStr += "&fields=photos,formatted_address,name,opening_hours,rating&radius={0}&location={1},{2}&key={3}";
                        reqStr = string.Format(reqStr, radius, position.Latitude, position.Longitude, ((App)App.Current).MapsApiKey);
                    }


                    MapPlaceQueryReturn returned;

                    url += "?" + reqStr;
                    useUrl = url;

                    var uri = new Uri(useUrl);
                    cli.Timeout = new TimeSpan(0, 0, 10);

                    for (int i = 0; i < pages; i++)
                    {
                        try
                        {
                            resp = await cli.GetAsync(uri, cts.Token);
                        }
                        catch
                        {
                            if (!suppressErrors) Acr.UserDialogs.UserDialogs.Instance.Alert(AppResources.ErrorNoServer, "", AppResources.OK);

                            if (places.Count > 0) return places;
                            else return null;
                        }

                        sJson = await resp.Content.ReadAsStringAsync();
                        returned = JsonConvert.DeserializeObject<MapPlaceQueryReturn>(sJson);

                        places.AddRange(returned.Results);

                        if (returned.NextPageToken == null)
                            break;

                        else if (pages > 1)
                            useUrl = url + "&pagetoken=" + returned.NextPageToken;

                    }

                    return places;
                }
                catch
                {
                    if (places.Count > 0) return places;
                    else return null;
                }
            }

        }

   public class MapPlaceQueryReturn
    {

        [JsonProperty("html_attributions")]
        public string[] HtmlAttributions { get; set; }


        [JsonProperty("next_page_token")]
        public string NextPageToken { get; set; }


        [JsonProperty("results")]
        public List<MapPlace> Results { get; set; }


    }

    public class MapPlace
    {

        [JsonProperty("place_id")]
        public string PlaceId { get; set; }

        [JsonProperty("geometry")]
        public PlaceGeometry Geometry { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("vicinity")]
        public string Address { get; set; }

        [JsonProperty("types")]
        public string[] Types { get; set; }

        [JsonProperty("price_level")]
        public double PriceLevel { get; set; }

        [JsonProperty("rating")]
        public double Rating { get; set; }

        public override string ToString() => $"{Name},{Address}";

        public override bool Equals(object obj) => (obj is MapPlace p && p.PlaceId == PlaceId);

        public override int GetHashCode() => (int)Crc32.Calculate(Encoding.UTF8.GetBytes(ToString()));

    }

    public class PlaceGeometry
    {
        [JsonProperty("location")]
        public PlaceLocation Location { get; set; }

        [JsonProperty("viewport")]
        public PlaceViewport Viewport { get; set; }

        public override string ToString()
        {
            return $"{Location.Latitude},{Location.Longitude}";
        }
    }

    public class PlaceLocation
    {

        [JsonProperty("lat")]
        public double Latitude { get; set; }

        [JsonProperty("lng")]
        public double Longitude { get; set; }

        public override string ToString()
        {
            return $"{Latitude},{Longitude}";
        }

        public PlaceLocation() { }

        public PlaceLocation(double latitude, double longitude)
        {
            Latitude = latitude;
            Longitude = longitude;
        }

        public static implicit operator PlaceLocation(Xamarin.Forms.Maps.Position value)
        {
            return new PlaceLocation(value.Latitude, value.Longitude);
        }

        public static implicit operator Xamarin.Forms.Maps.Position(PlaceLocation value)
        {
            return new Xamarin.Forms.Maps.Position(value.Latitude, value.Longitude);
        }

    }

    public class PlaceViewport
    {

        [JsonProperty("northeast")]
        public PlaceLocation Northeast { get; set; }

        [JsonProperty("southwest")]
        public PlaceLocation Southwest { get; set; }

    }

}

person Nathan M    schedule 10.09.2019    source источник
comment
Как и где вызываются члены PlacesHelper?   -  person Nkosi    schedule 11.09.2019
comment
См. Также Вы неправильно используете HttpClient   -  person Nkosi    schedule 11.09.2019
comment
Ну, хотя я не осознавал, что HttpClient был повторно входим, это не объясняет проблему зависания, которую я получаю при попытке подключиться к службе Google API из проекта iOS. Я делаю несколько других Http-вызовов на наши собственные серверы без каких-либо проблем.   -  person Nathan M    schedule 11.09.2019
comment
Две разные платформы могут обрабатывать потоки двумя разными способами под капотом, несмотря на общий синтаксис выше.   -  person Nkosi    schedule 11.09.2019
comment
Хорошо ... Я знаю о проблемах с потоками в iOS ... Я попытаюсь вызвать основной поток и посмотрю, поможет ли мне это хоть немного.   -  person Nathan M    schedule 11.09.2019
comment
Я починил это. Происходило то, что пока этот код находился в коде PCL, код, который его вызывал, находился в коде платформы. Я создал несколько интерфейсов и запустил несколько событий, которые заставили код PCL запустить этот код, и это сработало. (Кстати, то, что вы сказали мне попробовать, сделать универсальный HttpClient? Это сломало мое решение на обеих платформах.)   -  person Nathan M    schedule 12.09.2019
comment
Хорошо, что вы нашли решение. Что касается http-клиента, обычно рекомендуется иметь один экземпляр на время жизни приложения, чтобы избежать исчерпания сокетов. Я предполагаю, что он ведет себя по-другому для вас. У меня пока не было проблем с использованием этого шаблона.   -  person Nkosi    schedule 12.09.2019
comment
Как это сломалось. Если вы не возражаете, я спрошу.   -  person Nkosi    schedule 12.09.2019
comment
Он все время говорил мне, что мой токен отмены был удален (даже при попытке создать новый каждый раз) ... на обеих платформах. Я вернулся к созданию новых экземпляров HttpClient внутри метода, и проблема исчезла.   -  person Nathan M    schedule 12.09.2019


Ответы (1)


Я починил это. Происходило то, что пока этот код находился в коде PCL, код, который его вызывал, находился в коде платформы. Я создал несколько интерфейсов и запустил несколько событий, которые заставили код PCL запустить этот код, и это сработало.

Я не знаю, ПОЧЕМУ это сработало ... но сработало.

person Nathan M    schedule 13.09.2019