Xamarin Android 服务模式 GPS 示例






4.61/5 (7投票s)
Xamarin Android 服务模式 GPS 示例。
引言
在 Xamarin 中,如果您想使用许多 Android 原生服务/机制,则必须在 Activity
类中实现它们。至少 Xamarin 文档是这样希望您做的。
例如,如果您想使用 GPS 获取设备的当前位置。根据官方文档,您必须实现一个 Android Activity,类似于以下内容:
public class MainActivity : FormsApplicationActivity, ILocationListener
{
private LocationManager _locMgr;
private string _provider = LocationManager.GpsProvider;
private object _locationProvider;
public void OnLocationChanged(Location location)
{
}
public void OnProviderDisabled(string provider)
{
}
public void OnProviderEnabled(string provider)
{
}
public void OnStatusChanged(string provider, Availability status, Bundle extras)
{
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
_locMgr = GetSystemService(LocationService) as LocationManager;
var locationCriteria = new Criteria
{
Accuracy = Accuracy.Coarse,
PowerRequirement = Power.Medium
};
if (_locMgr != null)
{
_locationProvider = _locMgr.GetBestProvider(locationCriteria, true);
if (_locationProvider != null)
{
_locMgr.RequestLocationUpdates(_provider, 2000, 1, this);
}
else
{
throw new Exception();
}
}
}
protected override void OnPause()
{
base.OnPause();
_locMgr.RemoveUpdates(this);
}
protected override void OnResume()
{
base.OnResume();
_locMgr.RequestLocationUpdates(_provider, 2000, 1, this);
}
}
几句解释。ILocationListener
是一个 Android 接口,用于由操作系统通知应用程序设备 GPS 位置的变化。在最简单的示例中,它在 Activity
中实现。
以上有什么问题?当这种逻辑扩展 Activity
类时,它使得在您的应用程序之间共享 GPS 功能变得非常困难。此外,还有其他 Android 系统机制可能推荐以这种方式实现(例如 GSM 信号、网络连接、全局布局 等)。如果这样做,Activity
类将变得非常臃肿且难以维护。此外,在视图模型中使用这种服务实现也有问题。除非您在 MainActivity
类中编写更多功能(新方法、事件以及对 MainActivity
实例的静态访问等),但这并不是我们想要的。
另一个坏主意是像 Xamarin GPS 文档中那样使用原生 Android 视图。考虑到他们宣传自己的框架具有跨平台共享代码的可能性,然后要求您使用原生视图来实现如此基本的东西(如 GPS 位置):)。是的,可以更新 Xamarin 视图的新位置而不是原生视图,但我老实说看不到意义,除非您非常赶时间,并且应用程序只有一个视图。在更复杂的应用程序中,在另一个视图中使用 GPS 将迫使您将另一个特定于视图的代码放入 Activity
中。这是另一个坏主意。要指出的另一件事是,如果您使用的是 XAML,您还应该使用 MVVM 模式,XAML 就是为此而设计的,并且在那里大放异彩。在这种情况下,以上述方式进行 GPS 会使事情变得更加复杂,因为在获得 Xamarin 视图后,您将被迫获得视图模型,然后才能用更新的 GPS 位置更新视图模型。
但是,有**更好**的方法。
可重用的 Android 服务
目标是让所有与 GPS 相关的代码都位于类-接口组合中。理想情况下,两者都注册到 IoC 容器中,并从中检索(可能在某些视图上使用之前进行简单的初始化)。然后可以将其放在某个库中并在应用程序之间共享。
让我们从实现 ILocationListener
接口开始。它不必在 Activity
类中实现。它可以是任何基类型为 Java.Lang.Object
的对象。当然,由于这是一个 Android 平台类,LocationListener
需要放置在相应的平台项目中。
public class LocationListener : Java.Lang.Object, ILocationListener
{
public void OnLocationChanged(Location location)
{
}
public void OnProviderDisabled(string provider)
{
}
public void OnProviderEnabled(string provider)
{
}
public void OnStatusChanged(string provider, Availability status, Bundle extras)
{
}
}
我们真正关心的唯一方法是 OnLocationChanged
。我们需要将这个新位置推送到我们的新服务。
我们可以创建一个新的 PCL 接口 ILocationService
,它应该作为需要从 IoC 容器解析的接口,以便在特定平台上实现。
public interface ILocationService
{
event EventHandler<LocationEventArgs> LocationChanged;
void RequestLocation();
void StopRequests();
}
当然,我们需要事件来通知其他对象位置的变化。另外两个方法用于打开和关闭设备上的 GPS 机制。这是必需的,因为根据Google 文档,最好只在需要时启用 GPS,因此一旦获得足够好的位置,就应禁用 GPS。
好的,现在到实现。大部分代码都从之前的 Activity 实现中复制过来,并且作为一个 Activity
类,它需要放置在 Android 项目中。
public class LocationService : ILocationService
{
public LocationListener Listener;
private readonly LocationManager _locMgr;
private readonly Criteria _locationCriteria;
public LocationService(MainActivity activity)
{
_locMgr = activity.GetSystemService(Context.LocationService) as LocationManager;
Listener = new LocationListener();
_locationCriteria = new Criteria
{
Accuracy = Accuracy.Coarse,
PowerRequirement = Power.Medium
};
if (_locMgr == null)
{
throw new Exception("No LocationManager instance!");
}
}
public event EventHandler<LocationEventArgs> LocationChanged;
public void RequestLocation()
{
var provider = _locMgr.GetBestProvider(_locationCriteria, true);
if (provider == null)
{
throw new Exception("No GPS provider could be found for given criteria!");
}
_locMgr.RequestLocationUpdates(provider, 2000, 1, Listener);
}
public void StopRequests()
{
_locMgr.RemoveUpdates(Listener);
}
protected virtual void OnLocationChanged(LocationEventArgs e)
{
LocationChanged?.Invoke(this, e);
}
}
不过有一些变化。实际开始位置更新已移至 RequestLocation
方法。如果 GPS 机制有问题,还会抛出两个错误。不过都不是什么大事。
上述问题在于 LocationListener
中的位置更新与 LocationService
之间没有连接。最简单的方法是在 LocationService
中创建一个 PushLocation
方法,并从 LocationListener
中执行它。
public class LocationListener : Java.Lang.Object, ILocationListener
{
private readonly LocationService _locationService;
public LocationListener(LocationService locationService)
{
_locationService = locationService;
}
public void OnLocationChanged(Location location)
{
_locationService.PushLocation(location);
}
public void OnProviderDisabled(string provider)
{
}
public void OnProviderEnabled(string provider)
{
}
public void OnStatusChanged(string provider, Availability status, Bundle extras)
{
}
}
要使用新的构造函数,我们需要更改 LocationService
构造函数中 LocationListener
实例的创建方式。
Listener = new LocationListener(this);
在 LocationListener
中拥有服务实例后,我们可以使用新的 PushLocation
方法。
internal void PushLocation(Location location)
{
OnLocationChanged(new LocationEventArgs(location));
}
此方法将引发 LocationChanged
事件,并将新位置推送到每个处理程序。非常简单,但有效。
好的。现在我们应该在某个应用程序中对其进行测试。应用程序的主页可以只有一个简单的标签,用于显示位置。
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Service.MainPageView">
<Label Text="{Binding Location}"
VerticalOptions="Center"
HorizontalOptions="Center"
FontSize="40"/>
</ContentPage>
上述视图的视图模型也不是什么花哨的东西。只是 INotifyPropertyChanged
的实现,以及为依赖注入位置服务而设计的构造函数。
public class MainPageViewModel : INotifyPropertyChanged
{
private readonly ILocationService _locationService;
public MainPageViewModel(ILocationService locationService)
{
_locationService = locationService;
_locationService.LocationChanged += _locationService_LocationChanged;
}
public string Location { get; set; }
private void _locationService_LocationChanged(object sender, LocationEventArgs e)
{
Location = e.Location.Longitude + ", " + e.Location.Latitude;
OnPropertyChanged("Location");
}
public void OnAppearing()
{
_locationService.RequestLocation();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnDisappering()
{
_locationService.StopRequests();
}
}
位置事件处理程序(_locationService_LocationChanged
)只是将两个坐标粘合在一起,并设置新位置的 string
表示形式,并通知视图更新。
如何将 LocationService
注入视图模型?有一个非常好的库叫做 TinyIoC。在我上一篇文章中,我解释了如何为这个库添加 PCL 支持,以及这个将在示例中使用的版本。但是,如果您愿意,可以在 Android 项目中从 TinyIoC官方包。我个人认为,将 IoC 容器放在 PCL 中,而不是在平台项目中担心它(这要容易得多,因为您不需要创建另一个接口作为 PCL 到平台项目中 TinyIoC 的代理)要好得多。您甚至可以将其放在您在所有项目中使用的某个共享库中,然后即可轻松使用。
无论如何,在 Android Activity
类中,您可以放置服务实现的初始化。
[Activity(Label = "Service", Icon = "@drawable/icon",
MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : FormsApplicationActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Xamarin.Forms.Forms.Init(this, bundle);
RegisterServices();
LoadApplication(new App());
}
private void RegisterServices()
{
var container = TinyIoCContainer.Current;
container.Register(this);
container.Register<ILocationService, LocationService>();
}
}
整个 IoC 魔术发生在 RegisterServices
方法中,其中 LocationService
及其 PCL 接口一起注册。这实际上仍然迫使您在 Activity 中做一些工作,但您必须承认,用一行代码启用某些功能比实现整个接口要方便得多。如果您真的不喜欢这种注册服务的方式,TinyIoC 中有自动注册功能。此外,还可以添加自己的自动注册,注册域中可用的某些特定程序集或标记有属性的类等。
现在,最后,可以创建 Xamarin 应用程序并将主页设置为,嗯,MainPage
。:)
public partial class App
{
public App()
{
InitializeComponent();
var mainPageViewModel = TinyIoCContainer.Current.Resolve<MainPageViewModel>();
MainPage = new MainPageView
{
BindingContext = mainPageViewModel
};
}
}
如您所见,从 IoC 容器创建视图模型实例会自动处理 ILocationService
的依赖注入。
现在,我们终于可以在我们新创建的应用程序中测试一切是否正常。
上述测试是在模拟器上进行的,它可能看起来有点奇怪(有两个窗口等),但我认为它很好地展示了设备位置的更新如何更新 MainPage
上的标签。我不用到处拿着平板电脑去改变位置。:)
兴趣点
这确实是一个基本的 GPS 服务实现。当然,可以实现更复杂的逻辑。在示例中,在 ILocationService
中添加一种平台无关的方式来请求特定的 GPS 更新精度会很好。在上面的代码中,使用了 Criteria
类来选择位置提供程序。可以通过某种开关将其设置为 GPS 或 NETWORK 或两者兼有。此外,示例代码设置为恒定的时间间隔(2 秒,即传递给 LocationManager
类的 RequestLocationUpdates
方法的 2000 毫秒)或位置更新之间的最小距离差为 1 米(传递给 RequestLocationUpdates
方法的 1)。还可以实现更复杂的请求位置算法,以便它在符合Android 文档的建议的情况下,以最佳结果节省电池电量。
摘要
同样,我们可以为更多机制创建 Android 服务:加速计、网络状态、软键盘可见性等,而无需将所有这些都实现在 Activity
类中,那样会使它变得臃肿,代码也难以在应用程序之间轻松共享。毕竟,编写一次然后重用它,比到处复制粘贴相同的代码要好得多。
视图模型服务注入也是一个更明智的做法。您无法轻易地从 Activity
更新 Xamarin 视图,而 Xamarin 官方文档就是这样做的。更令人难以置信的是,Xamarin 文档涉及修改 Android 原生视图来显示 GPS 位置:)。如果您无论如何都在原生 Android 视图中进行所有操作,那么使用 Xamarin 有什么意义?是的,可以在 Activity
类中获取 Xamarin 当前视图页面并从中更新,但这会强制为此创建另一个机制。将新位置推送到视图模型会更复杂,而应用程序视图逻辑无论如何都应该位于那里。像这样做事是一个糟糕的模式。将此类平台服务分开并将它们的接口注入视图模型中是一种更简单、更干净的方法。
您可以从本文顶部的链接下载代码,或者在 GitHub 上找到它。