Содержание
Геолокация — определение реального географического местоположения электронного устройства. Использование геолокации на устройствах Android, по-видимому, одно из самых интересных и популярных направлений в разработке различных приложений. С геолокацией мы сталкиваемся в различных навигаторах, при работе с картографическими приложениями, с использованием геолокации может даже рассчитываться скорость перемещения устройства в пространстве и так далее. В этой части мы рассмотрим использование геолокации в Android.
Интерфейс IGeolocation
Для использования геолокации в .NET MAUI используется интерфейс IGeolocation
, который предоставляет нам следующие методы:
Метод | Возвращаемый тип | Описание |
Get |
Task<Location> |
Возвращает последнее известное расположение устройства. |
Get |
Task<Location> |
Возвращает текущее расположение устройства. |
Start |
Task<Boolean> |
Начинает прослушивать обновления расположения с помощью события LocationChanged . События могут отправляться только в том случае, если приложение находится на переднем плане. |
Stop |
void |
Останавливает прослушивание обновлений расположения, когда приложение находится на переднем плане. Не оказывает никакого влияния, если свойство IsListeningForeground имеет значение false . |
Также у интерфейса определено два события:
event EventHandler<GeolocationLocationChangedEventArgs>? LocationChanged;
генерируется при смене расположения устройства и
event EventHandler<GeolocationListeningFailedEventArgs>? ListeningFailed;
которое возникает при ошибке во время прослушивания события LocationChanged
. При срабатывании этого события прослушивание дальнейших обновлений расположения останавливается. Интерфейс реализуется в свойстве Default
класса Geolocation
. Рассмотрим применение геолокации в нашем приложении.
Геолокация в .NET MAUI
Определение необходимых разрешений
Для получения данных о координатах расположения устройства, нашему приложению может потребоваться три разрешения:
ACCESS_COARSE_LOCATION
— позволяет приложению получить доступ к приблизительному местоположению.ACCESS_FINE_LOCATION
— позволяет приложению получать доступ к точному местоположению.ACCESS_BACKGROUND_LOCATION
— позволяет приложению получать доступ к местоположению в фоновом режиме
Кроме того, мы должны также указать, что приложение будет использовать следующие аппаратные возможности:
android.hardware.location
— приложение использует одну или несколько функций устройства для определения местоположения, например местоположение по GPS или местоположение по вышкам сотовой сетиandroid.hardware.location.gps
— приложение использует точные координаты местоположения, полученные от приемника глобальной системы позиционирования (GPS) на устройствеandroid.hardware.location.network
— приложение использует приблизительные координаты местоположения, полученные из сетевой системы геолокации, поддерживаемой устройством
Все эти разрешение и использование аппаратных возможностей мы можем определить в с помощью атрибутов сборки в файле Platforms/Android/MainApplication.cs. Создадим новый проект .NET MAUI и изменим файл следующим образом:
using Android.App; using Android.Runtime; [assembly: UsesPermission(Android.Manifest.Permission.AccessCoarseLocation)] [assembly: UsesPermission(Android.Manifest.Permission.AccessFineLocation)] [assembly: UsesPermission(Android.Manifest.Permission.AccessBackgroundLocation)] [assembly: UsesFeature("android.hardware.location", Required = false)] [assembly: UsesFeature("android.hardware.location.gps", Required = false)] [assembly: UsesFeature("android.hardware.location.network", Required = false)] namespace MauiLocation; [Application] public class MainApplication : MauiApplication { public MainApplication(IntPtr handle, JniHandleOwnership ownership) : base(handle, ownership) { } protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); }
Что касается разрешений, то мы уже ни один раз их использовали при работе в Android. Здесь стоит обратить внимание на последние три строки в которых мы указываем какие аппаратные функции будет использовать наше приложение.
При определении атрибутов с UsesFeature()
мы используем параметр Required=false
, который означает, что приложение использует эту функцию, если она присутствует на устройстве, но оно предназначено для работы без указанной функции. То есть, грубо говоря, если функция имеется на устройстве — супер, будем использовать, нет — хорошо, обойдемся без неё.
Получение данных о последнем местоположении
Устройства могут кэшировать данные о последнем местоположении и часто получить последнее местоположение из кэша устройства бывает быстрее, чем ожидать выполнение запроса. При этом, стоит учитывать, что полученные из кэша данные могут быть менее точными (а иногда вообще не соответствовать реальному местоположению устройства).
Чтобы продемонстрировать получение данных о последнем местоположении устройства, изменим XAML-код страницы MainPage следующим образом:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MauiLocation.MainPage"> <ScrollView> <VerticalStackLayout Padding="30,0" Spacing="25"> <Grid> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Label Text="Широта" Grid.Column="0" Grid.Row="0"/> <Label Text="00,000000" x:Name="LatitudeLabel" Grid.Column="1" Grid.Row="0"/> <Label Text="Долгота" Grid.Column="0" Grid.Row="1"/> <Label Text="00,000000" x:Name="LongitudeLabel" Grid.Column="1" Grid.Row="1"/> </Grid> <Button x:Name="CounterBtn" Text="Click me" Clicked="OnCounterClicked" HorizontalOptions="Fill" /> </VerticalStackLayout> </ScrollView> </ContentPage>
Для вывода данных о широте и долготе местоположения устройства будут использоваться две метки Label
:
<Label Text="00,000000" x:Name="LatitudeLabel" Grid.Column="1" Grid.Row="0"/> <Label Text="00,000000" x:Name="LongitudeLabel" Grid.Column="1" Grid.Row="1"/>
Вызом метода Get
будет осуществляться при клике по кнопке CounterBtn
, у которой уже определен обработчик события Clicked
— метод OnCounterClicked()
. Изменим этот метод следующим образом:
private async void OnCounterClicked(object sender, EventArgs e) { var location = await Geolocation.Default.GetLastKnownLocationAsync(); if (location != null) { LatitudeLabel.Text = location.Latitude.ToString(); LongitudeLabel.Text = location.Longitude.ToString(); } }
Метод GetLastKnownLocationAsync()
вернет нам объект типа Location
, о свойствах и методах которого мы поговорим ниже. Сейчас же мы используем только два свойства этого объекта — это значение широты (Latitude
) и долготы (Longitude
). Проверим работу приложения.
При первом запуске и клике по кнопке, приложение запросит необходимые разрешения:
После подтверждения разрешений вы увидите на экране последние данные о широте и долготе расположения устройства:
Геолокация: Получение данных о текущем местоположении
Чтобы получить данные о местоположении текущем местоположении устройства, необходимо использовать метод Get
public Task<Location?> GetLocationAsync(GeolocationRequest request, CancellationToken cancelToken);
Этот метод, в отличие от предыдущего, требует указания параметра request
типа GeolocationRequest
, с помощью которого мы можем указать критерии определения местоположения. Класс GeolocationRequest
определяет следующие свойства:
Свойство | Тип | Описание |
DesiredAccuracy |
GeolocationAccuracy |
Желаемая точность определения местоположения устройства. Может принимать следующие значения:
|
RequestFullAccuracy |
bool |
Указывает следует ли запрашивать полную точность определения местоположения. Это свойство используется только в iOS 14+ |
Timeout |
TimeSpan |
Время ожидания запроса |
Стоит отметить, что запрос текущего местоположения может занимать продолжительное время. Поэтому, при использовании метода Get
стоит предусмотреть отмену выполнения запроса.
Изменим код XAML страницы MainPage следующим образом:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MauiLocation.MainPage"> <ScrollView> <VerticalStackLayout Padding="30,0" Spacing="25"> <ActivityIndicator ZIndex="1" IsRunning="False" x:Name="Indicator"/> <Grid> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Label Text="Широта" Grid.Column="0" Grid.Row="0"/> <Label Text="00,000000" x:Name="LatitudeLabel" Grid.Column="1" Grid.Row="0"/> <Label Text="Долгота" Grid.Column="0" Grid.Row="1"/> <Label Text="00,000000" x:Name="LongitudeLabel" Grid.Column="1" Grid.Row="1"/> </Grid> <Button x:Name="CounterBtn" Text="Click me" Clicked="OnCounterClicked" HorizontalOptions="Fill" /> <Button Text="Отменить запрос" Clicked="CancelRequestClicked" HorizontalOptions="Fill" /> </VerticalStackLayout> </ScrollView> </ContentPage>
Здесь мы добавили на страницу ещё одну кнопку клик по которой будет отменять операцию:
<Button Text="Отменить запрос" Clicked="CancelRequestClicked" HorizontalOptions="Fill" />
а также элемент ActivityIndicator
, чтобы видеть, когда приложение пытается получить текущее местоположение. Теперь изменим класс MainPage
следующим образом:
namespace MauiLocation { public partial class MainPage : ContentPage { private CancellationTokenSource _cancelTokenSource; private bool _isCheckingLocation; public MainPage() { InitializeComponent(); } private async void OnCounterClicked(object sender, EventArgs e) { _isCheckingLocation = true; Indicator.IsRunning = _isCheckingLocation; GeolocationRequest request = new GeolocationRequest(GeolocationAccuracy.Best, TimeSpan.FromSeconds(20)); _cancelTokenSource = new CancellationTokenSource(); Location? location = await Geolocation.Default.GetLocationAsync(request, _cancelTokenSource.Token); if (location != null) { LatitudeLabel.Text = location.Latitude.ToString(); LongitudeLabel.Text = location.Longitude.ToString(); } _isCheckingLocation = false; Indicator.IsRunning = _isCheckingLocation; } public void CancelRequestClicked(object sender, EventArgs e) { if (_isCheckingLocation && _cancelTokenSource != null && _cancelTokenSource.IsCancellationRequested == false) _cancelTokenSource.Cancel(); } } }
Рассмотрим основные изменения в классе. Во-первых, мы объявили два поля класса:
private CancellationTokenSource _cancelTokenSource; private bool _isCheckingLocation;
Первое поле — это токен отмены операции, а второе — флаг, указывающий используется ли в данный момент геолокация на устройстве. Во-вторых, мы изменили обработчик Clicked
кнопки для получения местоположения. Здесь, помимо всего прочего. мы запрашиваем текущие координаты устройства, используя наибольшую точность определения и, ожидая ответа на запрос не более 20 секунд:
GeolocationRequest request = new GeolocationRequest(GeolocationAccuracy.Best, TimeSpan.FromSeconds(20));
Для того, чтобы можно было отменить запрос, мы передаем в метод GetLocationAsync()
токен отмены:
_cancelTokenSource = new CancellationTokenSource(); Location? location = await Geolocation.Default.GetLocationAsync(request, _cancelTokenSource.Token);
В-третьих, мы добавили обработчик кнопки для отмены запроса:
public void CancelRequestClicked(object sender, EventArgs e) { if (_isCheckingLocation && _cancelTokenSource != null && _cancelTokenSource.IsCancellationRequested == false) _cancelTokenSource.Cancel(); }
Можно запустить приложение и проверить результат. Вот как будет выглядеть страница при запросе данных о местоположении:
Геолокация: использование событий
События IGeolocation
можно использовать для отслеживания местоположения устройства в фоновом режиме. Чтобы использовать события Location
и Listening
добавим их обработчики в класс MainPage
:
public partial class MainPage : ContentPage { ... public ObservableCollection<Location> Locations { get; private set; } = []; public string LastError { get; private set; } = string.Empty; ... private void OnLocationChanged(object? sender, GeolocationLocationChangedEventArgs args) { Locations.Add(args.Location); } public void OnListeningFiled(object? sender, GeolocationListeningFailedEventArgs args) { switch (args.Error) { case GeolocationError.Unauthorized: { LastError = "Разрешение на получение данных о местоположении устройства было отозвано"; break; } case GeolocationError.PositionUnavailable: { LastError = "Не удалось получить данные о местоположении"; break; } } } ... }
В первом обработчике — OnLocationChanged()
мы заносим полученные данные о местоположении в свойство-коллекцию Locations
, чтобы затем выводить список координат на странице. Второй обработчик — OnListeningFiled()
служит для вывода сведений об ошибке, если таковая случится при прослушивании. Перечисление GeolocationError
, используемое в этом методе может принимать два значения:
Unauthorized
— если приложение по каким-либо причинам не может получить доступ к геолокации, например, пользователь запретил или отклонил разрешениеPositionUnavailable
— если в процессе прослушивания возникает какая-либо другая ошибка.
Теперь добавим на страницу MainPage
ещё одну кнопку и создадим для неё следующий обработчик события Clicked
:
private async void OnListeningClicked(object sender, EventArgs e) { if (Geolocation.Default.IsListeningForeground) { Geolocation.Default.LocationChanged -= OnLocationChanged; Geolocation.Default.ListeningFailed -= OnListeningFiled; Geolocation.Default.StopListeningForeground(); } else { Geolocation.Default.LocationChanged += OnLocationChanged; Geolocation.Default.ListeningFailed += OnListeningFiled; GeolocationListeningRequest request = new GeolocationListeningRequest(); request.DesiredAccuracy = GeolocationAccuracy.Best; request.MinimumTime = TimeSpan.FromSeconds(1); if (await Geolocation.Default.StartListeningForegroundAsync(request) == false) LastError = "Не смогли запустить прослушивание"; } }
Здесь мы проверяем значение свойства IsListeningForeground
, которое показывает осуществляется ли сейчас прослушивание местоположение или нет и, в зависимости от значения этого свойства, либо запускаем процесс прослушивания и подписываемся на события, либо наоборот — останавливаем прослушивание.
Код XAML страницы MainPage изменим следующим образом:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:locations="clr-namespace:Microsoft.Maui.Devices.Sensors;assembly=Microsoft.Maui.Essentials" x:Class="MauiLocation.MainPage" xmlns:m="clr-namespace:MauiLocation" x:DataType="m:MainPage"> <ScrollView> <VerticalStackLayout Padding="30,0" Spacing="25"> <ActivityIndicator ZIndex="1" IsRunning="False" x:Name="Indicator"/> <Grid> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Label Text="Широта" Grid.Column="0" Grid.Row="0"/> <Label Text="00,000000" x:Name="LatitudeLabel" Grid.Column="1" Grid.Row="0"/> <Label Text="Долгота" Grid.Column="0" Grid.Row="1"/> <Label Text="00,000000" x:Name="LongitudeLabel" Grid.Column="1" Grid.Row="1"/> </Grid> <Button x:Name="CounterBtn" Text="Click me" Clicked="OnCounterClicked" HorizontalOptions="Fill" /> <Button Text="Отменить запрос" Clicked="CancelRequestClicked" HorizontalOptions="Fill" /> <Button Text="Начать прослушивание" Clicked="OnListeningClicked"/> <CollectionView ItemsSource="{Binding Locations, Source={RelativeSource AncestorType={x:Type m:MainPage}}, Mode=TwoWay}"> <CollectionView.ItemTemplate> <DataTemplate x:DataType="locations:Location"> <HorizontalStackLayout> <Label Text="{Binding Latitude, StringFormat='Широта {0}; '}"/> <Label Text="{Binding Longitude, StringFormat='Долгота {0} '}"/> </HorizontalStackLayout> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> <Label Text="{Binding LastError}"/> </VerticalStackLayout> </ScrollView> </ContentPage>
Список координат будем выводить в CollectionView
, используя привязку к свойству Locations
и форматирование строки в привязке.
<Label Text="{Binding Latitude, StringFormat='Широта {0}; '}"/> <Label Text="{Binding Longitude, StringFormat='Долгота {0} '}"/>
Запустим приложение и проверим его работу. После того, как запустится прослушивание текущего местоположения, страница приложение будет выглядеть, примерно, следующим образом:
Итого
В этой части мы познакомились с основами использования геолокации в .NET MAUI — научились получать данные о последнем местоположении, текущем местоположении, а также использовать события для получения данных об изменении местоположения устройства. В следующей части мы продолжим изучение этой темы.