Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Orientation Sensor(方向傳感器)詳解與應用

Android Orientation Sensor(方向傳感器)詳解與應用

編輯:關於Android編程



一、前言



本篇blog是我的“Android進階”的第一篇文章,從初學Android到現在斷斷續續也有4個多月時間了,也算是有了一些自己的心得體會,也能自己獨立做一些東西了,這都要感謝我們公司的安卓開發璟博和無所不能的鴻洋給我的幫助和指點。本系列blog將記錄我在開發中、學習中遇到的較為重點的、值得記錄的知識點和技巧,簡單的說就不再是基礎教程了。由於項目中需要用到方向傳感器,所以就借此機會來學一學Android的傳感器部分的知識了,自然也就是本篇blog的內容了。



二、傳感器基礎



官方文檔說的很清楚,Android平台支持三大類的傳感器,它們分別是:

a. Motion sensors

b. Environmental sensors

c. Position sensors


從另一個角度劃分,安卓的傳感器又可以分為基於硬件的和基於軟件的。基於硬件的傳感器往往是通過物理組件去實現的,他們通常是通過去測量特殊環境的屬性獲取數據,比如:重力加速度、地磁場強度或方位角度的變化。而基於軟件的傳感器並不依賴物理設備,盡管它們是模仿基於硬件的傳感器的。基於軟件的傳感器通常是通過一個或更多的硬件傳感器獲取數據,並且有時會調用虛擬傳感器或人工傳感器等等,線性加速度傳感器和重力傳感器就是基於軟件傳感器的例子。下面通過官方的一張圖看看安卓平台支持的所有傳感器類型:

\

\

\


使用傳感器的話那麼首先需要了解的必然是傳感器API了,在Android中傳感器類是通過Sensor類來表示的,它屬於android.hardware包下的類,顧名思義,和硬件相關的類。傳感器的API不復雜,包含3個類和一個接口,分別是:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+PHN0cm9uZz5TZW5zb3JNYW5hZ2VyPC9zdHJvbmc+PC9wPgo8cD48c3Ryb25nPlNlbnNvcjwvc3Ryb25nPjwvcD4KPHA+PHN0cm9uZz5TZW5zb3JFdmVudDwvc3Ryb25nPjwvcD4KPHA+PHN0cm9uZz5TZW5zb3JFdmVudExpc3RlbmVyPC9zdHJvbmc+PC9wPgo8cD48c3Ryb25nPjxicj4KPC9zdHJvbmc+PC9wPgo8cD64+b7dudm3vc7EtbW1xLjFyva31rHwvPK1pb3iys3Su8/C1eI0uPZBUEm1xNPDtKajujwvcD4KPHA+U2Vuc29yTWFuYWdlcqO6v8nS1M2ouf3V4rj2wODIpbS0vajSu7j2tKu40Mb3t/7O8bXEyrXA/aOs1eK49sDgzOG5qbXEuPfW1re9t6i/ydLUt8POyrSruNDG98HQse2hoteisuG78r3is/3XorLhtKu40Mb3ysK8/rzgzP2horvxyKG3vc670MXPorXIoaM8L3A+CjxwPlNlbnNvcqO608PT2rS0vajSu7j2zNi2qLXEtKu40Mb3yrXA/aOs1eK49sDgzOG5qbXEt723qL/J0tTIw8Tjvva2qNK7uPa0q7jQxve1xLmmxNyhozwvcD4KPHA+U2Vuc29yRXZlbnSjus+1zbO74c2ouf3V4rj2wOC0tL2o0ru49rSruNDG98rCvP621M/zo6zM4bmpwcvSu7j2tKu40Mb3tcTKwrz+0MXPoqOssPy6rNK7z8LE2sjdo6zUrcn6tcS0q7jQxvfK/b7doaK0pbeitKu40Mb3tcTKwrz+wODQzaGivqvIt7XEyv2+3dLUvLDKwrz+t6LJ+rXEyrG85KGjPC9wPgo8cD5TZW5zb3JFdmVudExpc3RlbmVyo7q/ydLUzai5/dXiuPa907/atLS9qMG9uPa72LX308O3qMC0vdPK1bSruNDG97XEysK8/s2o1qqjrLHIyOe1sbSruNDG97XEJiMyMDU0MDu3osn6seS7r8qxoaM8L3A+CjxwPjxicj4KPC9wPgo8cD696cncwcu7+bShtcS31sDg1q6686OsztLDx9TZv7S/tLSruNDG97XEv8nTw9DUse0mIzI2Njg0O6OssrvNrLXEtKu40Mb31Nqyu82stcRBbmRyb2lksOaxvtauvOTKx9PQsu7S7LXEo6yxyMjn09C1xNTatc2w5rG+v8nS1NPDo6y1q9TauN+w5rG+vs2xu8b608OjrM/qz7i1xMr9vt3SwMi7v7S/tLnZt721xNXi1cWx7SYjMjY2ODQ7o7o8L3A+CjxwPjxpbWcgc3JjPQ=="/uploadfile/Collfiles/20141209/20141209082759107.jpg" alt="\">


