T.M. SoftStudio

feci quod potui, faciant meliora potentes

Купить полную версию курса "Программирование мобильных приложений для платформы Android" (русскоязычная версия, лекции, тесты, лабораторные работы)

Программирование мобильных приложений для платформы Android

Лекция 17

Датчики. Часть 1

Портативные устройства позволяют выполнять контекстно-зависимые вычисления, и это означает, что приложения могут реагировать или вести себя по-разному, основываясь на контексте, принимая во внимание такие факторы, как местоположение, где приложение используется, как пользователь держит устройство, какая освещенность, или как быстро пользователь движется.

Чтобы делать это, приложения считывают информацию из различных датчиков, которые встроены в сегодняшние портативные устройства.

В этом уроке я расскажу о датчиках, которые устройства Android могут поддерживать, и я расскажу о том, как приложения могут получить доступ к этим датчикам.

Далее, я буду обсуждать события датчиков, класс, который Android использует для представления показаний датчиков, и я буду обсуждать слушателей событий датчиков, которые используются для передачи информации от датчика вашему приложению.

После этого, я буду обсуждать некоторые общие методы, которые используются, чтобы сгладить или отфильтровать значения датчика для использования приложением значений различными способами.

Я буду демонстрировать несколько примеров приложений, которые используют общие датчики.

Датчики это аппаратные компоненты, которые измеряют физическую среду вокруг устройства и эти датчики поставляются трех типов.

Есть датчики, которые измеряют движение, например, как быстро вы двигаетесь.

Есть датчики, которые измеряют положение устройства.

Например, где вы находитесь или ориентацию устройства.

И есть датчики, которые измеряют характеристики среды, например, количество света, давление или влажность вокруг устройства.

Например, мое устройство имеет 3х-осевой акселерометр, который измеряет силы, действующие на устройство, например, когда его встряхивают.

Оно также имеет 3х-осевое магнитное поле, которое может быть использовано для измерения позиции или ориентации по отношению к магнитному полю земли.

И мы увидим это в действии позже в одном из примеров приложений.

И, наконец, мое устройство имеет барометр, который измеряет атмосферное давление.

Для того чтобы приложение использовало датчики, оно сначала должно получить ссылку на SensorManager.

SensorManager является системным сервисом, который управляет датчиками.

Приложение получить ссылку на SensorManager, вызывая метод getSystemService, передавая значение Context.SENSOR_SERVICE.

Для того чтобы получить доступ к определенному датчику, приложение использует метод getDefaultSensor сервиса SensorManager, передавая константу, соответствующую желаемому датчику.

Некоторые из этих констант типов датчиков включают в себя Sensor.TYPE_ACCELEROMETER для акселерометра, Sensor.TYPE_MAGNETIC_FIELD для датчика магнитного поля или Sensor.TYPE_PRESSURE для барометра.

Если приложение хочет получить информацию от датчика, оно должно будет реализовать интерфейс SensorEventListener.

И этот интерфейс определяет методы обратного вызова, которые вызываются при изменении точности датчика и при изменении значений датчика.

Когда точность датчика изменяется, Android вызывает метод onAccuracyChanged, передавая ему датчик, который изменился, и передавая новую точность.

Когда датчик имеет новые значения, вызывается метод onSensorChanged, передавая событие SensorEvent, соответствующее новому состоянию датчика.

Перед тем как приложение может получить SensorEvents, однако, оно должно будет зарегистрировать SensorEventListener, и, как только работа с датчиком выполнена, вы также захотите отменить регистрацию датчика, для того, чтобы не тратить заряд батареи.

Для регистрации SensorEventListener для данного датчика, нужно вызвать метод registerListener, передавая SensorEventListener, вызваемый для датчика, который вы хотите слушать, и скорость, с которой вы хотите, чтобы с датчика снимались показания.

Чтобы удалить слушателя для всех датчиков, для которых он был зарегистрирован, вы можете, например, вызвать метод unregisterListener, передавая SensorEventListener и передавая битовую маску, указывающую датчики, которые вы больше не хотите слушать.

Показания датчика представлены в виде экземпляров класса SensorEvent.

Данные, которые содержит этот класс, конечно, зависят от конкретного вида датчика и включают в себя тип датчика, метка времени, точность и данные измерений.

