2

我的一个 iOS 应用程序似乎具有经典 Heisenbug 的症状。该应用程序跟踪用户的家庭位置,因此当用户进入和离开他们的家庭位置时会发生某些事件。

在我测试应用程序时,它运行良好。我进出 aCLCircularRegion并且无论我尝试哪种方式它都有效。它在后台与应用程序一起工作。它适用于关闭的应用程序。它适用于前台的应用程序。它适用于绿色鸡蛋和火腿。

不幸的是,用户报告的问题将延迟 15 分钟左右。用户将进入他们的家,但该事件要到稍后才会发生。在某些情况下,事件根本不会发生。模式似乎是,当用户第一次开始使用应用程序时,它工作得很好。大约一天后,该应用程序似乎无法正常工作。事件被延迟。

我将是第一个承认我不是CLLocationManagerand内部工作的专家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例如ActivityTypeDesiredAccuracyDistanceFilter。我将所有这些都保留为默认值,我认为这会很好,但我不太确定。

另一种理论是,在“服务”在后台运行一段时间后,会抛出未捕获的异常。如果是这样的话,iOS 有没有什么可以给我一个堆栈跟踪或什么的?在我所有的测试中,我从来没有遇到过从这段代码中抛出的任何异常,所以我有点怀疑这就是问题所在。不过,在这一点上,我愿意接受任何想法或建议。

另外,请记住,为了让此应用程序按预期方式工作,位置更新事件必须在用户进入或存在时立即发生CLCircularRegion(至少在一分钟左右)。显然,我必须将其留给用户来保持他们的位置服务启用并允许应用程序拥有适当的权限。

4

2 回答 2

1

你的诊断很可能是正确的——这是典型的观察者效应。

当您测试应用程序时,当用户使用新应用程序时,iPhone 正在被积极使用。它没有机会入睡。第二天,当用户回家时会发生什么 - 他们的手机很可能在到达家之前很长时间没有使用:通常我们不会在离开公共交通工具后的“最后一英里”步行期间或开车返回时使用手机家。iOS 会注意到这种延长的不活动期并调整自己的行为以优化电池寿命。

观察这一点的最简单方法是组合一个简单的面包屑应用程序 - 在您的位置设置地理围栏,并在每次收到退出事件时继续这样做。根据您使用(或不使用)的方式,您的手机结果在走同一条路线时会有很大不同。

当你回到家时,电话通常也是你最不想接触的东西。

您可能想要求用户提供更多详细信息,例如他们在进入家之前和之后的 15 分钟内究竟如何使用手机、他们使用了哪些其他应用程序、如果他们开车,他们是否会保持转弯导航应用程序运行等。您会发现这种模式.

回覆。另外,请记住,为了使此应用程序按预期方式工作,位置更新事件必须在用户进入或存在 CLCircularRegion 时立即发生(至少在一分钟左右)。

您不能仅使用地理围栏来做到这一点,特别是考虑到不同的到达/离开模式 - 步行与驾驶,“下降”路径(例如,带有 U 形转弯的到达)。您必须预计超过 1 分钟的延迟和“过早”触发。恐怕没有解决方法。

于 2016-02-01T17:38:22.833 回答
1

需要检查的一些事项:

  1. 半径的一些典型值是什么?您可能需要考虑减少它。

  2. 如果设备启用了 WiFi,即使用户未连接到网络,iOS 定位服务也会提供更快的响应。检查问题用户是否禁用了 wifi,如果您还没有这样做,则可以测试您的设备 w/o wifi。

  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
    
于 2016-02-01T20:44:27.157 回答