右上角標有1的是在Android1.5版本添加的,並且在Android2.3之後就無法使用。

右上角標有2的是已經過時的。

很明顯,我們需要用到的方向傳感器TYPE_ORIENTATION就已經過時了,後面再說用什麼來替代它。


最後看一下常用的傳感器的方法:

1.實例化SensorManager

SensorManager mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);

2.獲取設備支持的全部Sensor的List

List deviceSensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);


下面就通過這兩個方法看一下手機支持哪些傳感器,並以列表數據展示出來,代碼很簡單:

package com.example.sensordemo;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends Activity {

	private SensorManager mSensorManager;
	private ListView sensorListView;
	private List sensorList;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		sensorListView = (ListView) findViewById(R.id.lv_all_sensors);
		// 實例化傳感器管理者
		mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
		// 得到設置支持的所有傳感器的List
		sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
		List sensorNameList = new ArrayList();
		for (Sensor sensor : sensorList) {
			sensorNameList.add(sensor.getName());
		}
		ArrayAdapter adapter = new ArrayAdapter(this,
				android.R.layout.simple_list_item_1, sensorNameList);
		sensorListView.setAdapter(adapter);
	}

}

最後看一下真機的效果圖:

\


了解了傳感器的基礎知識,下面我們就具體看看我們需要用到的Orientation Sensor。



三、Orientation Sensor



安卓平台提供了2個傳感器用於讓我們判斷設備的位置,分別是地磁場傳感器(the geomagnetic field sensor)和方向傳感器(the orientation sensor)。關於Orientation Sensor在官方文檔中的概述裡有這樣一句話:

The orientation sensor is software-based and derives its data from the accelerometer and the geomagnetic field sensor. (方向傳感器是基於軟件的,並且它的數據是通過加速度傳感器和磁場傳感器共同獲得的)

至於具體算法Android平台已經封裝好了我們不必去關心實現,下面在了解方向傳感器之前我們還需要了解一個重要的概念:傳感器坐標系統(Sensor Coordinate System)。


在Android平台中,傳感器框架通常是使用一個標准的三維坐標系去表示一個值的。以方向傳感器為例,確定一個方向當然也需要一個三維坐標,畢竟我們的設備不可能永遠水平端著吧,准確的說android給我們返回的方向值就是一個長度為3的float數組,包含三個方向的值。下面看一下官方提供的傳感器API使用的坐標系統示意圖:

\


仔細看一下這張圖,不難發現,z是指向地心的方位角,x軸是仰俯角(由靜止狀態開始前後反轉),y軸是翻轉角(由靜止狀態開始左右反轉)。下面切入正題,看看如何通過方向傳感器API去獲取方向。結合上面的圖再看看官方提供的方向傳感器事件的返回值:

\

