1. 程式人生 > >梳理一下感測器的資料流和框架是怎麼樣讓螢幕旋轉的。

梳理一下感測器的資料流和框架是怎麼樣讓螢幕旋轉的。

本文章轉載:http://blog.csdn.net/a345017062/article/details/6592527

這篇文章寫的感測器資料從驅動傳遞到應用程式的整個流程,還有資料校正的問題。

應用程式怎麼樣設定可以讓自己隨著裝置的傾斜度變化而旋轉方向呢?在AndroidManifest.xml檔案中的android:screenOrientation就可以了。這裡追蹤一下它的內部機制。
先看一個最關鍵的部件:/frameworks/base/core/java/android/view/WindowOrientationListener.java
這個介面註冊一個accelerator,並負責把accelerator的資料轉化為orientation。這個API對應用程式不公開,我看Android2.3的原始碼時發現只有PhoneWindowManager使用到它了。
/frameworks/base/policy/../PhoneWindowManager.java
PhonwWindowManager註冊了一個WindowOrientationListener,就可以非同步獲取當前裝置的orientation了。再結合應用程式在AndroidManifest.xml中設定的值來管理著應用程式介面的旋轉方向。以下是PhoneWindowManager.java中相關的兩個程式碼片段。

  1. publicvoid onOrientationChanged(int rotation) {  
  2.             // Send updates based on orientation value
  3.             if (localLOGV) Log.v(TAG, "onOrientationChanged, rotation changed to " +rotation);  
  4.             try {  
  5.                 mWindowManager.setRotation(rotation, false,  
  6.                         mFancyRotationAnimation);  
  7.             } catch (RemoteException e) {  
  8.                 // Ignore
  9.             }  
  10.         }  
  11. ... ...  
  12. switch (orientation) {//這個值就是當前裝置螢幕的旋轉方向,再結合應用程式設定的android:configChanges屬性值就可以確定應用程式介面的旋轉方向了。應用程式設定值的優先順序大於感測器確定的優先順序。
  13.                 case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:  
  14.                     //always return portrait if orientation set to portrait
  15.                     return mPortraitRotation;  
  16.                 case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:  
  17.                     //always return landscape if orientation set to landscape
  18.                     return mLandscapeRotation;  
  19.                 case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:  
  20.                     //always return portrait if orientation set to portrait
  21.                     return mUpsideDownRotation;  
  22.                 case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:  
  23.                     //always return seascape if orientation set to reverse landscape
  24.                     return mSeascapeRotation;  
  25.                 case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:  
  26.                     //return either landscape rotation based on the sensor
  27.                     mOrientationListener.setAllow180Rotation(  
  28.                             isLandscapeOrSeascape(Surface.ROTATION_180));  
  29.                     return getCurrentLandscapeRotation(lastRotation);  
  30.                 case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:  
  31.                     mOrientationListener.setAllow180Rotation(  
  32.                             !isLandscapeOrSeascape(Surface.ROTATION_180));  
  33.                     return getCurrentPortraitRotation(lastRotation);  
  34.             }  

讓應用程式隨螢幕方向自動旋轉的實現原理就這麼交待完了。我解決這一步時也沒有費多少力氣,在板子上開啟SensorTest,對比一下XYZ三個軸和MileStone上面的資料,修改一下正負值就可以了。但要解決Teeter執行時Z軸反轉的問題,還得深層次挖一挖。

PhoneWindowManager.java中有這麼一句:
mWindowManager.setRotation(rotation, false, mFancyRotationAnimation);
當PhonewindowManager通過WindowOrientationListener這個監聽器得知螢幕方向改變時,會通知給WindowManagerService(/frameworks/base/service/../WindowManagerService.java)
WindowManagerService中有這麼一個監聽器集合:mRotationWatchers,誰想監聽螢幕方向改變,就會在這裡註冊一個監聽器。SensorManager就這麼幹了。然後,通過下面這個非同步方法獲知當前的螢幕方向
  1. publicvoid onRotationChanged(int rotation) {  
  2.         synchronized(sListeners) {  
  3.             sRotation  = rotation;  
  4.         }  
  5.     }  
  6. staticint getRotation() {  
  7.         synchronized(sListeners) {  
  8.             return sRotation;  
  9.         }  
  10.     }  

SensorManager要這個值有什麼作用呢?看看在哪裡使用了SensorManager.getRotation()吧。只有一個方法:mapSensorDataToWindow
當一個Activity註冊了一個Sensor事件監聽器後,總是會通過介面來非同步獲取sensor事件的。在這裡,新老版本出現了分化。老版本中,Android1.5以前,Sensor事件被分發給監聽者(onSensorChanged)之前,總會先用這個方法處理一下。新版本的監聽介面是SensorEventListener,分發前是沒有處理方法的。看一下這個方法,原來是轉換座標系用的。應用程式的介面方向隨螢幕發生變化以後,通過非同步分發介面傳遞給它的感測器資料也要從感測器的座標系轉換到應用程式的座標系。假設螢幕預設方向是豎屏,這個時候分發給它的SensorEvent裡面的值與frameworks層從HAL的sensor.c中讀到的資料是一樣的。當裝置右側擡起,螢幕切換到橫屏是,應用程式的介面也旋轉了90度,這個時候,SensorEvent在分發給應用程式之前就需要先把自己的座標系順時針旋轉90度。
新版本介面中,感測器資料直接通過SensorEventListener分發給應用程式。而老版本介面中,分發之前先要結合當前裝置的旋轉方向對感測器資料做一個座標系轉換。到這裡,一切都清楚了。WindowOrientationListener藉助SensorManager的accelerator資料製造了螢幕旋轉方向,而螢幕旋轉方向又被SensorManager用來相容老版本的SensorListener介面。可以說,如果不考慮相容老版本的介面的話,SensorManager是完全不用向WindowManagerService.java中註冊監聽器監聽當前裝置螢幕旋轉方向的,直接分發下去就好了。SensorManager的程式碼恐怕要減少一半多。framework層是時候把SensorListener相關的一系列API扔到一邊了。
還有一個地方,就是HAL層的sensor.c到SensorManager之間的部分。這個部分把sensor.c中讀到的感測器資料整合成一個服務(SensorService)供SensorManager使用。很好地隔離了API層和HAL層。但從資料處理的角度來講,只是扮演了一個數據傳遞者的角色,沒有對資料進行任何的改變。
上面的寫完了,接下來是HAL層了。各個廠商的寫法都不一樣,有的為了把所有感測器整合進來,還形成了自己的一個框架。讓我們穿過HAL框架,直接進入sensor.c。這裡有我最關心的最終如何與sensor的driver互動,向上層傳遞了哪些資訊。再複雜的frameworks,也不過是把sensor.c提供的介面封裝一下而己。sensor的資料從來沒有被改變過。這裡只是簡述一下sensor.c的大致功能,更詳細的分析可以參考一下這一篇文章(http://blog.csdn.net/a345017062/article/details/6558401),那裡我寫了一個可以通過ADB或者串列埠執行的C++程式專門演示控制driver和讀取資料的細節。
1、給上層提供一個獲取sensor list的介面。這個是寫死在sensor.c裡面的。往一個裝置上面移植frameworks時,這一部分是要根據裝置上的sensor來修改這個檔案的。
2、給上層提供控制介面:active/deactive某一個sensor。set某個sensor的delay值(即,獲取sensor資料的頻率,比如設定為200,000的話,就是驅動每隔200毫秒向上面發一次資料)。讀取某個sensor的資料。
現在我們知道了,除錯感測器時,只在兩個地方下手就可以了:
1、/frameworks/base/core/java/android/view/WindowOrientation.java,校正accelerator資料與螢幕旋轉方向的對應關係。
2、/hardware/libhardware/modules/sensor/sensor.c,對驅動遞上來的資料進行初步校正。這一步可以參考一下已經的校正好的機器(我用的是自己的MileStone),然後再執行一下SensorTest(網上有的下),只要同一個擺放姿勢下,我們讀上來的資料和它的一樣就可以了。因為前面說過,在新版本(1.5及以上)介面中,資料流經過sensor.c->SensorService->SensorManager,最後通過onSensorChanged分發給應用程式的整個過程中,是從來沒有被改變過的。至於老版本中SensorManager部分做的校正,讓他吃屎去吧。(我一開始在這裡做了很多的工作,最後發現從1.5就已經deprecate這個介面了,請允許我再一次的Shit!!!)。

好了,這篇文章總算是寫完了。別閒我囉嗦,我再說最後一次:sensor資料自從被sensor.c從driver讀上來,一直到傳遞給應用程式的onSensorChanged介面,整個過程中,資料都沒有被改變過。這很重要,因為這意味著frameworks層是不需要sensor校正的。我們只需要在WindowOrientationListener裡面找到那兩個陣列(THRESHOLDS和ROTATE_TO),然後調調螢幕旋轉方向就可以了。

補充於2011.7.25

相容感測器老介面時出現的問題。
螢幕旋轉後,感測器資料也要變的座標系,這和以前的理解不一樣,得糾正一下。
除錯好sensor後,螢幕可以正常旋轉了,但HTC手機自帶的Teeter執行起來有問題。跟蹤了一下,發現Teeter還在使用舊的感測器監聽介面onSensorChanged(int sensor, float[] value)。因此,需要修改/frameworks/base/core/java/android/hardware/SensorManager.java中的mapSensorDataToWindow方法,這個方法負責把HAL讀到的原始資料轉化成舊介面的資料(利用onSensorChanged(int sensor, float[] value)接收到的資料)。
目前只做了0度和90度兩個方向,所以也修改了一下WindowOrientationListener,讓所有API只能在這兩個方向上旋轉:
1、mAllow180Rotation變數永遠設定為false。
2、修改ROTATE_TO陣列,把270度全部修改為90度。

另外,sensor.c中poll data的介面會有一個int型返回值,表示讀到的sensors_event_t的個數,這個值一定要等於實際個數。我自己的程式裡面,實際讀到了一個(accelerator),但返回時不管讀到幾個,都返回當前感測器的個數。這樣的話,在應用程式中用老介面onSensorChanged(int sensor, float[] value)來監聽時,除了一個正常資料之外,還會讀到(sensor.c中的poll data函式返回值-1)個全部為0的冗餘資料。

補充於2011.7.26


通過AndroidManifest.xml設定螢幕方向的話,安裝後就不能改變,而程式內部設定螢幕方向就不會有這個限制。主要靠這兩個API:getRequestedOrientation()和setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
這兩個API通過ActivityManagerService.java的轉換後,實際上都是呼叫的WindowManagerService的同名方法。每個Activity在WindowManagerService端都有一個AppWindowToken做代表,而螢幕的方向資訊就儲存在這裡。

PhoneWindowManager會自動根據螢幕物理特性決定螢幕方向,看這段程式碼:

  1. if (mPortraitRotation < 0) {  
  2.     // Initialize the rotation angles for each orientation once.
  3.     Display d = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))  
  4.             .getDefaultDisplay();  
  5.     if (d.getWidth() > d.getHeight()) {  
  6.         mPortraitRotation = Surface.ROTATION_90;  
  7.         mLandscapeRotation = Surface.ROTATION_0;  
  8.         mUpsideDownRotation = Surface.ROTATION_270;  
  9.         mSeascapeRotation = Surface.ROTATION_180;  
  10.     } else {  
  11.         mPortraitRotation = Surface.ROTATION_0;  
  12.         mLandscapeRotation = Surface.ROTATION_90;  
  13.         mUpsideDownRotation = Surface.ROTATION_180;  
  14.         mSeascapeRotation = Surface.ROTATION_270;  
  15.     }  
  16. }  

這裡的d.getWidth() 和 d.getHeight()得到的是物理螢幕的寬高。一般來說,平板和手機的是不一樣的。平板是寬比高大(0度時位於landscape模式,右轉90度進入porit模式),手機是高比寬大(0度是位於porit模式,右轉90度進入landscape模式)。如果應用程式只關心當前是橫屏還是豎屏,而不直接使用感測器的話,沒什麼問題。如果像依靠重力感應的遊戲那樣直接使用感測器,就需要自己根據物理螢幕的座標系對感測器資料做轉化,否則就會出現座標系混亂的問題。

我這裡碰到的是Range Thunder和Teeter兩個小遊戲。它們都沒有通過上面的d.getWidth()和d.getHeight()來檢測裝置的物理螢幕從確定哪個是landscape和porit模式,而是直接假設裝置是和手機一樣的模式。由於遊戲執行在landscape模式下,它們都把感測器資料右轉90度。這樣做法在手機上是沒有問題,但在平板電腦上是不應該轉化的,這是因為物理螢幕寬比高大的情況下,預設就是landscape模式。

補充於2011.8.2


看到下面一樓讀者提到的問題後,補充一下我針對他說的那個問題的解決方案。
拿新介面來說,我們可以在onSensorChangedLocked介面中,SensorEvent傳遞出去之前,對座標系調整一下。但是,按照這種方法把使用新介面遊戲調整正確後,發現使用老介面的遊戲又亂了,螢幕的旋轉方向也亂了。
我們已經知道,WindowOrientationListener使用SensorManager來確定螢幕的旋轉方向,SensorManager再根據旋轉方向對底層讀上來的感測器做座標系轉換,然後傳遞給onSensorChanged(SensorEvent event)。而老介面onSensorChanged(int sensor,float[] value)是用onSensorChanged(SensorEvent event)的資料再做座標系轉換。
所以,你還需要在老介面中根據新介面中的座標系轉換也做相應地轉換。這樣,使用老介面的遊戲也可以了。但螢幕旋轉不對怎麼辦?這個問題陷入了一個怪圈。好吧,就到這,下面記錄一下我的解決方案:
我們先為SensorEvent增加一個屬性來記錄感測器的原始資料:

  1. /** 
  2.      * 這個註釋一定要加上,要不你編譯時還要先update-api一下。 
  3.      * {@hide} 
  4.      */
  5. float[] originalValue=newfloat[3];  
有三個地方用到它,在onSensorChangedLocked中為新介面做座標系轉換,LegacyListener.onSensorChanged介面中為老介面做坐系轉換,在WiindowOrientationListener中根據感測器資料計算螢幕旋轉方向。
在這三個地方進行計算時都使用originalValue裡面存放的原始資料進行計算,計算結果放到SensorEvent.value中。這樣一來,哪個介面不對調整哪個介面,因為都是使用的原始資料,所以互不影響,再不會出現按下葫蘆起來瓢的事情了。

補充於2011.8.5

不過呢,要是寫遊戲的人比較認真,不是隻簡單地考慮Landscape/Porit模式,而是使用Display.getRotation()來獲取螢幕的旋轉實際角度來做gsensor資料座標系的轉換,那他的程式在我們的板子上就悲劇了:
獲取當前螢幕旋轉角度:

  1. WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);  
  2. Display mDisplay = windowManager.getDefaultDisplay();  
  3. mDisplay.getRotation();  
很不幸,Gallery3D就是這樣來做圖片翻轉特效的。下面程式碼段位於/packages/apps/Gallery3D/src/com/cooliris/media/GridInputProcessor.java檔案中,根據螢幕旋轉角度計算出圖片的傾斜度。只好讓它使用原始資料了,下面分別是修改前和修改後的程式碼。
修改前:
  1. publicvoid onSensorChanged(RenderView view, SensorEvent event, int state) {  
  2.     if (mZoomGesture)  
  3.         return;  
  4.     switch (event.sensor.getType()) {  
  5.     case Sensor.TYPE_ACCELEROMETER:  
  6.         float[] values = event.values;  
  7.         float valueToUse;  
  8.         switch (mDisplay.getRotation()) {  
  9.         case Surface.ROTATION_0:  
  10.             valueToUse = values[0];  
  11.             break;  
  12.         case Surface.ROTATION_90:  
  13.             valueToUse = -event.values[1];  
  14.             break;  
  15.         case Surface.ROTATION_180:  
  16.             valueToUse = -event.values[0];  
  17.             break;  
  18.         case Surface.ROTATION_270:  
  19.             valueToUse =  event.values[1];  
  20.             break;  
  21.         default:  
  22.             valueToUse = 0.0f;  
  23.         }  
  24.  ...  
  25.     }  
  26. }  

修改後的程式碼:
 
  1. publicvoid onSensorChanged(RenderView view, SensorEvent event, int state) {  
  2.      if (mZoomGesture)  
  3.          return;  
  4.      switch (event.sensor.getType()) {  
  5.      case Sensor.TYPE_ACCELEROMETER:  
  6.          float[] values = event.original;  
  7.          float valueToUse;  
  8.          switch (mDisplay.getRotation()) {  
  9.          case Surface.ROTATION_0:  
  10.              valueToUse = values[0];  
  11.              break;  
  12.          case Surface.ROTATION_90:  
  13.              valueToUse = -values[1];  
  14.              break;  
  15.          case Surface.ROTATION_180:  
  16.              valueToUse = -values[0];  
  17.              break;  
  18.          case Surface.ROTATION_270:  
  19.              valueToUse =  values[1];  
  20.              break;  
  21.          default:  
  22.              valueToUse = 0.0f;  
  23.          }  
  24.         ... ...  
  25.      }  
  26.  } 

相關推薦

梳理一下感測器資料框架是怎麼樣螢幕旋轉

本文章轉載:http://blog.csdn.net/a345017062/article/details/6592527 這篇文章寫的感測器資料從驅動傳遞到應用程式的整個流程,還有資料校正的問題。 應用程式怎麼樣設定可以讓自己隨著裝置的傾斜度變化而旋轉方向呢?在Andro

TCP/UDP協議——資料資料

TCP/UDP協議——資料流和資料包 資料流可以分成多個有序的資料包。 TCP傳輸:有連線的資料流服務。tcp提供可靠的傳輸機制,也就是說只要是被髮送的資料都會被接收方接收到,並且雙方也知道被正確接收了。 UDP傳輸:無連線的資料報服務。udp不負責可靠傳輸,他只知道盡最大的努力把資料傳

第四十八講 I/O——常用IO(資料記憶體操作)

資料流 資料流是操作基本資料型別的流,分為資料輸入流和資料輸出流。下面分別來對它們進行介紹。 資料輸入流 概述 資料輸入流DataInputStream允許應用程式以與機器無關方式從底層輸入流中讀取基本Java資料型別。應用程式可以使用資料輸出流寫入稍後由資料輸入流讀取的

Kinect顯示彩色資料深度資料

Kinect顯示彩色資料流和深度資料流 Kinect顯示彩色資料流(當前場景) Kinect顯示深度資料流(將人物扣在背景上) Kinect顯示彩色資料流(當前場景) 1.勾選Computer Color Map; 2.新

資料處理框架介紹

實時流處理簡單概述:實時是說整個流處理相應時間較短,流式技算是說資料是源源不斷的,沒有盡頭的。實時流處理一般是將業務系統產生的資料進行實時收集,交由流處理框架進行資料清洗,統計,入庫,並可以通過視覺化的方式對統計結果進行實時的展示。本文涉及到的框架或技術有 Fl

live555 h264 videostream 資料時間戳的分析

rtsp客戶端傳送播放請求後,rtsp伺服器呼叫流程如下 h264 video rtsp 1.ServerMediaSubsession::startStream -> OnDemandServerMediaSubsession::startStream 2.

C#之資料字串壓縮

.Net自帶的類庫,System.IO.Compress中自帶GZip壓縮。 using System; using System.IO; using System.IO.Compression; using System.Text; public class GZip

系統學習 Java IO (十二)----資料物件 DataInputStream/DataOutputStream & ObjectInputStream/ObjectOutputStream

DataInputStream/DataOutputStream 允許應用程式以與機器無關方式從底層輸入流中讀取基本 Java 資料型別。 要想使用資料輸出流和輸入流,必須按指定的格式儲存資料,才可以將資料輸入流將資料讀取進來,所以通常使用 DataInputStream 來讀取 DataOutputStr

給 Java 開發者的 10 個大資料工具框架

  當今IT開發人員面對的最大挑戰就是複雜性,硬體越來越複雜,OS越來越複雜,程式語言和API越來越複雜,我們構建的應用也越來越複雜。根據外媒的一項調查報告,中軟卓越專家列出了Java程式設計師在過去12個月內一直使用的一些工具或框架,或許會對你有意義。 先來看看大資料的概念。根據維基百科,大資料是龐大或

利用python中的gzip模組壓縮和解壓資料檔案

直接給出原始碼實現, 分為兩種情況: 1.網路連線中的資料流的壓縮和解壓,或是開啟的檔案讀取一部分 2.開啟檔案壓縮或是解壓 #!/usr/bin/env python #encoding: utf-8 #filename: gzip_demo.py #author: [

vue的單向資料雙向繫結解釋

Vue 在不同元件間強制使用單向資料流。這使應用中的資料流更加清晰易懂。資料只能從父元件到子元件或是反向。 雙向繫結v-model是同時實現bind attribute(value)和Listen

java緩衝資料物件

一:緩衝流 1:定義:在記憶體與硬碟之間建立一個大小合適的緩衝區,當記憶體和硬碟進行資料訪問時,能提高訪問硬碟的次數,提高效率。 2:分類:緩衝分為位元組緩衝流(BufferedInputStream和BufferedOutputStream)和字元緩衝流(Buffered

資料處理怎樣才能新興市場發展更好

多年來,大資料已經改變了很多事情,其中之一就是流處理。這就是它產生影響的方式和原因。   隨著當今技術的發展,對流處理的需求也越來越大。例如,必須快速處理資料,以便企業能夠實時跟上不斷變化的業務和市場狀況。這就是實時流處理進入圖片的地方,它可能會改變人們所知道的關於大資料的一切。 &nbs

http 非同步 接收 回傳 資料文字檔案

public void HttpListenerStar() { try { HttpListener httpListener = new HttpListener();

軟體工程--資料資料字典

資料流圖 資料流圖(Data Flow Diagram):簡稱DFD,它從資料傳遞和加工角度,以圖形方式來表達系統的邏輯功能、資料在系統內部的邏輯流向和邏輯變換過程,是結構化系統分析方法的主要表達工具及用於表示軟體模型的一種圖示方法。 基本的圖形符號: 加工中常用的關係符號表示

我對前後端資料模型資料的理解

程式設計源於生活 程式設計是什麼?我們寫的業務程式碼是什麼?它和我們的現實世界有什麼關係? 我之前一直在想這個問題。現在我覺得,程式碼是對現實世界的一種抽象,源於生活又高於生活,他通過資料的方式來抽象現實世界的一些過程,可能是一次商業活動,可能是一次運動的過程等等。 資料是最基礎的東西,資料來源於自動採集的

3.7 Java之列印資料(附字元位元組練習)

列印流 例項 資料流 資料流輸出 資料流輸入 字元位元組流練習 位元組流輸出 字元流輸出

Java內容梳理(3)識別符號資料型別

識別符號 1、識別符號的命名規範 (1)是由26個英文字母(大小寫),數字,下劃線_,$組成; (2)識別符號僅不能以數字開頭,大小寫敏感,識別符號不能以關鍵字和保留字命名 2、程式設計中遇到的識別符號命名處理 (1)包名:全小寫,倒域名,如百度域名:baidu.c

Libnids基礎TCP資料

目錄   本文內容 器材(裝置、元器件) 正文 1.Libnids是什麼?可以幹什麼? 2.ids做軟體開發流程 3. ids主要函式 4.ids實現tcp流程式分析框架 5.使用NIDS庫編寫TCP流分析程式,解釋和輸出三個結構體tuple4,half

[C#原始碼]網路資料讀寫封裝類,支援多執行緒下同時讀寫,自動資源管理,字串分隔符\r\n

using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using Syst