Отслеживание iOS CLCircularRegion - Гейзенбаг

Кажется, у одного из моих iOS-приложений есть признаки классического гейзенбагга. Приложение отслеживает домашнее местоположение пользователя, поэтому определенные события происходят, когда пользователь входит и выходит из своего домашнего местоположения.

Пока я тестирую приложение, оно отлично работает. Я вхожу и выхожу из CLCircularRegion и это работает во всех отношениях, я пытаюсь это сделать. Работает с приложением в фоновом режиме. Работает с закрытым приложением. Работает с приложением на переднем плане. Работает с зелеными яйцами и ветчиной.

К сожалению, пользователи сообщают о проблемах, когда это будет отложено примерно на 15 минут. Пользователи войдут в свои дома, но событие произойдет не позднее, чем позже. В некоторых случаях событие не происходит вообще. Кажется, что шаблон заключается в том, что когда пользователь впервые начинает использовать приложение, оно прекрасно работает. Примерно через день приложение, похоже, не работает. События задерживаются.

Я буду первым, кто признает, что я не специалист по внутренней работе CLLocationManager а также CLCircularRegion, Я верю, что у меня все настроено правильно, и мне очень трудно понять, как я могу отладить что-то подобное.

Во всяком случае, я покажу некоторые из моего кода здесь. Имейте в виду, что это разработано с Xamarin, так что это в C#.

AppDelegate.cs

public static AppDelegate self;
private CLLocationManager locationManager;
private CLCircularRegion[] locationFences;



private void initializeLocationManager()
{
    this.locationManager = new CLLocationManager();

    // iOS 8 additional permissions requirements
    if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
    {
        locationManager.RequestAlwaysAuthorization();
    }

    locationManager.AuthorizationChanged += (sender, e) =>
    {
        var status = e.Status;

        // Location services was turned off or turned off for this specific application.
        if (status == CLAuthorizationStatus.Denied)
        {
            stopLocationUpdates();
        }
        else if (status == CLAuthorizationStatus.AuthorizedAlways &&
            iOSMethods.getKeyChainBool(OptionsViewController.GENERIC, OptionsViewController.SERVICE_GEOLOCATION_ENABLED))
        {
            startLocationUpdates();
        }
    };

    if (CLLocationManager.IsMonitoringAvailable(typeof(CLCircularRegion)))
    {
        locationManager.RegionEntered += (sender, e) =>
        {
            setRegionStatus(e, "Inside");
        };

        locationManager.RegionLeft += (sender, e) =>
        {
            setRegionStatus(e, "Outside");
        };

        locationManager.DidDetermineState += (sender, e) =>
        {
            setRegionStatus(e);
        };
    }
    else
    {
        // cant do it with this device
    }

    init();
}

public void init()
{
    var data = SQL.query<SQLTables.RoomLocationData>("SELECT * FROM RoomLocationData").ToArray();
    int dLen = data.Length;
    if (dLen > 0)
    {
        locationFences = new CLCircularRegion[dLen];
        for (int x = 0; x < dLen; x++)
        {
            var d = data[x];
            CLCircularRegion locationFence = new CLCircularRegion(new CLLocationCoordinate2D(d.Latitude, d.Longitude), d.Radius, d.SomeID.ToString() + ":" + d.AnotherID.ToString());
            locationFence.NotifyOnEntry = true;
            locationFence.NotifyOnExit = true;
            locationFences[x] = locationFence;
        }
    }
}