這樣就和上面提到的相吻合了,確實是通過一個長度為3的float數組去表示一個位置信息的,並且數組的第一個元素表示方位角(z軸),數組的第二個元素表示仰俯角(x軸),而數組的第三個元素表示翻轉角(y軸),最後看看代碼怎麼寫。


依舊參考官方文檔Using the Orientation Sensor部分內容,首先是實例化一個方向傳感器:

mOrientation = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

雖然這樣做沒錯,但是如果你在IDE中寫了這樣一行代碼的話,不難發現它已經過期了,但是沒關系,我們先用這個看看,後面再介紹代替它的方法。


下面是創建一個自定義傳感器事件監聽接口:

	class MySensorEventListener implements SensorEventListener {

		@Override
		public void onSensorChanged(SensorEvent event) {
			// TODO Auto-generated method stub
			float a = event.values[0];
			azimuthAngle.setText(a + "");
			float b = event.values[1];
			pitchAngle.setText(b + "");
			float c = event.values[2];
			rollAngle.setText(c + "");
		}

		@Override
		public void onAccuracyChanged(Sensor sensor, int accuracy) {
			// TODO Auto-generated method stub

		}

	}

最後通過SensorManager為Sensor注冊監聽即可:

mSensorManager.registerListener(new MySensorEventListener(),
				mOrientation, SensorManager.SENSOR_DELAY_NORMAL);


當設備位置發生變化時觸發監聽,界面上的值改變,由於模擬器無法演示傳感器效果,真機也沒有root沒法用屏幕投影啥的,所以就貼一張截圖象征性看一下,這幾個值無時無刻都在變化:

\

由於我在截圖的時候手機是水平端平的,所以後兩個值都接近於0,而第一個方位角就代表當前的方向了,好了,現在功能基本算實現了,那麼現在就解決一下Sensor類的常量過期的問題。不難發現,在IDE中這行代碼是這樣的:

mOrientation = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

既然過期了必定有新的東西去替代它,我們打開源代碼可以看到這樣的注釋:

\

顯而易見,官方推薦我們用SensorManager.getOrientation()這個方法去替代原來的TYPE_ORITNTATION。那我們繼續在源碼中看看這個方法:

    public static float[] getOrientation(float[] R, float values[]) {
        /*
         * 4x4 (length=16) case:
         *   /  R[ 0]   R[ 1]   R[ 2]   0  \
         *   |  R[ 4]   R[ 5]   R[ 6]   0  |
         *   |  R[ 8]   R[ 9]   R[10]   0  |
         *   \      0       0       0   1  /
         *
         * 3x3 (length=9) case:
         *   /  R[ 0]   R[ 1]   R[ 2]  \
         *   |  R[ 3]   R[ 4]   R[ 5]  |
         *   \  R[ 6]   R[ 7]   R[ 8]  /
         *
         */
        if (R.length == 9) {
            values[0] = (float)Math.atan2(R[1], R[4]);
            values[1] = (float)Math.asin(-R[7]);
            values[2] = (float)Math.atan2(-R[6], R[8]);
        } else {
            values[0] = (float)Math.atan2(R[1], R[5]);
            values[1] = (float)Math.asin(-R[9]);
            values[2] = (float)Math.atan2(-R[8], R[10]);
        }
        return values;
    }

再看一下這個方法的注釋中的一句話:

\

第一行講了這個方法的作用,計算設備的方向基於旋轉矩陣,這個旋轉矩陣我們當成一種計算方向的算法就OK了,不必深究,下面再看我標出來的這句話,很明顯說明了我們通常不需要這個方法的返回值,這個方法會根據參數R[ ]的數據填充values[ ]而後者就是我們想要的。既然不需要返回值,那麼就是參數的問題了,這兩個參數:float[ ] R 和 float[ ] values該怎麼獲取呢?繼續看注釋,首先是第一個參數R:

\

