Собираем показания датчиков с Android смартфона. Работа над ошибками

  • Tutorial
В своем предыдущем посте я рассказал, как получить углы наклона аппарата во всех трех плоскостях. Однако, как оказалось, метод, использованный в топике является deprecated начиная с API Level 8 (Android 2.2). Исправлю эту ошибку и расскажу, как правильно получать данные под катом.

Сначала чуть-чуть теории


В документации по Android нам предлагают вместо SENSOR_ORIENTATION использовать метод
getOrientation (float[] R, float[] values)

Этот метод принимает два параметра:
  • R — RotationMatrix или матрица поворота устройства;
  • values — массив из трех элементов типа float, в который запишутся углы наклона аппарата в радианах;

Значит, для осуществления цели нам всего лишь необходимо получить ту самую матрицу поворота. А получить ее можно из другого метода:
getRotationMatrix (float[] R, float[] I, float[] gravity, float[] geomagnetic)

Данный метод в первые два массива помещает матрицу поворота и матрицу отклонения аппарата от (магнитного полюса Земли ?). Для этого ей необходимо также передать данные с датчика акселерометра и геомагнитного датчика. К счастью TYPE_ACCELEROMETER и TYPE_MAGNETIC_FIELD не являются deprecated. Следовательно, с них мы и будем снимать показания.

Переходим к практике


Layout мы можем взять тот же, что был в прошлом примере. Вот он:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="25dp"
            android:text="Угол XY" />

        <TextView
            android:id="@+id/xyValue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="25dp"
            android:text="0" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:id="@+id/textView3"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="25dp"
            android:text="Угол XZ" /> />

        <TextView
            android:id="@+id/xzValue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="25dp"
            android:text="0" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout3"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:id="@+id/textView5"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="25dp"
            android:text="Угол ZY" />

        <TextView
            android:id="@+id/zyValue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="25dp"
            android:text="0" />

    </LinearLayout>

</LinearLayout>


В главной активити объявим переменные:

    private final SensorManager msensorManager; //Менеджер сенсоров аппрата
	
    private float[] rotationMatrix;     //Матрица поворота
    private float[] accelData;           //Данные с акселерометра
    private float[] magnetData;       //Данные геомагнитного датчика
    private float[] OrientationData; //Матрица положения в пространстве

    private TextView xyView;
    private TextView xzView;
    private TextView zyView;


Метод onCreate должен реализовывать методы класса SensorEventListener, поэтому его объявление изменится:

    public class Main extends Activity implements SensorEventListener{

И добавятся два обязательных метода onAccuracyChanged и onSensorChanged.
А перед setContentView пишем:

    msensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        
    rotationMatrix = new float[16];
    accelData = new float[3];
    magnetData = new float[3];
    OrientationData = new float[3];
        
    xyView = (TextView) findViewById(R.id.xyValue);  //
    xzView = (TextView) findViewById(R.id.xzValue);  // Наши текстовые поля для вывода показаний
    zyView = (TextView) findViewById(R.id.zyValue);  //


Если читали мой прошлый пост, то ничего нового пока не увидели. Если же нет, то Вам нужно знать, что в первой строчке мы получаем объект менеджера датчиков.
Теперь создадим метод onResume, в котором уточним, данные каких датчиков нам необходимы:

    @Override
    protected void onResume() {
    	super.onResume();
    	msensorManager.registerListener(this, msensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI );
    	msensorManager.registerListener(this, msensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_UI );
    }

Здесь мы передаем в метод registerListener тип нужного нам датчика (полный список обозначений датчиков) и частоту обновления данных (может быть SENSOR_DELAY_NORMAL, SENSOR_DELAY_UI, SENSOR_DELAY_GAME, or SENSOR_DELAY_FASTEST в порядке увеличения частоты).

Чтобы наша программа не съедала ресурсы смартфона будучи свернутой по событию onPause «снимаем» получение данных с датчиков:

    @Override
    protected void onPause() {
        super.onPause();
        msensorManager.unregisterListener(this);
    }


Давайте создадим новый метод, в котором данные с датчиков будем заносить в соответствующий датчику массив. Назовем метод loadNewSensorData:

    private void loadNewSensorData(SensorEvent event) {
        final int type = event.sensor.getType(); //Определяем тип датчика
        if (type == Sensor.TYPE_ACCELEROMETER) { //Если акселерометр
                accelData = event.values.clone();
        }
       
        if (type == Sensor.TYPE_MAGNETIC_FIELD) { //Если геомагнитный датчик
                magnetData = event.values.clone();
        }
    }

Ну вот, почти и все! Осталось только написать обработчик события onSensorChanged:

	public void onSensorChanged(SensorEvent event) {
	loadNewSensorData(event); // Получаем данные с датчика
        SensorManager.getRotationMatrix(rotationMatrix, null, accelData, magnetData); //Получаем матрицу поворота
        SensorManager.getOrientation(rotationMatrix, OrientationData); //Получаем данные ориентации устройства в пространстве
        
        if((xyView==null)||(xzView==null)||(zyView==null)){  //Без этого работать отказалось.
        	xyView = (TextView) findViewById(R.id.xyValue);
            xzView = (TextView) findViewById(R.id.xzValue);
            zyView = (TextView) findViewById(R.id.zyValue); 
        }
        
       //Выводим результат
        xyView.setText(String.valueOf(Math.round(Math.toDegrees(OrientationData[0]))));
        xzView.setText(String.valueOf(Math.round(Math.toDegrees(OrientationData[1]))));
        zyView.setText(String.valueOf(Math.round(Math.toDegrees(OrientationData[2]))));
	}


Все готово! Теперь я исправил свою ошибку и научил вас получать данные с датчиков не deprecated способом. :)

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