private void setRegionStatus(CLRegionEventArgs e, string status, bool calledFromDidDetermineState = false)
{
    string identifier = e.Region.Identifier;

    string lastStatus = iOSMethods.getKeyChainItem(OptionsViewController.GENERIC, OptionsViewController.SERVICE_LAST_GEO_STATUS);
    if (lastStatus == status + ":" + identifier)
    {
        return;
    }
    iOSMethods.setKeychainItem(OptionsViewController.GENERIC, OptionsViewController.SERVICE_LAST_GEO_STATUS, status + ":" + identifier);

    string[] split = identifier.Split(new string[] { ":" }, StringSplitOptions.RemoveEmptyEntries);
    if (split.Length == 2)
    {
        try
        {
            int someID = Convert.ToInt32(split[0]);
            int anotherID = Convert.ToInt32(split[1]);

            // Notifies our API of a change.
            updateGeofenceStatus(someID, anotherID, status);

            if (iOSMethods.getKeyChainBool(OptionsViewController.GENERIC, OptionsViewController.SERVICE_GEOLOCATION_NOTIFICATIONS) &&
                (status == "Inside" || status == "Outside" || status == "Unknown"))
            {
                var rm = SQL.query<SQLTables.KeyRoomPropertyData>("SELECT * FROM KeyRoomPropertyData WHERE SomeID ID = ? AND AnotherID = ?",
                    new object[] { someID, anotherID }).ToArray();
                if (rm.Length > 0)
                {
                    if (status == "Unknown")
                    {
                        return;
                    }
                    var rmD = rm[0];
                    UILocalNotification notification = new UILocalNotification();
                    notification.AlertAction = "Geolocation Event";
                    notification.AlertBody = status == "Inside" ? "Entered " + rmD.SomeName + ": " + rmD.AnotherName :
                        status == "Outside" ? "Exited " + rmD.SomeName + ": " + rmD.AnotherName :
                        "Geolocation update failed. If you would like to continue to use Geolocation, please make sure location services are enabled and are allowed for this application.";
                    notification.SoundName = UILocalNotification.DefaultSoundName;
                    notification.FireDate = NSDate.Now;
                    UIApplication.SharedApplication.ScheduleLocalNotification(notification);
                }
            }
        }
        catch (Exception er)
        {
            // conversion failed. we don't have ints for some reason.
        }
    }
}

private void setRegionStatus(CLRegionStateDeterminedEventArgs e)
{
    string state = "";
    if (e.State == CLRegionState.Inside)
    {
        state = "Inside";
    }
    else if (e.State == CLRegionState.Outside)
    {
        state = "Outside";
    }
    else
    {
        state = "Unknown";
    }
    CLRegionEventArgs ee = new CLRegionEventArgs(e.Region);
    setRegionStatus(ee, state, true);
}

public void startLocationUpdates()
{
    if (CLLocationManager.LocationServicesEnabled)
    {
        init();
        if (locationFences != null)
        {
            foreach (CLCircularRegion location in locationFences)
            {
                locationManager.StartMonitoring(location);
                Timer t = new Timer(new TimerCallback(delegate(object o) { locationManager.RequestState(location); }), null, TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(-1));
            }
        }
    }
}

public void stopLocationUpdates(bool isRestarting = false)
{
    if (locationFences != null)
    {
        foreach (CLCircularRegion location in locationFences)
        {
            locationManager.StopMonitoring(location);
        }
    }
    if (!isRestarting)
    {
        var rooms = SQL.query<SQLTables.KeyRoomPropertyData>("SELECT * FROM KeyRoomPropertyData").ToArray();
        foreach (SQLTables.KeyRoomPropertyData room in rooms)
        {
            // notifies our API of a change
            updateGeofenceStatus(room.SomeID, room.AnotherID, "Unknown");
        }
    }
}

Я знаю, что каждый может просмотреть много кода, но у меня действительно нет хорошей теории на данный момент относительно того, что является причиной этой ошибки или возможно ли ее исправить с помощью ограничений iOS.

Несколько теорий, которые у меня есть, если CLLocationManager,PausesLocationUpdatesAutomatically свойство может иметь какое-то отношение к нему, или какое-либо другое свойство CLLocationManager такие как ActivityType, DesiredAccuracy, или же DistanceFilter, Я оставил все это по умолчанию, которое, как я полагаю, будет в порядке, но я не совсем уверен.