既然這個方法是基於旋轉矩陣去計算方向,那麼第一個參數R自然就表示一個旋轉矩陣了,實際上它是用來保存磁場和加速度的數據的,根據注釋我們可以發現讓我們通過getRotationMatrix這個方法來填充這個參數R[ ],那我們就再去看看這個方法源碼,依舊是SensorManager的一個靜態方法:

    public static boolean getRotationMatrix(float[] R, float[] I,
            float[] gravity, float[] geomagnetic) {
        // TODO: move this to native code for efficiency
        float Ax = gravity[0];
        float Ay = gravity[1];
        float Az = gravity[2];
        final float Ex = geomagnetic[0];
        final float Ey = geomagnetic[1];
        final float Ez = geomagnetic[2];
        float Hx = Ey*Az - Ez*Ay;
        float Hy = Ez*Ax - Ex*Az;
        float Hz = Ex*Ay - Ey*Ax;
        final float normH = (float)Math.sqrt(Hx*Hx + Hy*Hy + Hz*Hz);
        if (normH < 0.1f) {
            // device is close to free fall (or in space?), or close to
            // magnetic north pole. Typical values are  > 100.
            return false;
        }
        final float invH = 1.0f / normH;
        Hx *= invH;
        Hy *= invH;
        Hz *= invH;
        final float invA = 1.0f / (float)Math.sqrt(Ax*Ax + Ay*Ay + Az*Az);
        Ax *= invA;
        Ay *= invA;
        Az *= invA;
        final float Mx = Ay*Hz - Az*Hy;
        final float My = Az*Hx - Ax*Hz;
        final float Mz = Ax*Hy - Ay*Hx;
        if (R != null) {
            if (R.length == 9) {
                R[0] = Hx;     R[1] = Hy;     R[2] = Hz;
                R[3] = Mx;     R[4] = My;     R[5] = Mz;
                R[6] = Ax;     R[7] = Ay;     R[8] = Az;
            } else if (R.length == 16) {
                R[0]  = Hx;    R[1]  = Hy;    R[2]  = Hz;   R[3]  = 0;
                R[4]  = Mx;    R[5]  = My;    R[6]  = Mz;   R[7]  = 0;
                R[8]  = Ax;    R[9]  = Ay;    R[10] = Az;   R[11] = 0;
                R[12] = 0;     R[13] = 0;     R[14] = 0;    R[15] = 1;
            }
        }
        if (I != null) {
            // compute the inclination matrix by projecting the geomagnetic
            // vector onto the Z (gravity) and X (horizontal component
            // of geomagnetic vector) axes.
            final float invE = 1.0f / (float)Math.sqrt(Ex*Ex + Ey*Ey + Ez*Ez);
            final float c = (Ex*Mx + Ey*My + Ez*Mz) * invE;
            final float s = (Ex*Ax + Ey*Ay + Ez*Az) * invE;
            if (I.length == 9) {
                I[0] = 1;     I[1] = 0;     I[2] = 0;
                I[3] = 0;     I[4] = c;     I[5] = s;
                I[6] = 0;     I[7] =-s;     I[8] = c;
            } else if (I.length == 16) {
                I[0] = 1;     I[1] = 0;     I[2] = 0;
                I[4] = 0;     I[5] = c;     I[6] = s;
                I[8] = 0;     I[9] =-s;     I[10]= c;
                I[3] = I[7] = I[11] = I[12] = I[13] = I[14] = 0;
                I[15] = 1;
            }
        }
        return true;
    }

依舊是4個參數,請觀察30~41行之間的代碼,不難發現這個旋轉矩陣無非就是一個3*34*4的數組,再觀察一下if語句塊中的代碼,不難發現給數組元素依次賦值,而這些值是從哪裡來的呢?我們29行倒著看,直到第4行,不難發現其實最後的數據源是通過這個方法的後兩個參數提供的,即:float[] gravity, float[] geomagnetic,老規矩,看看這兩個參數的注釋:

\

到這裡應該就清晰了吧,分別是從加速度傳感器和地磁場傳感器獲取的值,那麼很明顯,應當在監聽中的回調方法onSensorChanged中去獲取數據,同時也驗證了上面的判斷方向需要兩個傳感器的說法,分別是:加速度傳感器(Sensor.TYPE_ACCELEROMETER)和地磁場傳感器(TYPE_MAGNETIC_FIELD)。