Для того чтобы разобраться в данных, вы должны знать, как измерения интерпретируются для конкретного датчика.

Например, многие датчики используют трехмерную систему координат.

Когда ориентация по умолчанию это портрет и когда устройство лежит плоско, лицом вверх на столе, оси системы координат, как показано здесь.

Система координат ориентирована для ориентации по умолчанию устройства и не изменится, даже если устройство меняет свое текущую ориентацию.

Например, даже если устройство переходит от портретного режима в ландшафтный режим, система координат не изменяется.

Наш первый пример для этого урока называется SensorRawAccelerometer.

Это приложение просто отображает необработанные значения, которые получает от акселерометра устройства.

Как вы можете видеть, наибольшая сила в настоящее время приложена по оси Y.

В то время как я делаю эту демонстрацию, я пытаюсь держать устройство прямо вверх и вниз.

Теперь я поверну устройство против часовой стрелки на 90 градусов вокруг оси Z, и вы увидите, что в настоящее время наибольшая сила приложена по оси Х.

Давайте посмотрим на исходный код для этого приложения.

Откроем основную активность.

Обратите внимание, что этот класс реализует интерфейс SensorEventListener.

Так что мы можем принимать обратные вызовы от SensorManager.

В методе оnCreate, приложение получает ссылку на SensorManager.

Далее, код получает ссылку на акселерометр устройства с помощью вызова SensorManager.getDefaultSensor, передавая константу типа, который соответствует акселерометру.

В методе onResume, приложение регистрирует этот класс в качестве слушателя событий акселерометра путем вызова метода registerListener.

Последний параметр SensorManager.SENSOR_DELAY_UI соответствует относительно низкой частоте опроса датчика.

Далее метод оnPause отменяет регистрацию этого класса в качестве слушателя для всех датчиков.

В onSensorChanged методе код сначала проверяет, чтобы убедиться, что это событие относится к акселерометру.

Далее, код проверяет, что определенное количество времени прошло с момента последнего опроса датчика.

И если да, код записывает значения акселерометра X, Y, и Z.

А потом он отображает эти значения на экране.

Датчики. Часть 2

В примере приложения, который мы только что посмотрели, я пытался держать устройство идеально прямо.

И если бы я был в состоянии сделать это, акселерометр, в идеале, сообщил бы значения х = 0 м/c2, у = 9,81 м/c2, z = 0 м/c2.

Но, как вы видели в примере приложения, значения акселерометра колебались.

Все приложения будут подвержены этому, из-за естественных движений пользователя, не совсем гладких поверхностей, электрического шума и так далее.

При создании приложений, использующих датчики, разработчики часто применяют преобразования к необработанным данным, чтобы сгладить их.

Две общих вида преобразований это низкочастотные фильтры и высокочастотные фильтры.

Давайте поговорим о каждом из них.

Низкочастотные фильтры используются, чтобы убирать изменения краткосрочных сил, при этом выделяя долгосрочные постоянные силы.

Вы можете использовать низкочастотный фильтр, когда ваше приложение имеет дело с постоянной силой тяжести, например, и вы не хотите обращать внимание на то, что ваша рука трясется немного.

Реальным примером этого может быть уровень плотника.

Пузырь уровня должен двигаться на основе тяжести, а не из-за небольших дрожаний руки.

В отличие от этого, вы используете высокочастотный фильтр, если вы хотите выделить изменения краткосрочных сил.

И вы хотите преуменьшить роль постоянных сил.

Вы можете использовать фильтр высоких частот, когда ваше приложение должно игнорировать постоянную силу тяжести, например.

Но при этом следует реагировать на движения пользователя.

Реальным примером этого может быть ударный музыкальный инструмент.

Вы действительно не заботитесь о силе тяжести здесь, вы заботитесь о том, как пользователь ударяет по инструменту.

Следующее приложение называется SensorFilteredAccelerometer.

Это приложение применяет как низкочастотный, так и высокочастотный фильтр для исходных значений акселерометра.

А потом приложение отображает отфильтрованные значения.

Давайте запустим его.

Приложение отображает исходные значения после применения низкочастотного фильтра и исходные значения после применения высокочастотного фильтра.

Вы увидите, что низкочастотные значения близки к идеальным показаниям акселерометра.

В то же время вы можете видеть, что высокочастотные значения стремятся к нулю.