P.S.: Вы наверняка заметили лишнюю проверку переменных на отличие от null в последнем листинге. Без нее компилятор выдает java.lang.NullPointerException. Буду признателен, если кто-нибудь объяснит или укажет на ошибку.

UPD: Проблема решилась благодаря firexel.
Переставьте местами setContentView и строчки с findViewById в onCreate

После этого можно убрать лишнюю проверку.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 11

    +1
    Переставьте местами setContentView и строчки с findViewById в onCreate и не будет вылетать с NPE
      –1
      Блин, правда про это даже не подумал. Помогло, спасибо!
    • UFO just landed and posted this here
        0
        Если честно, то не имел дела с андроид-планшетами, поэтому не уверен, что там дела обстоят идентично.
        +1
        > //Получаем позицию устройства в пространстве
        Наверное всетаки «параметры ориентации в пространстве», а не позиция?
          0
          Спасибо, исправил.
          +3
          Это не урок, а «вот смотрите я накидал кода и циферки меняются, ура»

          >> Метод onCreate должен реализовывать методы класса SensorEventListener
          В Java методы умеют реализывывать другие методы класса? SensorEventListener — вообще не класс а интерфейс.

          раз пишете под андройд почитайте это:
          source.android.com/source/code-style.html

          >> public TextView xyView;
          >> public TextView xzView;
          >> public TextView zyView;

          Вот это зачем паблик делать? тут надо прайват написать.

          >> if((xyView==null)||(xzView==null)||(zyView==null)){ //Без этого работать отказалось.

          Это вот вообще бред. Надо в onCreate найти ваши TextView, посмотреть что они не нул, если ктото из них нул — дальше не работать.

          некоторые свойста хорошо бы сделать final, например, msensorManager никогда не меняется.

          лучше использовать не clone, а System.arraycopy — а то всю память замусорите, постоянно создавая новые массивы под данные с датчиков. Если использовать clone то все эти (rotationMatrix, accelData, magnetData, OrientationData) данные нужно сделать final.

          Это же урок для новичков, нужно обращать внимание на все мелочи, чтобы люди, которые плохо разбираются, сразу учились всему, а не только радовались что у них 3 цифры на экране меняются.
            0
            Спасибо за ценные замечания. Дело в том, что я сам еще все это осваиваю. Однако, не согласен с Вами в одном: в моем посте объясняется значение практически каждой строки и нет фраз вроде «сделайте так и не вникайте». Поэтому это вполне можно назвать уроком.
            0
            вот теперь получше. Теперь осталось рассказать о сглаживании данных.

            + пример как сделать компасс www.netmite.com/android/mydroid/cupcake/development/samples/Compass/src/com/example/android/compass/CompassActivity.java
              0
              Спасибо, но как видите, есть еще, куда работать)
              0
              да не за что. хотя ещё более интересный топик в этом направлении — remappping of coordinates

              допустим цифирки азимута на экаран вы вывели, а знаете ли вы, как именно определен этот угол? Между чем и чем? А можно ли это изменить? Чтобы например азимут считался от направления куда смотрит камера в landscape mode?

              Only users with full accounts can post comments. Log in, please.