Другая теория заключается в том, что существует неисследованное исключение, которое выдается через некоторое время после того, как "служба" работает в фоновом режиме в течение некоторого времени. Если это так, то есть ли что-нибудь, что может сделать iOS, что бы мне подсчитало стек? Во всех моих тестах я никогда не сталкивался с какими-либо исключениями из этого кода, поэтому я сомневаюсь, что это проблема. На данный момент, однако, я готов принять любые идеи или предложения.

Кроме того, имейте в виду, что для того, чтобы это приложение работало так, как оно было задумано, события обновления местоположения ДОЛЖНЫ происходить, как только пользователь входит или существует CLCircularRegion (по крайней мере, в течение минуты) Очевидно, что я должен оставить это пользователю, чтобы оставить его службы определения местоположения включенными и позволить приложению иметь соответствующие разрешения.

2 ответа

Некоторые вещи, чтобы проверить:

  1. Каковы некоторые типичные значения для радиуса? Вы можете рассмотреть возможность уменьшения этого.

  2. Службы определения местоположения iOS обеспечат более быстрый ответ, если на устройстве включен WiFi, даже если пользователь не подключен к сети. Убедитесь, что у проблемных пользователей отключен Wi-Fi, и если вы этого еще не сделали, возможно, протестируйте свое устройство без Wi-Fi.

  3. Есть ли задержка в уведомлении? То есть правильно ли происходит событие региона, но по какой-то причине задержка в уведомлении?

  4. Сколько существует записей RoomLocationData? iOS ограничивает каждое приложение максимум 20 регионами.

  5. Предполагая, что пользователи едут в / из своего дома, вы можете попробовать следующие настройки (код Swift):

    locationManager.distanceFilter = kCLDistanceFilterNone
    locationManager.desiredAccuracy = kCLLocationAccuracyBest // or kCLLocationAccuracyBestForNavigation
    locationManager.pausesLocationUpdatesAutomatically = true // try false if nothing else works
    locationManager.allowsBackgroundLocationUpdates = true
    locationManager.activityType = CLActivityType.AutomotiveNavigation
    

Скорее всего, вы поставили диагноз точно в цель - это классический эффект наблюдателя.

При тестировании приложения, когда пользователи играют с новым приложением, iphone активно используется. Ему не дают возможности уснуть. Что происходит на следующий день, когда пользователи возвращаются домой - их телефоны, скорее всего, не будут использоваться в течение продолжительного времени непосредственно перед тем, как добраться до дома: обычно мы не используем телефоны во время прогулки "последней мили" после выхода из общественного транспорта или во время поездки назад Главная. iOS замечает этот продленный период бездействия и корректирует свое поведение, чтобы оптимизировать время автономной работы.

Самый простой способ убедиться в этом - собрать простое приложение "хлебные крошки" - установить геозону на своем месте и делать это каждый раз, когда вы получаете событие выхода. В зависимости от того, как вы используете (или не используете), результаты вашего телефона могут сильно отличаться при ходьбе по тому же маршруту.

И когда вы возвращаетесь домой, телефон, как правило, является последним, что вы достигаете также.

Возможно, вы захотите попросить пользователей предоставить более подробную информацию о том, как именно они пользовались телефонами за последние 15 минут до и после входа в дом, какие другие приложения они используют, если они едут, продолжают ли они работать приложение навигации по очереди и т. Д. Вы обнаружите шаблон,

число рейнольдса Кроме того, имейте в виду, что для того, чтобы это приложение работало так, как оно было задумано, события обновления местоположения ДОЛЖНЫ происходить, как только пользователь входит в CLCircularRegion или существует его (по крайней мере, в течение минуты или около того).

Вы не можете сделать это только с геозоной, особенно принимая во внимание различные схемы прибытия / отправления - ходьба против вождения, "спуск" (например, прибытие с разворотами). Вы должны ожидать как задержки более 1 минуты, так и преждевременное срабатывание. Я боюсь, что нет обходного пути.

Другие вопросы по тегам