65.9K
CodeProject 正在变化。 阅读更多。
Home

Xamarin Android 服务模式 GPS 示例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.61/5 (7投票s)

2017年1月30日

CPOL

8分钟阅读

viewsIcon

13875

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 上找到它。

© . All rights reserved.