Давайте посмотрим на исходный код для этого приложения.

Откроем основную активность.

И заметьте, еще раз, что этот класс реализует интерфейс SensorEventListener.

Таким образом, можно получать обратные вызовы от менеджера датчика.

В методе OnCreate, код приложения получает ссылку на менеджер датчика.

Далее, код получает ссылку на акселерометр устройства с помощью вызова SensorManager.getDefaultSensor, передавая константу типа, который соответствует акселерометру.

В методе onResume, приложение регистрирует данный класс в качестве слушателя событий акселерометра с помощью вызова метода registerListener.

Далее в методе onPause код отменяет регистрацию этого класса в качестве слушателя для любых датчиков.

Далее посмотрим на onSensorChanged метод.

И, как и прежде, этот метод сначала проверяет, является ли это событие событием акселерометра.

Затем код проверяет, прошло ли определенное количество времени с момента последнего чтения датчика.

Если это так, код записывает значения акселерометра X, Y, и Z.

И затем применяет низкочастотный фильтр для каждого из исходных значений, после чего код применяет высокочастотный фильтр для каждого из исходных значений.

Давайте посмотрим на код этих двух фильтров.

Вот метод lowPass, который вычисляет низкочастотный фильтр.

Этот метод принимает два параметра, текущее показание и среднее показание.

Далее вычисляется отфильтрованное значение, как своего рода средневзвешенное значение.

В этом случае фильтруется значение равное 80% от среднего значения плюс 20% от текущего значения.

Со временем, это значение приближается к идеальному значению, о котором мы говорили ранее.

Метод highPass вычисляет высокочастотные отфильтрованные значения.

И этот метод также имеет два параметра, текущее значение и долгосрочное среднее значение.

Этот код вычитает долгосрочное среднее из текущего значения, и поэтому представляет часть значения, которое не относится к силе тяжести.

Рассмотрим пример приложения, которое называется SensorCompass.

Это приложение использует акселерометр устройства и его магнитометр для ориентации компаса на северный магнитный полюс.

Как вы можете видеть, это приложение отображает зеленый круг с белой стрелкой.

Сейчас, эта стрелка указывает на магнитный север.

Если я начинаю поворачивать устройство, вы видите, что стрелка продолжает указывает на север.

Что и должен делать компас.

Давайте посмотрим на исходный код для этого приложения.

Откроем основную активность и метод оnCreate.

Как и в других приложениях, он начинается с создания пользовательского интерфейса.

И, в частности, он создает пользовательское представление, которое содержит компас со стрелкой.

А потом добавляет это представление в главное представление активности.

Затем код получает ссылку на менеджер датчика.

После этого код получает ссылку на акселерометр устройства и на магнитометр устройства.

В методе onResume код регистрирует этот класс в качестве слушателя для событий акселерометра и событий магнитометра.

В методе onPause код отменяет регистрацию этого класса в качестве слушателя для всех датчиков.

В методе onSensorChanged обрабатываются входящие события датчика.

Этот метод сначала проверяет, является ли это событие событием акселерометра или магнитометра.

А потом копирует соответствующие данные события.

Далее, если есть показания от каждого из двух датчиков, код вызывает метод SensorManager.getRotationMatrix, передавая показания датчиков и массив для хранения матрицы вращения.

Если этот метод успешно выполняется, код вызывает метод SensorManager.getOrientation, передавая матрицу вращения, которую мы только что получили из вызова метода getRotationMatrix.

Там также передается другой массив orientationMatrix.

Когда этот метод возвращает, матрица ориентации будет содержать информацию, которая необходима приложению для определения, как устройство ориентировано по отношению к магнитному северу Земли.

Код затем берет это значение из матрицы ориентации.

А так как эта величина измеряется в радианах, код преобразует радианы в градусы.

После этого код делает недействительными представление компаса со стрелкой, а затем очищает массивы, которые содержат показания датчика.

Давайте посмотрим на представление компаса со стрелкой, как оно использует новую информацию об ориентации.

В методе оnDraw, сначала код сохраняет текущий холст и затем поворачивает это представление на холсте.

В основном идея здесь заключается в том, что если устройство повернуто, скажем, на 90 градусов в сторону от севера, тогда стрелка компаса должна повернуть назад на 90 градусов для того, чтобы компас со стрелкой указывал на север.