演练 - Xamarin.iOS 中的后台位置
在此示例中,我们将生成一个 iOS 位置应用程序,该应用程序将打印有关当前位置的信息:纬度、经度和其他参数到屏幕。 此应用程序将演示如何正确执行位置更新,无论应用程序处于活动状态还是后台。
本演练介绍一些关键背景概念,包括将应用注册为后台必需应用程序、在应用背景化时暂停 UI 更新以及处理WillEnterBackground
和WillEnterForeground
AppDelegate
方法。
应用程序设置
首先,创建新的 iOS > 应用 > 单视图应用程序 (C#)。 将其称为“位置”,并确保选中了 iPad 和 iPhone。
位置应用程序在 iOS 中限定为后台必需的应用程序。 通过编辑项目的 Info.plist 文件,将应用程序注册为 Location 应用程序。
在“解决方案资源管理器”下,双击 Info.plist 文件将其打开,然后滚动到列表底部。 勾选“启用后台模式”和“位置更新”选项旁的方框。
在 Visual Studio for Mac 中,它将如下所示:
在 Visual Studio 中,Info.plist 需要通过添加以下键/值对来手动更新:
<key>UIBackgroundModes</key> <array> <string>location</string> </array>
注册应用程序后,可以从设备获取位置数据。 在 iOS 中,
CLLocationManager
类用于访问位置信息,并可以引发提供位置更新的事件。在代码中,创建一个名为
LocationManager
的新类,为各种屏幕和代码提供一个位置来订阅位置更新。 在LocationManager
类中,将CLLocationManager
的实例设置为LocMgr
:public class LocationManager { protected CLLocationManager locMgr; public LocationManager () { this.locMgr = new CLLocationManager(); this.locMgr.PausesLocationUpdatesAutomatically = false; // iOS 8 has additional permissions requirements if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) { locMgr.RequestAlwaysAuthorization (); // works in background //locMgr.RequestWhenInUseAuthorization (); // only in foreground } if (UIDevice.CurrentDevice.CheckSystemVersion (9, 0)) { locMgr.AllowsBackgroundLocationUpdates = true; } } public CLLocationManager LocMgr { get { return this.locMgr; } } }
上述代码对 CLLocationManager 类设置许多属性和权限:
PausesLocationUpdatesAutomatically
– 这是一个布尔值,可以根据系统是否允许暂停位置更新而进行设置。 在某些设备上,它默认为true
,这可能导致设备在大约 15 分钟后停止获取后台位置更新。RequestAlwaysAuthorization
- 应传递此方法,以向用户提供允许在后台访问位置的选项。 如果希望为用户提供允许仅在应用位于前台时访问位置的选项,也可以传递RequestWhenInUseAuthorization
。AllowsBackgroundLocationUpdates
– 这是 iOS 9 中引入的布尔属性,该属性可以设置为允许应用在挂起时接收位置更新。
重要
iOS 8(及更高)还需要 Info.plist 文件中的条目,以将用户显示为授权请求的一部分。
为应用所需的权限类型
NSLocationAlwaysUsageDescription
、NSLocationWhenInUseUsageDescription
和/或NSLocationAlwaysAndWhenInUseUsageDescription
添加 Info.plist 键,该字符串将在请求位置数据访问的警报中向用户显示。iOS 9 要求在使用
AllowsBackgroundLocationUpdates
时,Info.plist 包含具有值location
的键UIBackgroundModes
。 如果已完成本演练的步骤 2,则应已在 Info.plist 文件中。在
LocationManager
类中,使用以下代码创建一个名为StartLocationUpdates
的方法。 此代码演示如何开始从CLLocationManager
接收位置更新:if (CLLocationManager.LocationServicesEnabled) { //set the desired accuracy, in meters LocMgr.DesiredAccuracy = 1; LocMgr.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) => { // fire our custom Location Updated event LocationUpdated (this, new LocationUpdatedEventArgs (e.Locations [e.Locations.Length - 1])); }; LocMgr.StartUpdatingLocation(); }
此方法中发生了几个重要的事情。 首先,我们执行检查以查看应用程序是否有权访问设备上的位置数据。 我们通过对
CLLocationManager
调用LocationServicesEnabled
来验证这一点。 如果用户拒绝应用程序访问位置信息,此方法将返回 false。接下来,告知位置管理器更新的频率。
CLLocationManager
提供了许多用于筛选和配置位置数据的选项,包括更新频率。 在此示例中,将DesiredAccuracy
设置为在按计量更改的位置时更新。 有关配置位置更新频率和其他首选项的详细信息,请参阅 Apple 文档中 CLLocationManager 类参考。最后,对
CLLocationManager
实例调用StartUpdatingLocation
。 这会告知位置管理器获取当前位置的初始修补程序,并开始发送更新
到目前为止,已创建位置管理器,并配置了要接收的数据类型,并确定初始位置。 现在,代码需要将位置数据呈现到用户界面。 可以使用将 CLLocation
作为参数的自定义事件执行此操作:
// event for the location changing
public event EventHandler<LocationUpdatedEventArgs>LocationUpdated = delegate { };
下一步是从 CLLocationManager
订阅位置更新,并在新位置数据可用时引发自定义 LocationUpdated
事件,以参数的形式传入位置。 为此,请创建新的类 LocationUpdateEventArgs.cs。 此代码可在主应用程序中访问,并在引发事件时返回设备位置:
public class LocationUpdatedEventArgs : EventArgs
{
CLLocation location;
public LocationUpdatedEventArgs(CLLocation location)
{
this.location = location;
}
public CLLocation Location
{
get { return location; }
}
}
用户界面
使用 Xcode Interface Builder 生成将显示位置信息的屏幕。 双击 Main.storyboard 文件开始。
在情节提要上,将多个标签拖到屏幕上,充当位置信息的占位符。 在此示例中,有纬度、经度、海拔、路线和速度的标签。
有关详细信息,请参阅用 Xcode 设计用户界面。
在 Solution Pad 中,双击
ViewController.cs
文件并对其进行编辑以创建 LocationManager 的新实例,并在其上调用StartLocationUpdates
。 更改代码,如下所示:#region Computed Properties public static bool UserInterfaceIdiomIsPhone { get { return UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone; } } public static LocationManager Manager { get; set;} #endregion #region Constructors public ViewController (IntPtr handle) : base (handle) { // As soon as the app is done launching, begin generating location updates in the location manager Manager = new LocationManager(); Manager.StartLocationUpdates(); } #endregion
这将在应用程序启动时启动位置更新,尽管不会显示任何数据。
收到位置更新后,请使用位置信息更新屏幕。 以下方法从
LocationUpdated
事件中获取位置,并在 UI 中显示该位置:#region Public Methods public void HandleLocationChanged (object sender, LocationUpdatedEventArgs e) { // Handle foreground updates CLLocation location = e.Location; LblAltitude.Text = location.Altitude + " meters"; LblLongitude.Text = location.Coordinate.Longitude.ToString (); LblLatitude.Text = location.Coordinate.Latitude.ToString (); LblCourse.Text = location.Course.ToString (); LblSpeed.Text = location.Speed.ToString (); Console.WriteLine ("foreground updated"); } #endregion
我们仍然需要订阅 AppDelegate 中的 LocationUpdated
事件,并调用新方法来更新 UI。 在 StartLocationUpdates
调用后立即在 ViewDidLoad,
中添加以下代码:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// It is better to handle this with notifications, so that the UI updates
// resume when the application re-enters the foreground!
Manager.LocationUpdated += HandleLocationChanged;
}
现在,当应用程序运行时,它应如下所示:
处理活动状态和后台状态
应用程序在前台处于活动状态时正在输出位置更新。 若要演示应用进入后台时会发生什么情况,请重写跟踪应用程序状态更改的
AppDelegate
方法,以便在应用程序在前台和后台之间切换时写入控制台:public override void DidEnterBackground (UIApplication application) { Console.WriteLine ("App entering background state."); } public override void WillEnterForeground (UIApplication application) { Console.WriteLine ("App will enter foreground"); }
在
LocationManager
中添加以下代码,将更新的位置数据持续输出到应用程序输出,以验证位置信息在后台是否仍然可用:public class LocationManager { public LocationManager () { ... LocationUpdated += PrintLocation; } ... //This will keep going in the background and the foreground public void PrintLocation (object sender, LocationUpdatedEventArgs e) { CLLocation location = e.Location; Console.WriteLine ("Altitude: " + location.Altitude + " meters"); Console.WriteLine ("Longitude: " + location.Coordinate.Longitude); Console.WriteLine ("Latitude: " + location.Coordinate.Latitude); Console.WriteLine ("Course: " + location.Course); Console.WriteLine ("Speed: " + location.Speed); } }
代码存在一个剩余的问题:尝试在应用后台时更新 UI 将导致 iOS 终止它。 当应用进入后台时,代码需要取消订阅位置更新并停止更新 UI。
当应用即将过渡到其他应用程序状态时,iOS 会向我们提供通知。 在这种情况下,我们可以订阅
ObserveDidEnterBackground
通知。以下代码片段演示如何使用通知让视图知道何时停止 UI 更新。 这将进入
ViewDidLoad
:UIApplication.Notifications.ObserveDidEnterBackground ((sender, args) => { Manager.LocationUpdated -= HandleLocationChanged; });
应用运行时,输出将如下所示:
应用程序在前台运行时将位置更新到屏幕,并在后台运行时继续将数据打印到应用程序输出窗口。
只有一个未完成的问题仍然存在:屏幕在首次加载应用时启动 UI 更新,但它无法知道应用何时重新进入前台。 如果后台应用程序被带回前台,UI 更新将不会恢复。
若要解决此问题,请在另一个通知内嵌套启动 UI 更新的调用,当应用程序进入活动状态时将触发该调用:
UIApplication.Notifications.ObserveDidBecomeActive ((sender, args) => {
Manager.LocationUpdated += HandleLocationChanged;
});
现在,UI 将在首次启动应用程序时开始更新,并在应用返回到前台时继续更新。
在本演练中,我们构建了一个行为良好的后台感知 iOS 应用程序,用于将位置数据输出到屏幕和应用程序输出窗口。