AndroidOrientation Sensor(方向传感器),新的替代方法详解(安卓官方提供)
? ? 本文將帶大家去解讀下安卓官方關(guān)于方向傳感器數(shù)據(jù),提供的新方法。熟悉手機(jī)傳感器開發(fā)的朋友對(duì)這段代碼一定不會(huì)陌生吧。
sm.registerListener(this,
??????? ???? sm.getDefaultSensor(Sensor.TYPE_ORIENTATION),?
??????????? SensorManager.SENSOR_DELAY_NORMAL);
??????? sm.registerListener(this,
??????????? sm.getDefaultSensor(Sensor.TYPE_GRAVITY),
在高版本的安卓中,Sensor.TYPE_ORIENTATION被畫上了橫線,那么我們鼠標(biāo)放上去看下IDE提示是這樣寫的:The fieldSensor.TYPE_ORIENTATION is deprecated,意思是說這個(gè)方法已經(jīng)被棄用了。當(dāng)然隨著版本的更新,老方法被棄用,自然會(huì)有更精確,更高效的方法來代替。那么新方法就是本文要重要介紹的。為了更好解釋,首先從傳感器基礎(chǔ)開始講起。
?
一、傳感器基礎(chǔ)知識(shí)
官方文檔說的很清楚,Android平臺(tái)支持三大類的傳感器,它們分別是:
a.Motion sensors
b.Environmental sensors
c.Position sensors
從另一個(gè)角度劃分,安卓的傳感器又可以分為基于硬件的和基于軟件的。基于硬件的傳感器往往是通過物理組件去實(shí)現(xiàn)的,他們通常是通過去測(cè)量特殊環(huán)境的屬性獲取數(shù)據(jù),比如:重力加速度、地磁場(chǎng)強(qiáng)度或方位角度的變化。而基于軟件的傳感器并不依賴物理設(shè)備,盡管它們是模仿基于硬件的傳感器的。基于軟件的傳感器通常是通過一個(gè)或更多的硬件傳感器獲取數(shù)據(jù),并且有時(shí)會(huì)調(diào)用虛擬傳感器或人工傳感器等等,線性加速度傳感器和重力傳感器就是基于軟件傳感器的例子。下面通過官方的一張圖看看安卓平臺(tái)支持的所有傳感器類型:
使用傳感器的話那么首先需要了解的必然是傳感器API了,在Android中傳感器類是通過Sensor類來表示的,它屬于android.hardware包下的類,顧名思義,和硬件相關(guān)的類。傳感器的API不復(fù)雜,包含3個(gè)類和一個(gè)接口,分別是:
SensorManager
Sensor
SensorEvent
SensorEventListener
根據(jù)官方文檔的概述分別簡(jiǎn)單解釋一下這4個(gè)API的用處:
SensorManager:可以通過這個(gè)類去創(chuàng)建一個(gè)傳感器服務(wù)的實(shí)例,這個(gè)類提供的各種方法可以訪問傳感器列表、注冊(cè)或解除注冊(cè)傳感器事件監(jiān)聽、獲取方位信息等。
Sensor:用于創(chuàng)建一個(gè)特定的傳感器實(shí)例,這個(gè)類提供的方法可以讓你決定一個(gè)傳感器的功能。
SensorEvent:系統(tǒng)會(huì)通過這個(gè)類創(chuàng)建一個(gè)傳感器事件對(duì)象,提供了一個(gè)傳感器的事件信息,包含一下內(nèi)容,原生的傳感器數(shù)據(jù)、觸發(fā)傳感器的事件類型、精確的數(shù)據(jù)以及事件發(fā)生的時(shí)間。
SensorEventListener:可以通過這個(gè)接口創(chuàng)建兩個(gè)回調(diào)用法來接收傳感器的事件通知,比如當(dāng)傳感器的值發(fā)生變化時(shí)。
?
介紹了基礎(chǔ)的分類之后,我們?cè)倏纯磦鞲衅鞯目捎眯员砀?#xff0c;不同的傳感器在不同的Android版本之間是有差異的,比如有的在低版本可以用,但在高版本就被棄用,詳細(xì)的數(shù)據(jù)依然看看官方的這張表格:
右上角標(biāo)有1的是在Android1.5版本添加的,并且在Android2.3之后就無法使用。
右上角標(biāo)有2的是已經(jīng)過時(shí)的。
很明顯,我們需要用到的方向傳感器TYPE_ORIENTATION就已經(jīng)過時(shí)了,后面再說用什么來替代它。
接著講一下如何使用常用的傳感器:
1.???實(shí)例化SensorManager
SensorManager mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);2.???獲取設(shè)備支持的全部Sensor的List
List<Sensor> deviceSensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);3.???下面就通過這兩個(gè)方法看一下手機(jī)支持哪些傳感器,并以列表數(shù)據(jù)展示出來,代碼很簡(jiǎn)單:
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<Sensor> sensorList;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);sensorListView = (ListView) findViewById(R.id.lv_all_sensors);// 實(shí)例化傳感器管理者mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);// 得到設(shè)置支持的所有傳感器的ListsensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);List<String> sensorNameList = new ArrayList<String>();for (Sensor sensor : sensorList) {sensorNameList.add(sensor.getName());}ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, sensorNameList);sensorListView.setAdapter(adapter);}}最后看一下真機(jī)的效果圖:
了解了傳感器的基礎(chǔ)知識(shí),下面就具體到我們要重點(diǎn)講解的Orientation Sensor傳感器。
?
三、OrientationSensor官方給的替代方法
安卓平臺(tái)提供了2個(gè)傳感器用于讓我們判斷設(shè)備的位置,分別是地磁場(chǎng)傳感器(thegeomagnetic field sensor)和方向傳感器(theorientation sensor)。關(guān)于OrientationSensor在官方文檔中的概述里有這樣一句話:
Theorientation sensor is software-based and derives its data from theaccelerometer and the geomagnetic field sensor.(方向傳感器是基于軟件的,并且它的數(shù)據(jù)是通過加速度傳感器和磁場(chǎng)傳感器共同獲得的)
至于具體算法Android平臺(tái)已經(jīng)封裝好了我們不必去關(guān)心實(shí)現(xiàn),下面在了解方向傳感器之前我們還需要了解一個(gè)重要的概念:傳感器坐標(biāo)系統(tǒng)(Sensor?Coordinate System)。
?
在Android平臺(tái)中,傳感器框架通常是使用一個(gè)標(biāo)準(zhǔn)的三維坐標(biāo)系去表示一個(gè)值的。以方向傳感器為例,確定一個(gè)方向當(dāng)然也需要一個(gè)三維坐標(biāo),畢竟我們的設(shè)備不可能永遠(yuǎn)水平端著吧,準(zhǔn)確的說android給我們返回的方向值就是一個(gè)長(zhǎng)度為3的float數(shù)組,包含三個(gè)方向的值。下面看一下官方提供的傳感器API使用的坐標(biāo)系統(tǒng)示意圖:
仔細(xì)看一下這張圖,不難發(fā)現(xiàn),z是指向地心的方位角,x軸是仰俯角(由靜止?fàn)顟B(tài)開始前后反轉(zhuǎn)),y軸是翻轉(zhuǎn)角(由靜止?fàn)顟B(tài)開始左右反轉(zhuǎn))。下面切入正題,看看如何通過方向傳感器API去獲取方向。結(jié)合上面的圖再看看官方提供的方向傳感器事件的返回值:
這樣就和上面提到的相吻合了,確實(shí)是通過一個(gè)長(zhǎng)度為3的float數(shù)組去表示一個(gè)位置信息的,并且數(shù)組的第一個(gè)元素表示方位角(z軸),數(shù)組的第二個(gè)元素表示仰俯角(x軸),而數(shù)組的第三個(gè)元素表示翻轉(zhuǎn)角(y軸),最后看看代碼怎么寫。
?
依舊參考官方文檔Usingthe Orientation Sensor部分內(nèi)容,首先是實(shí)例化一個(gè)方向傳感器:
mOrientation = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);雖然這樣做沒錯(cuò),但是如果你在IDE中寫了這樣一行代碼的話(API低版本可能不會(huì)提示),如文中開頭說的,發(fā)現(xiàn)它已經(jīng)過期了,但是沒關(guān)系,我們先用這個(gè)看看,后面再介紹代替它的方法。
下面是創(chuàng)建一個(gè)自定義傳感器事件監(jiān)聽接口:
class MySensorEventListener implements SensorEventListener {@Overridepublic void onSensorChanged(SensorEvent event) {// TODO Auto-generated method stubfloat a = event.values[0];azimuthAngle.setText(a + "");float b = event.values[1];pitchAngle.setText(b + "");float c = event.values[2];rollAngle.setText(c + "");}@Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) {// TODO Auto-generated method stub}}最后通過SensorManager為Sensor注冊(cè)監(jiān)聽即可:
mSensorManager.registerListener(new MySensorEventListener(),mOrientation, SensorManager.SENSOR_DELAY_NORMAL);當(dāng)設(shè)備位置發(fā)生變化時(shí)觸發(fā)監(jiān)聽,界面上的值改變,由于模擬器無法演示傳感器效果,就用真機(jī)截圖看一下,這幾個(gè)值無時(shí)無刻都在變化:
由于我在截圖的時(shí)候手機(jī)是水平端平的,所以后兩個(gè)值都接近于0,而第一個(gè)方位角就代表當(dāng)前的方向了,好了,現(xiàn)在功能基本算實(shí)現(xiàn)了,那么現(xiàn)在就解決一下Sensor類的常量過期的問題。不難發(fā)現(xiàn),在IDE中這行代碼是這樣的:
mOrientation= mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
既然過期了必定有新的東西去替代它,我們打開源代碼可以看到這樣的注釋:
顯而易見,官方推薦我們用SensorManager.getOrientation()這個(gè)方法去替代原來的TYPE_ORITNTATION。那我們繼續(xù)在源碼中看看這個(gè)方法:
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;}再看一下這個(gè)方法的注釋中的一句話:
第一行講了這個(gè)方法的作用,計(jì)算設(shè)備的方向基于旋轉(zhuǎn)矩陣,這個(gè)旋轉(zhuǎn)矩陣我們當(dāng)成一種計(jì)算方向的算法就OK了,不必深究,下面再看我標(biāo)出來的這句話,很明顯說明了我們通常不需要這個(gè)方法的返回值,這個(gè)方法會(huì)根據(jù)參數(shù)R[ ]的數(shù)據(jù)填充values[ ]而后者就是我們想要的。既然不需要返回值,那么就是參數(shù)的問題了,這兩個(gè)參數(shù):float[ ] R 和 float[ ] values該怎么獲取呢?繼續(xù)看注釋,首先是第一個(gè)參數(shù)R:
既然這個(gè)方法是基于旋轉(zhuǎn)矩陣去計(jì)算方向,那么第一個(gè)參數(shù)R自然就表示一個(gè)旋轉(zhuǎn)矩陣了,實(shí)際上它是用來保存磁場(chǎng)和加速度的數(shù)據(jù)的,根據(jù)注釋我們可以發(fā)現(xiàn)讓我們通過getRotationMatrix這個(gè)方法來填充這個(gè)參數(shù)R[ ],那我們就再去看看這個(gè)方法源碼,依舊是SensorManager的一個(gè)靜態(tài)方法:
public static boolean getRotationMatrix(float[] R, float[] I,float[] gravity, float[] geomagnetic) {// TODO: move this to native code for efficiencyfloat 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個(gè)參數(shù),請(qǐng)觀察30~41行之間的代碼,不難發(fā)現(xiàn)這個(gè)旋轉(zhuǎn)矩陣無非就是一個(gè)3*3或4*4的數(shù)組,再觀察一下if語句塊中的代碼,不難發(fā)現(xiàn)給數(shù)組元素依次賦值,而這些值是從哪里來的呢?我們29行倒著看,直到第4行,不難發(fā)現(xiàn)其實(shí)最后的數(shù)據(jù)源是通過這個(gè)方法的后兩個(gè)參數(shù)提供的,即:float[] gravity, float[]geomagnetic,老規(guī)矩,看看這兩個(gè)參數(shù)的注釋:
到這里應(yīng)該就清晰了吧,分別是從加速度傳感器和地磁場(chǎng)傳感器獲取的值,那么很明顯,應(yīng)當(dāng)在監(jiān)聽中的回調(diào)方法onSensorChanged中去獲取數(shù)據(jù),同時(shí)也驗(yàn)證了上面的判斷方向需要兩個(gè)傳感器的說法,分別是:加速度傳感器(Sensor.TYPE_ACCELEROMETER)和地磁場(chǎng)傳感器(TYPE_MAGNETIC_FIELD)。
說完了getRotationMatrix方法的后兩個(gè)參數(shù),那么前兩個(gè)參數(shù)R和I又該如何定義呢?其實(shí)很簡(jiǎn)單,第一個(gè)參數(shù)R就是getOrientation()方法中需要填充的那個(gè)數(shù)組,大小是9。而第二個(gè)參數(shù)I是用于將磁場(chǎng)數(shù)據(jù)轉(zhuǎn)換進(jìn)實(shí)際的重力坐標(biāo)系中的,一般默認(rèn)設(shè)置為NULL即可。到這里關(guān)于方向傳感器基本就已經(jīng)介紹完畢,最后看一個(gè)完整的例子:
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; // 地磁場(chǎng)傳感器private TextView azimuthAngle;private float[] accelerometerValues = new float[3];private float[] magneticFieldValues = new float[3];private static final String TAG = "---MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 實(shí)例化傳感器管理者mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);// 初始化加速度傳感器accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);// 初始化地磁場(chǎng)傳感器magnetic = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);azimuthAngle = (TextView) findViewById(R.id.azimuth_angle_value);calculateOrientation();}@Overrideprotected void onResume() {// TODO Auto-generated method stub// 注冊(cè)監(jiān)聽mSensorManager.registerListener(new MySensorEventListener(),accelerometer, Sensor.TYPE_ACCELEROMETER);mSensorManager.registerListener(new MySensorEventListener(), magnetic,Sensor.TYPE_MAGNETIC_FIELD);super.onResume();}@Overrideprotected void onPause() {// TODO Auto-generated method stub// 解除注冊(cè)mSensorManager.unregisterListener(new MySensorEventListener());super.onPause();}// 計(jì)算方向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 {@Overridepublic void onSensorChanged(SensorEvent event) {// TODO Auto-generated method stubif (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {accelerometerValues = event.values;}if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {magneticFieldValues = event.values;}calculateOrientation();}@Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) {// TODO Auto-generated method stub}}}四、總結(jié)
本文詳細(xì)介紹了官方提供的方向傳感器新方法,對(duì)于具體的精度和效率的提升,本人沒有進(jìn)一步去比較,希望嘗試過的朋友能和大家一起討論下。后續(xù)工作會(huì)把兩種方法對(duì)比結(jié)果補(bǔ)充進(jìn)來~~~
?
參考:http://www.bkjia.com/Androidjc/924827.html
?
?
總結(jié)
以上是生活随笔為你收集整理的AndroidOrientation Sensor(方向传感器),新的替代方法详解(安卓官方提供)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MATLAB中估算运行时间-tic、to
- 下一篇: 【已解决】Android5.0版本如何打