我的一个iOS应用程序似乎有经典Heisenbug的症状。应用程序跟踪用户的家庭位置,以便在用户进入和离开其家庭位置时发生某些事件。
虽然我正在测试应用程序,但效果很好。我走进和走出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
。我已将所有这些都保留为默认设置,我认为这样会很好,但我并不确定。
另一个理论是,在&#34;服务&#34;之后有一段未被捕获的例外被抛出。已经在后台运行了一段时间。如果是这种情况,iOS会做什么会给我一个堆栈跟踪或什么?在我的所有测试中,我从未遇到过从此代码中抛出的任何异常,所以我有点怀疑这个问题。不过,在这一点上,我愿意接受任何想法或建议。
此外,请记住,为了使此应用程序按照预期的方式工作,一旦用户输入或存在CLCircularRegion
(大约一分钟左右),就必须立即发生位置更新事件最小)。显然,我必须将其留给用户以保持其位置服务的启用,并允许该应用拥有适当的权限。
答案 0 :(得分:1)
你最有可能在你的诊断目标上 - 这是典型的观察者效应。
当您测试应用时,当用户使用新应用时,正在积极使用iphone。它没有机会入睡。当用户返回家中时,第二天会发生什么 - 他们的手机很可能在到达原位之前没有长时间使用:通常我们在离开公共交通工具或“开车”后的“最后一英里”步行时不使用手机家。 iOS注意到此延长的不活动时间并调整其自身行为以优化电池寿命。
最简单的观察方法是将简单的面包屑应用程序放在一起 - 在您的位置设置地理围栏,并在每次退出事件时继续这样做。根据您使用(或不使用)的方式,当您走同一条路线时,手机的结果会有很大不同。
当你回到家时,手机通常也是你最后接触到的东西。
您可能想要让用户详细了解他们在进入家门前后15分钟使用手机的确切程度,他们使用的其他应用程序,如果他们开车,他们会轮流导航应用程序运行等等。发现模式。
重。此外,请记住,为了这个应用程序 按照预期的方式工作,位置更新事件必须以 用户输入或存在CLCircularRegion(在一分钟内) 至少)。
您不能仅使用地理围栏进行此操作,尤其是考虑到不同的到达/离开模式 - 步行与驾驶,“下降”路径(例如,具有U形转弯的到达)。您必须预测两个延迟超过1分钟并且“过早”触发。恐怕没有解决方法。
答案 1 :(得分:1)
要检查的一些事项:
半径有哪些典型值?您可能需要考虑减少它。
如果设备启用了WiFi,即使用户未连接到网络,iOS位置服务也会提供更快的响应。检查问题用户是否已禁用wifi,如果您还没有这样做,也可以测试您的无线wifi设备。
通知是否有延迟?也就是说,区域事件是否正确发生,但由于某种原因,通知有延迟?
有多少个RoomLocationData条目? iOS将每个应用限制为最多20个区域。
假设用户正在开车到/离开他们的房子,您可能想尝试以下设置(代码是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