深入探討Android傳感器
對于Java開發(fā)人員來說,Android 平臺是通過使用硬件傳感器創(chuàng)建創(chuàng)新應(yīng)用程序的理想平臺。我們將學(xué)習(xí)一些可用于 Android 應(yīng)用程序的接口連接選項(xiàng),包括使用傳感器子系統(tǒng)和錄制音頻片段。
利用配備Android 的設(shè)備的硬件功能可以構(gòu)建哪些應(yīng)用程序呢?任何需要電子監(jiān)視和監(jiān)聽的應(yīng)用程序都可以構(gòu)建。嬰兒監(jiān)視器、安全系統(tǒng),甚至地震儀都可以。理論上講,您不能同時 出現(xiàn)在兩個地方,但 Android 可以利用一些可行的方法實(shí)現(xiàn)這一點(diǎn)??v觀本文始末,您必須記住,使用的 Android 設(shè)備不僅僅局限于 “手機(jī)”,還可以是部署在固定位置、具有無線網(wǎng)絡(luò)連接的設(shè)備,比如 EDGE 或 WiFi。
使用 Android 平臺有一個很新穎的地方,那就是您可以在設(shè)備內(nèi)部訪問一些 “好工具”。過去,訪問設(shè)備底層硬件的能力一度讓移動開發(fā)人員感到非常棘手。盡管 Android Java 環(huán)境的角色仍然是您和設(shè)備的橋梁,但 Android 開發(fā)團(tuán)隊(duì)讓許多硬件功能浮出了水面。該平臺是一個開源平臺,因此您可以自由地編寫代碼實(shí)現(xiàn)您的任務(wù)。
如果尚未安裝 Android,您可以 下載 Android SDK。您還可以 瀏覽 android.hardware 包的內(nèi)容并參考本文的示例。android.media 包 包含了一些提供有用和新穎功能的類。
Android SDK 中包含的一些面向硬件的功能描述如下。
表 1. Android SDK 中提供的面向硬件的特性
特性 | 描述 |
---|---|
android.hardware.Camera |
允許應(yīng)用程序與相機(jī)交互的類,可以截取照片、獲取預(yù)覽屏幕的圖像,修改用來治理相機(jī)操作的參數(shù)。 |
android.hardware.SensorManager |
允許訪問 Android 平臺傳感器的類。并非所有配備 Android 的設(shè)備都支持 SensorManager 中的所有傳感器,雖然這種可能性讓人非常興奮。(可用傳感器的簡介見下文) |
android.hardware.SensorListener |
在傳感器值實(shí)時更改時,希望接收更新的類要實(shí)現(xiàn)的接口。應(yīng)用程序?qū)崿F(xiàn)該接口來監(jiān)視硬件中一個或多個可用傳感器。例如,本文中的 代碼 包含實(shí)現(xiàn)該接口的類,實(shí)現(xiàn)后可以監(jiān)視設(shè)備的方向和內(nèi)置的加速表。 |
android.media.MediaRecorder |
用于錄制媒體樣例的類,對于錄制特定位置(比如嬰兒保育)的音頻活動非常有用。還可以分析音頻片段以便在訪問控件或安全應(yīng)用程序時進(jìn)行身份鑒定。例如,它可以幫助您通過聲音打開門,以節(jié)省時間,不需要從房產(chǎn)經(jīng)紀(jì)人處獲取鑰匙。 |
android.FaceDetector |
允許對人臉(以位圖形式包含)進(jìn)行基本識別的類。不可能有兩張完全一樣的臉??梢允褂迷擃愖鳛樵O(shè)備鎖定方法,無需記密碼 — 這是手機(jī)的生物特征識別功能。 |
android.os.* | 包含幾個有用類的包,可以與操作環(huán)境交互,包括電源管理、文件查看器、處理器和消息類。和許多可移動設(shè)備一樣,支持 Android 的電話可能會消耗大量電能。讓設(shè)備在正確的時間 “醒來” 以監(jiān)視感興趣的事件是在設(shè)計時需要首先關(guān)注的方面。 |
java.util.Date java.util.Timer java.util.TimerTask |
當(dāng)測量實(shí)際的事件時,數(shù)據(jù)和時間往往很重要。例如,java.util.Date 類允許您在遇到特定的事件或狀況時獲取時間戳。您可以使用 java.util.Timer 和 java.util.TimerTask 分別執(zhí)行周期性任務(wù)或時間點(diǎn)任務(wù)。 |
android.hardware.SensorManager 包含幾個常量,這表示 Android 傳感器系統(tǒng)的不同方面,包括:
傳感器類型
方向、加速表、光線、磁場、臨近性、溫度等。
采樣率
最快、游戲、普通、用戶界面。當(dāng)應(yīng)用程序請求特定的采樣率時,其實(shí)只是對傳感器子系統(tǒng)的一個提示,或者一個建議。不保證特定的采樣率可用。
準(zhǔn)確性
高、低、中、不可靠。
SensorListener 接口是傳感器應(yīng)用程序的中心。它包括兩個必需方法:
onSensorChanged(int sensor,float values[]) 方法在傳感器值更改時調(diào)用。該方法只對受此應(yīng)用程序監(jiān)視的傳感器調(diào)用(更多內(nèi)容見下文)。該方法的參數(shù)包括:一個整數(shù),指示更改的傳感器;一個浮點(diǎn)值數(shù)組,表示傳感器數(shù)據(jù)本身。有些傳感器只提供一個數(shù)據(jù)值,另一些則提供三個浮點(diǎn)值。方向和加速表傳感器都提供三個數(shù)據(jù)值。
當(dāng)傳感器的準(zhǔn)確性更改時,將調(diào)用 onAccuracyChanged(int sensor,int accuracy) 方法。參數(shù)包括兩個整數(shù):一個表示傳感器,另一個表示該傳感器新的準(zhǔn)確值。
要與傳感器交互,應(yīng)用程序必須注冊以偵聽與一個或多個傳感器相關(guān)的活動。注冊使用 SensorManager 類的 registerListener 方法完成。本文中的 代碼示例 演示了如何注冊和注銷 SensorListener。
記住,并非所有支持 Android 的設(shè)備都支持 SDK 中定義的所有傳感器。如果某個傳感器無法在特定的設(shè)備上使用,您的應(yīng)用程序就會適當(dāng)?shù)亟导墶?/p>
樣例應(yīng)用程序僅監(jiān)控對方向和加速表傳感器的更改(源代碼見 下載)。當(dāng)收到更改時,傳感器值在 TextView 小部件的屏幕上顯示。圖 1 展示了該應(yīng)用程序的運(yùn)行情況。
圖 1. 監(jiān)視加速和方向

使用 Eclipse 環(huán)境和 Android Developer Tools 插件創(chuàng)建的應(yīng)用程序。(關(guān)于使用 Eclipse 開發(fā) Android 應(yīng)用程序的信息,請參見 參考資料。)清單 1 展示了該應(yīng)用程序的代碼。
清單 1. IBMEyes.java
- package com.msi.ibm.eyes;
- import android.app.Activity;
- import android.os.Bundle;
- import android.util.Log;
- import android.widget.TextView;
- import android.hardware.SensorManager;
- import android.hardware.SensorListener;
- public class IBMEyes extends Activity implements SensorListener {
- final String tag = "IBMEyes";
- SensorManager sm = null;
- TextView xViewA = null;
- TextView yViewA = null;
- TextView zViewA = null;
- TextView xViewO = null;
- TextView yViewO = null;
- TextView zViewO = null;
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // get reference to SensorManager
- sm = (SensorManager) getSystemService(SENSOR_SERVICE);
- setContentView(R.layout.main);
- xViewA = (TextView) findViewById(R.id.xbox);
- yViewA = (TextView) findViewById(R.id.ybox);
- zViewA = (TextView) findViewById(R.id.zbox);
- xViewO = (TextView) findViewById(R.id.xboxo);
- yViewO = (TextView) findViewById(R.id.yboxo);
- zViewO = (TextView) findViewById(R.id.zboxo);
- }
- public void onSensorChanged(int sensor, float[] values) {
- synchronized (this) {
- Log.d(tag, "onSensorChanged: " + sensor + ", x: " +
- values[0] + ", y: " + values[1] + ", z: " + values[2]);
- if (sensor == SensorManager.SENSOR_ORIENTATION) {
- xViewO.setText("Orientation X: " + values[0]);
- yViewO.setText("Orientation Y: " + values[1]);
- zViewO.setText("Orientation Z: " + values[2]);
- }
- if (sensor == SensorManager.SENSOR_ACCELEROMETER) {
- xViewA.setText("Accel X: " + values[0]);
- yViewA.setText("Accel Y: " + values[1]);
- zViewA.setText("Accel Z: " + values[2]);
- }
- }
- }
- public void onAccuracyChanged(int sensor, int accuracy) {
- Log.d(tag,"onAccuracyChanged: " + sensor + ", accuracy: " + accuracy);
- }
- @Override
- protected void onResume() {
- super.onResume();
- // register this class as a listener for the orientation and accelerometer sensors
- sm.registerListener(this,
- SensorManager.SENSOR_ORIENTATION |SensorManager.SENSOR_ACCELEROMETER,
- SensorManager.SENSOR_DELAY_NORMAL);
- }
- @Override
- protected void onStop() {
- // unregister listener
- sm.unregisterListener(this);
- super.onStop();
- }
- }
編寫應(yīng)用程序必須基于常見的活動,因?yàn)樗皇抢脧膫鞲衅鳙@取的數(shù)據(jù)更新屏幕。在設(shè)備可能在前臺執(zhí)行其他活動的應(yīng)用程序中,將應(yīng)用程序構(gòu)建為服務(wù)可能更加合適。
該活動的 onCreate 方法可以引用 SensorManager,其中包含所有與傳感器有關(guān)的函數(shù)。onCreate 方法還建立了對 6 個 TextView 小部件的引用,您需要使用傳感器數(shù)據(jù)值更新這些小部件。
onResume() 方法使用對 SensorManager 的引用通過 registerListener 方法注冊傳感器更新:
第一個參數(shù)是實(shí)現(xiàn) SensorListener 接口的類的實(shí)例。
第二個參數(shù)是所需傳感器的位掩碼。在本例中,應(yīng)用程序從 SENSOR_ORIENTATION 和 SENSOR_ACCELEROMETER 請求數(shù)據(jù)。
第三個參數(shù)是一個系統(tǒng)提示,指出應(yīng)用程序更新傳感器值所需的速度。
應(yīng)用程序(活動)暫停后,需要注銷偵聽器,這樣以后就不會再收到傳感器更新。這通過 SensorManager 的 unregisterListener 方法實(shí)現(xiàn)。惟一的參數(shù)是 SensorListener 的實(shí)例。
在 registerListener 和 unregisterListener 方法調(diào)用中,應(yīng)用程序使用關(guān)鍵字 this。注意類定義中的 implements 關(guān)鍵字,其中聲明了該類實(shí)現(xiàn) SensorListener 接口。這就是要將它傳遞到 registerListener 和 unregisterListener 的原因。
SensorListener 必須實(shí)現(xiàn)兩個方法 onSensorChange 和 onAccuracyChanged。示例應(yīng)用程序不關(guān)心傳感器的準(zhǔn)確度,但關(guān)注傳感器當(dāng)前的 X、Y 和 Z 值。onAccuracyChanged 方法實(shí)質(zhì)上不執(zhí)行任何操作;它只在每次調(diào)用時添加一個日志項(xiàng)。
似乎經(jīng)常需要調(diào)用 onSensorChanged 方法,因?yàn)榧铀俦砗头较騻鞲衅髡诳焖侔l(fā)送數(shù)據(jù)。查看第一個參數(shù)確定哪個傳感器在發(fā)送數(shù)據(jù)。確認(rèn)了發(fā)送數(shù)據(jù)的傳感器之后,將使用方法第二個參數(shù)傳遞的浮點(diǎn) 值數(shù)組中所包含的數(shù)據(jù)更新相應(yīng)的 UI 元素。該示例只是顯示這些值,但在更加高級的應(yīng)用程序中,還可以分析這些值,比較原來的值,或者設(shè)置某種模式識別算法來確定用戶(或外部環(huán)境)的行為。
現(xiàn)在您已經(jīng)了解了傳感器子系統(tǒng),接下來的部分將回顧一個在 Android 手機(jī)上錄制音頻的代碼樣例。該樣例運(yùn)行在 DEV1 開發(fā)設(shè)備上。
使用 MediaRecorder
android.media 包包含與媒體子系統(tǒng)交互的類。使用 android.media.MediaRecorder 類進(jìn)行媒體采樣,包括音頻和視頻。MediaRecorder 作為狀態(tài)機(jī)運(yùn)行。您需要設(shè)置不同的參數(shù),比如源設(shè)備和格式。設(shè)置后,可執(zhí)行任何時間長度的錄制,直到用戶停止。
清單 2 包含的代碼在 Android 設(shè)備上錄制音頻。顯示的代碼不包括應(yīng)用程序的 UI 元素(完整源代碼見 下載)。
清單 2. 錄制音頻片段
- MediaRecorder mrec ;
- File audiofile = null;
- private static final String TAG="SoundRecordingDemo";
- protected void startRecording() throws IOException
- {
- mrec.setAudioSource(MediaRecorder.AudioSource.MIC);
- mrec.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
- mrec.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
- if (mSampleFile == null)
- {
- File sampleDir = Environment.getExternalStorageDirectory();
- try
- {
- audiofile = File.createTempFile("ibm", ".3gp", sampleDir);
- }
- catch (IOException e)
- {
- Log.e(TAG,"sdcard access error");
- return;
- }
- }
- mrec.setOutputFile(audiofile.getAbsolutePath());
- mrec.prepare();
- mrec.start();
- }
- protected void stopRecording()
- {
- mrec.stop();
- mrec.release();
- processaudiofile(audiofile.getAbsolutePath());
- }
- protected void processaudiofile()
- {
- ContentValues values = new ContentValues(3);
- long current = System.currentTimeMillis();
- values.put(MediaStore.Audio.Media.TITLE, "audio" + audiofile.getName());
- values.put(MediaStore.Audio.Media.DATE_ADDED, (int) (current / 1000));
- values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/3gpp");
- values.put(MediaStore.Audio.Media.DATA, audiofile.getAbsolutePath());
- ContentResolver contentResolver = getContentResolver();
- Uri base = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
- Uri newUri = contentResolver.insert(base, values);
- sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, newUri));
- }
在 startRecording 方法中,實(shí)例化并初始化 MediaRecorder 的實(shí)例:
輸入源被設(shè)置為麥克風(fēng)(MIC)。
輸出格式被設(shè)置為 3GPP(*.3gp 文件),這是移動設(shè)備專用的媒體格式。
編碼器被設(shè)置為 AMR_NB,這是音頻格式,采樣率為 8 KHz。NB 表示窄頻。SDK 文檔 解釋了不同的數(shù)據(jù)格式和可用的編碼器。
音頻文件存儲在存儲卡而不是內(nèi)存中。External.getExternalStorageDirectory() 返回存儲卡位置的名稱,在該目錄中將創(chuàng)建一個臨時文件名。然后,通過調(diào)用 setOutputFile 方法將文件關(guān)聯(lián)到 MediaRecorder 實(shí)例。音頻數(shù)據(jù)將存儲到該文件中。
調(diào)用 prepare 方法完成 MediaRecorder 的初始化。準(zhǔn)備開始錄制流程時,將調(diào)用 start 方法。在調(diào)用 stop 方法之前,將對存儲卡上的文件進(jìn)行錄制。release 方法將釋放分配給 MediaRecorder 實(shí)例的資源。
音頻采樣完成之后,需要采取以下步驟:
向設(shè)備的媒體庫添加該音頻。
執(zhí)行一些模式識別步驟確定聲音:
- 這是嬰兒的啼哭聲嗎?
- 這是所有人的聲音嗎?是否要解鎖手機(jī)?
- 這是 “芝麻開門” 嗎?是否要打開通往 “秘密通道” 的大門?
自動將音頻文件上傳到網(wǎng)絡(luò)位置以便處理。
在該代碼樣例中,processaudiofile 方法將音頻添加到媒體庫。使用 Intent 通知設(shè)備上的媒體應(yīng)用程序有新內(nèi)容可用。
關(guān)于該代碼片段最后要注意的是:如果您試用,它一開始不會錄制音頻。您將看到創(chuàng)建的文件,但是沒有任何音頻。您需要向 AndroidManifest.xml 文件添加權(quán)限:
- <uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission>
現(xiàn)在,您已經(jīng)學(xué)了一點(diǎn)關(guān)于與 Android 傳感器和錄制音頻相關(guān)的內(nèi)容。下一節(jié)將更全面的介紹與數(shù)據(jù)采集和報告系統(tǒng)有關(guān)的應(yīng)用程序架構(gòu)。
Android 作為傳感器平臺
Android 平臺包含各種用于監(jiān)視環(huán)境的傳感器選項(xiàng)。有了輸入或模擬選項(xiàng)數(shù)組,以及高級計算和互聯(lián)功能,Android 成為構(gòu)建實(shí)際系統(tǒng)的最佳平臺。圖 2 顯示了輸入、應(yīng)用程序邏輯、通知方法或輸出之間的簡單視圖。
圖 2. 以 Android 為中心的傳感器系統(tǒng)的方塊圖

該架構(gòu)很靈活;應(yīng)用程序邏輯可以劃分為本地 Android 設(shè)備和服務(wù)器端資源(可以實(shí)現(xiàn)更大的數(shù)據(jù)庫和計算功能)。例如,本地 Android 設(shè)備上錄制的音軌可以 POST 到 Web 服務(wù)器,其中將根據(jù)音頻模式數(shù)據(jù)庫比較數(shù)據(jù)。很明顯,這僅僅是冰山一角。希望您能更深入地研究,讓 Android 平臺超越移動電話的范疇。
結(jié)束語
在本文中,我們介紹了 Android 傳感器。樣例應(yīng)用程序度量了方向和加速,以及使用 MediaRecorder 類與錄制功能進(jìn)行交互。對于構(gòu)建實(shí)際系統(tǒng),Android 是一個靈活、有吸引力的平臺。Android 領(lǐng)域發(fā)展迅速,并且不斷壯大。請務(wù)必關(guān)注該平臺。