說完了getRotationMatrix方法的後兩個參數,那麼前兩個參數RI又該如何定義呢?其實很簡單,第一個參數R就是getOrientation()方法中需要填充的那個數組,大小是9。而第二個參數I是用於將磁場數據轉換進實際的重力坐標系中的,一般默認設置為NULL即可。到這裡關於方向傳感器基本就已經介紹完畢,最後看一個完整的例子:

package com.example.sensordemo;

import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends Activity {

	private SensorManager mSensorManager;

	private Sensor accelerometer; // 加速度傳感器
	private Sensor magnetic; // 地磁場傳感器

	private TextView azimuthAngle;

	private float[] accelerometerValues = new float[3];
	private float[] magneticFieldValues = new float[3];

	private static final String TAG = "---MainActivity";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		// 實例化傳感器管理者
		mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
		// 初始化加速度傳感器
		accelerometer = mSensorManager
				.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		// 初始化地磁場傳感器
		magnetic = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

		azimuthAngle = (TextView) findViewById(R.id.azimuth_angle_value);
		calculateOrientation();
	}

	@Override
	protected void onResume() {
		// TODO Auto-generated method stub
		// 注冊監聽
		mSensorManager.registerListener(new MySensorEventListener(),
				accelerometer, Sensor.TYPE_ACCELEROMETER);
		mSensorManager.registerListener(new MySensorEventListener(), magnetic,
				Sensor.TYPE_MAGNETIC_FIELD);
		super.onResume();
	}

	@Override
	protected void onPause() {
		// TODO Auto-generated method stub
		// 解除注冊
		mSensorManager.unregisterListener(new MySensorEventListener());
		super.onPause();
	}

	// 計算方向
	private void calculateOrientation() {
		float[] values = new float[3];
		float[] R = new float[9];
		SensorManager.getRotationMatrix(R, null, accelerometerValues,
				magneticFieldValues);
		SensorManager.getOrientation(R, values);
		values[0] = (float) Math.toDegrees(values[0]);

		Log.i(TAG, values[0] + "");
		if (values[0] >= -5 && values[0] < 5) {
			azimuthAngle.setText("正北");
		} else if (values[0] >= 5 && values[0] < 85) {
			// Log.i(TAG, "東北");
			azimuthAngle.setText("東北");
		} else if (values[0] >= 85 && values[0] <= 95) {
			// Log.i(TAG, "正東");
			azimuthAngle.setText("正東");
		} else if (values[0] >= 95 && values[0] < 175) {
			// Log.i(TAG, "東南");
			azimuthAngle.setText("東南");
		} else if ((values[0] >= 175 && values[0] <= 180)
				|| (values[0]) >= -180 && values[0] < -175) {
			// Log.i(TAG, "正南");
			azimuthAngle.setText("正南");
		} else if (values[0] >= -175 && values[0] < -95) {
			// Log.i(TAG, "西南");
			azimuthAngle.setText("西南");
		} else if (values[0] >= -95 && values[0] < -85) {
			// Log.i(TAG, "正西");
			azimuthAngle.setText("正西");
		} else if (values[0] >= -85 && values[0] < -5) {
			// Log.i(TAG, "西北");
			azimuthAngle.setText("西北");
		}
	}

	class MySensorEventListener implements SensorEventListener {
		@Override
		public void onSensorChanged(SensorEvent event) {
			// TODO Auto-generated method stub
			if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
				accelerometerValues = event.values;
			}
			if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
				magneticFieldValues = event.values;
			}
			calculateOrientation();
		}

		@Override
		public void onAccuracyChanged(Sensor sensor, int accuracy) {
			// TODO Auto-generated method stub

		}

	}

}



四、總結



第一次研究Android傳感器感覺還是挺難的,繼續努力吧,要學的東西還有很多,先去給Map項目集成指南針啦~


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved