<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                根據前文對Android平臺中位置管理相關模塊的介紹可知,LocationManagerService(LMS)是系統模塊的核心,我們先來看它的初始化。 **1、LMS初始化** 系統啟動時,LMS將由SystemServer創建,其初始化函數為systemReady,代碼如下所示。 **LocationManagerService.java::systemReady** ~~~ public void systemReady() { // 創建一個工作線程,其線程函數為LocationManagerService中的run函數 Thread thread = new Thread(null, this, THREAD_NAME); thread.start(); } // 直接來看LMS的run函數 public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Looper.prepare(); // 創建一個LocationWorkerHandler,其主要工作為將LP投遞的位置信息返回給客戶端 mLocationHandler = new LocationWorkerHandler(); init();// 重要的初始化函數 Looper.loop(); } ~~~ LMS的主要初始化工作都集中在init中,該函數的代碼如下所示。 **LocationManagerService.java::init** ~~~ private void init() { ......// 創建Wakelock等工作。關于Wakelock的工作原理,可閱讀《深入理解Android:卷Ⅱ》第5章 // 系統有一個黑白名單用于禁止使用某些特定的LP。在黑白名單中,LP由其對應的Java包名指定 mBlacklist = new LocationBlacklist(mContext, mLocationHandler); mBlacklist.init(); /* LocationFudger很有意思,Android平臺提供粗細兩種精度的位置信息。其中,粗精度的位置信息由下面這個 LocationFudger根據細精度的位置信息進行一定數學模糊處理后得到。粗精度默認的最大值為2000米, 最小值為200米。廠商可在Settings.db數據庫secure表的“locationCoarseAccuracy”設定。 本章不討論LocationFudger的內容,請感興趣的讀者自行研究。 */ mLocationFudger = new LocationFudger(mContext, mLocationHandler); synchronized (mLock) { loadProvidersLocked(); // 關鍵函數:創建或加載系統中的LP,下節將詳細分析它 } /* GeofenceManager為地理圍欄管理對象。Android平臺中其作用如下。 客戶端可以設置一個地理圍欄,該地理圍欄的范圍是一個以客戶端設置的某個點(指定其經/緯度)為中心 的圓,圓的半徑也由客戶端設置。當設備進入或離開該地理圍欄所覆蓋的圓時,LMS將通過相關回調函 數通知客戶端。目前Android 4.2中還沒有在SDK中公開地理圍欄相關的API。有需要使用該功能 的程序可通過Java放射機制調用LocationManager的addGeofence函數。 GeofenceManager比較簡單,感興趣的讀者也可自行閱讀。 */ mGeofenceManager = new GeofenceManager(mContext, mBlacklist); ......// 監聽APK安裝\卸載等廣播事件以及監聽用戶切換事件 /* 根據Settings中的設置情況來開啟或禁止某個LP。在此函數中,各LP實現的 LocationProviderInterface接口的enable/disable函數將被調用。 */ updateProvidersLocked(); } ~~~ init函數的內容比較多,本節重點關注其中的loadProvidersLocked函數。 >[info] 提示 讀者學完本章后不妨研究LocationFudger和GeofenceManager,其內容比較有意思。 **①、loadProvidersLocked流程** loadProvidersLocked用于創建及加載系統中所有的LocationProvider,其代碼如下所示。 **LocationManagerService.java::loadProvidersLocked** ~~~ private void loadProvidersLocked() { /* 先創建PassiveProvider。該LP名稱中的Passive(譯為“被動”)所對應的場景比較有意思,此 處舉一個例子。假設應用程序A使用GpsLP。GpsLP檢測到位置更新后將通知應用程序A。應用程序B 如果使用PassiveProvider,當GpsLP更新位置后,它也會觸發PassiveProvider以通知應用程 序B。也就是說,PassiveProvider自己并不能更新位置信息,而是靠其他LP來觸發位置更新的。 特別注意,PassiveProvider的位置更新是由LMS接收到其他LP的位置更新通知后主動調用 PassiveProvider的updateLocation函數來完成的。 目前,PassiveProvider被SystemServer中的TwilightService使用,TwilightService 將根據位置信息來計算當前時間是白天還是夜晚(Twilight有“黎明”之意)①。 另外,GpsLP也會使用PassiveProvider來接收NeworkLP的位置信息。 PassiveProvider非常簡單,請讀者在學完本章基礎后再自行研究它。 */ PassiveProvider passiveProvider = new PassiveProvider(this); // LMS將保存所有的LP addProviderLocked(passiveProvider); // PassiveProvider永遠處于啟用狀態。mEnabledProviders用于保存那些被啟用的LP mEnabledProviders.add(passiveProvider.getName()); mPassiveProvider = passiveProvider; if (GpsLocationProvider.isSupported()) { // 創建GpsLP實例,我們將用兩節來介紹 GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this); mGpsStatusProvider = gpsProvider.getGpsStatusProvider(); mNetInitiatedListener = gpsProvider.getNetInitiatedListener(); addProviderLocked(gpsProvider);// 保存GpsLP // GpsLP屬于真實的位置提供者,所以把它單獨保存在mRealProvider中 mRealProviders.put(LocationManager.GPS_PROVIDER, gpsProvider); } Resources resources = mContext.getResources(); ArrayList<String> providerPackageNames = new ArrayList<String>(); /* config_locationProviderPackageNames存儲了第三方LP的Java包名。Android原生代碼中該值 只有一個,為“com.android.location.fused”。FusedLP對應的源碼路徑為frameworks/base/ packages/FusedLocation。 */ String[] pkgs = resources.getStringArray( com.android.internal.R.array.config_locationProviderPackageNames); if (pkgs != null) providerPackageNames.addAll(Arrays.asList(pkgs)); /* 加載應用程序實現的LP服務時,LMS將檢查它們的簽名信息以及版本信息。筆者研究了這部分代碼, 感覺其目的可能是想提供一種數據一致性的保護機制,舉個例子。 筆者的Galaxy Note 2中默認安裝的是百度提供的NetworkLocation_baidu.apk, 筆者安裝Google的NetworkLocation.apk時,由于二者簽名不一致(根據LMS相關代碼工的作原理可 知,百度的NetworkLP屬于config_locationProviderPackageNames指定的,其簽名信息將被保存。 后續再安裝的LP將檢查其簽名是否和之前保存的簽名信息是否一致。也就是說,后續的LP必須使用 NetworkLocation_Baidu或FusedLP相同的簽名才能被LMS加載。而FusedLP屬于Android原生 應用,一般由手機廠商提供,第三方應用程序不太可能拿到其簽名),根據上述信息可知,只有百度旗 下或得到它授權的第三方LP才能在筆者的Galaxy Note 2中安裝和使用。 簽名檢查這部分代碼在下文介紹的LocationProviderProxy中也有,讀者僅了解其目的即可。 ensureFallbackFusedProviderPresentLocked用來檢查FusedLP的簽名和版本信息。 */ ensureFallbackFusedProviderPresentLocked(providerPackageNames); /* 加載NetworkLP,其中參數如下。 NETWORK_PROVIDER:類型為字符串,值為“network”。 NETWORK_LOCATION_SERVICE_ACTION:類型為字符串,值為 “com.android.location.service.v2.NetworkLocationProvider”, 它代表應用程序所實現的LP服務名,其作用見下節關于LocationProviderProxy的介紹。 注意,和Android 4.1比起來,Android 4.2 LMS相關的代碼變化較大, 下文將介紹LocationProviderProxy的工作流程。 */ LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( mContext,LocationManager.NETWORK_PROVIDER, NETWORK_LOCATION_SERVICE_ACTION, providerPackageNames, mLocationHandler, mCurrentUserId); if (networkProvider != null) { // NetworkLP也屬于真實的LP mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProvider); // 應用程序實現的LP保存在mProxyProviders變量中 mProxyProviders.add(networkProvider); addProviderLocked(networkProvider); } ...... // 加載FusedLP LocationProviderProxy fusedLocationProvider = LocationProviderProxy.createAndBind( mContext, LocationManager.FUSED_PROVIDER, FUSED_LOCATION_SERVICE_ACTION, providerPackageNames, mLocationHandler, mCurrentUserId); if (fusedLocationProvider != null) { addProviderLocked(fusedLocationProvider); mProxyProviders.add(fusedLocationProvider); // FusedLP默認處于啟用的狀態 mEnabledProviders.add(fusedLocationProvider.getName()); mRealProviders.put(LocationManager.FUSED_PROVIDER, fusedLocationProvider); } ...... // 創建Geocoder。GeocoderProxy的工作流程和LocationProviderProxy類似 mGeocodeProvider = GeocoderProxy.createAndBind(mContext, providerPackageNames, mCurrentUserId); ...... } ~~~ 在LMS的初始化函數中,loadProvidersLocked用于創建和加載系統中所有的LP如下。 * PassiveProvider:提供被動式的位置數據更新服務,其位置數據來源于其他的LP。 * GpsLocationProvider:由LMS創建并加載,運行在LMS所在的進程system_process中,屬于系統提供的LP服務。 * NetworkLocationProvider:該LP服務由應用程序提供。 * FusedLocationProvider:由FusedLocation.apk服務,屬于系統提供的應用程序。其內部將使用其他的LP。 * GeocodeProvider:由第三方應用程序提供。一般和NetworkLP位于同一個應用程序中。 對于本章來說,GpsLP是重中之重。不過在介紹它之前,我們先來看看LMS是如何與位于應用進程中的其他LP交互的。 **②、LocationProviderProxy介紹** 由loadProvidersLocked的代碼可知,LMS通過LocationProviderProxy(簡稱LPProxy)來加載應用進程中的LP。相關函數是LPProxy的createAndBind,代碼如下所示。 **LocationProviderProxy.java::createAndBind** ~~~ public static LocationProviderProxy createAndBind(Context context, String name, String action, List<String> initialPackageNames, Handler handler, int userId) { // 創建一個LPProxy對象 LocationProviderProxy proxy = new LocationProviderProxy(context, name, action,initialPackageNames, handler, userId); if (proxy.bind()) return proxy; else return null; } ~~~ LPProxy的createAndBind中有兩個關鍵函數,分別是LPProxy的構造函數以及bind。我們先來看其構造函數,代碼如下所示。 **LocationProviderProxy.java::LocationProviderProxy** ~~~ private LocationProviderProxy(Context context, String name, String action, List<String> initialPackageNames, Handler handler, int userId) { mContext = context; mName = name; /* ServiceWatcher是LPProxy中最重要的對象。在Android LM架構中,應用程序實現的LP服務都 通過Android四大組件中的Service提供。ServiceWatcher就是LPProxy中用來連接和監視應用程序 實現的LP服務的。 */ mServiceWatcher = new ServiceWatcher(mContext, TAG, action, initialPackageNames, mNewServiceWork, handler, userId); } ~~~ 在createAndBind函數的最后,LPProxy將調用bind函數,而這個bind將觸發ServiceWatcher的start被調用,我們直接來看它。 **ServiceWatcher.java::start** ~~~ public boolean start() { synchronized (mLock) { // bindBestPackageLocked見下文解釋 if (!bindBestPackageLocked(null)) return false; } // 監聽應用程序安裝、卸載、更新等廣播事件 mPackageMonitor.register(mContext, null, UserHandle.ALL, true); return true; } ~~~ bindBestPackageLocked的工作包括如下。 * 檢查目標應用程序的簽名。 * 根據createAndBind第三個參數查找該目標應用程序實現的Service(Android四大組件之一)。以NetworkLP為例,目標應用程序必須提供一個名為"com.android.location.service.v2.NetworkLocationProvider"的服務。 * 然后綁定到該服務上,并獲取一個類型為ILocationProvider接口的實例。通過該實例,LPProxy可與位于應用程序中的LP服務交互。 bindBestPackageLocked中最重要的函數是bindToPackageLocked,其代碼如下所示。 **ServiceWatcher.java::bindToPackageLocked** ~~~ private void bindToPackageLocked(String packageName, int version) { unbindLocked(); Intent intent = new Intent(mAction); intent.setPackage(packageName); mPackageName = packageName; mVersion = version; // 綁定到應用程序的LP Servic mContext.bindService(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE, mCurrentUserId); } ~~~ 綁定成功后,ServiceWathcer的onServiceConnected函數將被調用。 **ServiceWatcher.java::onServiceConnected** ~~~ public void onServiceConnected(ComponentName name, IBinder binder) { synchronized (mLock) { String packageName = name.getPackageName(); if (packageName.equals(mPackageName)) { mBinder = binder; if (mHandler !=null && mNewServiceWork != null) mHandler.post(mNewServiceWork);// mNewServiceWork由LPProxy提供 } ...... } } ~~~ mNewServiceWork是一個Runnable對象,它由LPProxy提供,其代碼如下所示。 **LocationProviderProxy.java::Runnable** ~~~ private Runnable mNewServiceWork = new Runnable() { public void run() { boolean enabled; ProviderProperties properties = null; ProviderRequest request; WorkSource source; ILocationProvider service; synchronized (mLock) { enabled = mEnabled; request = mRequest; source = mWorksource; // 返回ILocationProvider接口實例,它可和應用程序中LP交互 service = getService(); } try { /* 獲取LP的屬性信息,這些屬性統一封裝在類型為ProviderProperties的對象中,LP的屬性 由ProviderProperties的成員變量表示,這些變量如下。 mRequiresNetwork:是否需要使用網絡。 mRequiresSatellite:是否需要使用衛星。 mRequiresCell:是否需要使用基站。 mHasMonetaryCost:是否需要計費。 mSupportsAltitude:是否能提供海拔高度信息。 mSupportsSpeed:是否提供速度信息。 mSupportsBearing:是否支持方位信息。 mPowerRequirement:耗電量級別,可取值有Criteria類定義的HIGH、MEDIUM和LOW三種級別。 mAccuracy:精度級別,可取值有Criteria類定義的COARSE、FINE、HIGH、LOW四種級別。 */ properties = service.getProperties(); ...... if (enabled) { service.enable();// 啟動這個LP if (request != null) {// 如果客戶端有請求的話,則將該請求發送給LP service.setRequest(request, source); } } }catch ...... synchronized (mLock) { mProperties = properties; } } }; ~~~ 至此,LMS的初始化工作暫告一段落,下面來看看GpsLocationProvider的創建。 **③、GpsLocationProvider初始化** GpsLocationProvider類本身有一段初始化代碼,如下所示。 **GpsLocationProvider.java::static語句** ~~~ // GpsLP定義了一些native函數,此處的class_init_native將初始化相關JNI方法。后文介紹 static { class_init_native(); } ~~~ 接著來看GpsLP的創建,其代碼如下所示。 **GpsLocationProvider.java::GpsLocationProvider** ~~~ public GpsLocationProvider(Context context, ILocationManager ilocationManager) { mContext = context; /* NTP為Network Time Protocol之意,它是一種用來同步計算機時間的協議。該時間的源是UTC。 在下面這行代碼中,GpsLP將創建一個NtpTrustedTime對象,該對象將采用SNTP(Simple NTP)協議 來和指定NTP服務器通信以獲取準確的時間。Android平臺中,NTP服務器可在兩個地方設置。 1)在系統資源文件中設置,由字符串config_ntpServer表示,默認值為“2.android.pool.ntp.org”。  請求處理的超時時間由整型參數config_ntpTimeout控制,默認值為20000ms。 2)也可在Settings的數據庫中設置,對應的控制選項為“ntp_server”和“ntp_timeout”。 NtpTrustedTime優先使用Settings設置的信息。 NtpTrustedTime比較簡單,對NTP感興趣的讀者見參考資料[31]。 */ mNtpTime = NtpTrustedTime.getInstance(context); mILocationManager = ilocationManager; // GpsNetInitiatedHandler和ULP Network Initiated請求有關 // 主要處理來自GPS HAL層通知的NI(Network Initiated)事件 mNIHandler = new GpsNetInitiatedHandler(context); mLocation.setExtras(mLocationExtras); ...... mConnMgr = (ConnectivityManager)context.getSystemService( Context.CONNECTIVITY_SERVICE); mProperties = new Properties(); try { /* 讀取GPS配置文件,PROPERTIES_FILE的位置為“/etc/gps.conf”。 筆者的Galaxy Note中,該文件的內容如下所示,其中“#”后的內容為筆者添加的注釋。 NTP_SERVER=north-america.pool.ntp.org #指定NTP_SERVER #下面這幾個參數指定AGPS LTO(Long Term Orbits)數據的下載地址。LTO存儲了GPS衛星 #的星歷數據。從下面這些地址中的“4day”來看,這些數據的有效期為4天。AGPS使用時需要 #先從服務器上下載一些輔助數據。如果周圍沒有網絡的情況下該怎么辦呢? LTO就好比離線地圖, #當周圍沒有網絡時,終端可以利用事先保存的LTO數據來初始化GPS的定位。當然,LTO有時 #效限制,一般是4天。 XTRA_SERVER_1=http://gllto.glpals.com/4day/glo/v2/latest/lto2.dat XTRA_SERVER_2=http://gllto.glpals.com/4day/glo/v2/latest/lto2.dat XTRA_SERVER_3=http://gllto.glpals.com/4day/glo/v2/latest/lto2.dat SUPL_HOST=supl.google.com #指定SUPL的主機和端口 SUPL_PORT=7276 關于LTO,見參考資料[32]。 */ File file = new File(PROPERTIES_FILE); FileInputStream stream = new FileInputStream(file); mProperties.load(stream);// 解析該配置文件 stream.close(); // 獲取配置文件中設置的SUPL主機地址以及端口號 mSuplServerHost = mProperties.getProperty("SUPL_HOST"); String portString = mProperties.getProperty("SUPL_PORT"); if (mSuplServerHost != null && portString != null) { mSuplServerPort = Integer.parseInt(portString); ...... } // C2K是CDMA2000的縮寫。C2K_HOST和C2K_PORT主要用于GPS模塊的測試 // 對用戶來說,這兩個參數沒有太大的意義② mC2KServerHost = mProperties.getProperty("C2K_HOST"); portString = mProperties.getProperty("C2K_PORT"); if (mC2KServerHost != null && portString != null) { mC2KServerPort = Integer.parseInt(portString); ...... } }...... mHandler = new ProviderHandler(); // SUPL的初始化可以由兩種特殊的短信觸發,下文將簡單介紹listenForBroadcasts這個函數 listenForBroadcasts(); mHandler.post(new Runnable() { public void run() { LocationManager locManager = LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); // 接收來自NetworkLP的位置更新通知 // 當GpsLP收到來自NetworkLP的位置信息后,將把它們傳給GPS HAL層去處理 locManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, new NetworkLocationListener(), mHandler.getLooper()); } }); } ~~~ 下面來看上述代碼中提到的listenForBroadcasts函數,其內容如下所示。 **GpsLocationProvider.java::listenForBroadcasts** ~~~ private void listenForBroadcasts() { IntentFilter intentFilter = new IntentFilter(); /* SUPL INIT流程可由一條特殊的數據短信(Data Message)觸發。注意,數據短信和我們接觸最多的 文本短信(Text Message)不同。下面這個IntentFilter將接收發往127.0.0.1:7275的數據短信。 7275為OMA-SUPL使用的端口號,關于OMA-SUPL端口號見可參考資料[28]。 關于Android中數據短信的收發,見參考資料[33]。 */ intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION); intentFilter.addDataScheme("sms"); intentFilter.addDataAuthority("localhost","7275"); mContext.registerReceiver(mBroadcastReciever, intentFilter, null, mHandler); // SUPL INIT也可由WAP推送短信觸發,該短信包含的數據類型為MIME中的 // “application/vnd.omaloc-supl-init” intentFilter = new IntentFilter(); intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION); try { intentFilter.addDataType("application/vnd.omaloc-supl-init"); } ...... mContext.registerReceiver(mBroadcastReciever, intentFilter, null, mHandler); // 監聽ALARM事件和網絡事件(CONNECTIVITY_ACTION) intentFilter = new IntentFilter(); intentFilter.addAction(ALARM_WAKEUP); intentFilter.addAction(ALARM_TIMEOUT); // 監聽網絡事件,下文介紹AGPS時還會討論它 intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); mContext.registerReceiver(mBroadcastReciever, intentFilter, null, mHandler); } ~~~ 當GpsLP收到指定的數據短信或WAP推送短信后,checkSmsSuplInit或checkWapSuplInit函數將被調用。這兩個函數的功能比較簡單,就是將短信的內容傳遞到GPS HAL層,來看看它們的代碼。 **GpsLocationProvider.java::checkSmsSuplInit和checkWapSuplInit** ~~~ private void checkSmsSuplInit(Intent intent) { SmsMessage[] messages = Intents.getMessagesFromIntent(intent); for (int i=0; i <messages.length; i++) { byte[] supl_init = messages[i].getUserData(); native_agps_ni_message(supl_init,supl_init.length); } } private void checkWapSuplInit(Intent intent) { byte[] supl_init = (byte[]) intent.getExtra("data"); native_agps_ni_message(supl_init,supl_init.length); } ~~~ 至此,LMS的初始化流程就算介紹完畢。LMS本身還包括其他一些功能,例如通知客戶端位置更新等。這部分內容都比較簡單,讀者完全可自行學習并掌握它們。 下面來看GpsLP的工作流程。 **2、GpsLP工作流程分析** GpsLP整體結構如圖9-31所示。GpsLP分為Java、JNI層、HAL層以及內核層。其中,JNI層和HAL層都屬于Native層。 :-: ![](https://box.kancloud.cn/bfe368249d93751b0f168f1631b086a3_736x712.jpg) 圖9-31 GpsLP架構 * Java層主要文件是GpsLocationProvider.java。它通過native函數集與JNI層模塊通信。本章后文將介紹native函數集的內容。 * JNI層包括一個核心動態庫,即libandr-oid_server.so。其中,和GpsLP相關的JNI函數位于com_android_server_loca-tion_GpsLocationProvider.cpp中。JNI層通過JNI回調函數集將GPS信息返回給Java層。本章后文將介紹JNI回調函數集的內容。 * HAL層也包含一個核心動態庫,其命名規范為"gps.xxx.so"。其中,"xxx"為手機所使用的硬件平臺名。以筆者的Galaxy Note 2為例,對應的GPS HAL層動態庫文件名為gps.exynos4.so。Android系統為GPS HAL層和GPS JNI層雙寫通信定義了多個接口函數集(如GpsInterface等,它們都定義在gps.h文件中),下文介紹。 * 最后,GPS HAL層將和內核層中的gps驅動交互。 這里要特別對GPS HAL層進行說明。從AOSP源碼來看,幾大廠商都沒有公開其GPS HAL層的實現,AOSP中GPS HAL模塊的默認實現也沒有可參考價值。同時,網絡上能找到的關于GPS HAL模塊(從HAL層到驅動層)的框架或設計資料非常少。基于上述考慮,本節對GPS HAL層介紹的內容將集中在GpsInterface等JNI與HAL層交互接口上。 >[info] 注意 高通公司在其開源的QRD(高通參考設計)代碼中提供了高通平臺GPS HAL層模塊的實現,但高通平臺HAL層實現采用的是C/S架構。GPS HAL模塊僅僅將上層請求轉成QMI(QC MSM Interface)消息并發送給相關服務去處理。同樣,由于缺乏相關文檔,筆者很難搞清楚高通平臺上GPS模塊的具體工作流程。 下面我們將從Java層開始介紹GpsLP的工作流程。首先是GPS的啟動流程。 **①、啟動GPS** Android平臺中,GPS的開啟和關閉主要在設置程序中控制,相關控制界面如圖9-32所示。 :-: ![](https://box.kancloud.cn/de02db005c92bd1f9d2926ff2b9fe655_539x419.jpg) 圖9-32 定位服務設置界面 當用戶單擊圖9-32中三個選擇框時,以下函數將被觸發。 **LocationSettings.java::onPreferenceTreeClick** ~~~ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { final ContentResolver cr = getContentResolver(); if (preference == mNetwork) {// 對應圖9-32中“使用無線網絡”選項 Settings.Secure.setLocationProviderEnabled(cr, LocationManager.NETWORK_PROVIDER, mNetwork.isChecked()); } else if (preference == mGps) {// 對應圖9-32中“使用GPS衛星”選項 boolean enabled = mGps.isChecked(); Settings.Secure.setLocationProviderEnabled(cr, LocationManager.GPS_PROVIDER, enabled); if (mAssistedGps != null) { mAssistedGps.setEnabled(enabled); } } else if (preference == mAssistedGps) {// 對應圖9-32中“使用輔助性GPS”選項 // 注意,國內某些運營商定制的手機沒有該選項。此處將直接修改Settings // 數據庫中secure表中“assisted_gps_enabled”字段值 Settings.Global.putInt(cr, Settings.Global.ASSISTED_GPS_ENABLED, mAssistedGps.isChecked() ? 1 : 0); } return true; } ~~~ 上述代碼中的相關操作將修改settings數據庫中location_providers_allowed字段。當“GPS衛星”和“無線網絡”兩項都選中時,location_providers_allowed字段取值將變成"network,gps"。顯然,LMS只要通過ContentObserver③監聽該字段的變化就可快速響應用戶的設置。LMS中,設置對該字段監聽的相關代碼如下所示。 **LocationManagerService.java::init** ~~~ mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true, new ContentObserver(mLocationHandler) { public void onChange(boolean selfChange) { synchronized (mLock) { updateProvidersLocked();// 最終調用的處理函數是updateProvidersLocked } } }, UserHandle.USER_ALL); ~~~ updateProvidersLocked除了根據設置的情況調用對應LP的enable或disable函數外,還需要通知LP的監聽者(即9.3.1節應用示例中介紹的LocationListener對象)。根據LP的啟動或禁止情況,LocationListener的onProviderEnabled/onProviderDisabled將被調用。 現在,重點介紹GpsLP的enable函數。該函數內部將發送ENABLE_MSG消息,而該消息最終將調用GpsLP的handleEnable進行處理。該函數的代碼如下所示。 **GpsLocationProvider.java::handleEnable** ~~~ private void handleEnable() { synchronized (mLock) { if (mEnabled) return; mEnabled = true; } boolean enabled = native_init(); // 初始化GPS HAL層模塊 if (enabled) { mSupportsXtra = native_supports_xtra(); // 判斷GPS模塊是否支持Xtra if (mSuplServerHost != null) {// 設置SUPL服務器地址和端口 native_set_agps_server(AGPS_TYPE_SUPL, mSuplServerHost, mSuplServerPort); } if (mC2KServerHost != null) { native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort); } } ......// 處理disabled的情況 } ~~~ 在handleEnable函數中,GpsLP主要通過調用native函數集中的幾個函數來初始化底層GPS模塊。這些函數將在介紹GPS HAL層時再來詳細介紹。 當GPS模塊啟動成功后,GPS HAL層將通過JNI回調函數通知GpsLP底層GPS引擎的工作能力,這個回調函數是setEngineCapabilities,其代碼如下所示。 **GpsLocationProvider.java::setEngineCapabilities** ~~~ private void setEngineCapabilities(int capabilities) { mEngineCapabilities = capabilities; // capabilities代表GPS引擎的工作能力,詳情見下文解釋 if (!hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME) && !mPeriodicTimeInjection) { mPeriodicTimeInjection = true; requestUtcTime();// 輸入UTC時間信息 } } ~~~ 目前,Android平臺中定義的GPS引擎工作能力取值有如下幾種。 ·GPS_CAPABILITY_SCHEDULING:如果設置,則GPS模塊在工作周期內(即GPS開啟之后,關閉之前這一段時間)能定時通知位置信息,例如每10秒通知一次位置信息。如果GPS模塊不支持該功能,表明GPS模塊在每個工作周期內只能通知一次位置信息。對于這種GPS,GpsLP將通過不斷啟動和關閉GPS模塊來模擬實現GPS_CAPABILITY_SCHEDULING的功能。下文代碼中將介紹模擬實現方面的內容。 * **GPS_CAPABILITY_MSB**:如果設置,則GPS模塊支持Mobile Service Based AGPS。 * **GPS_CAPABILITY_MSA**:如果設置,則GPS模塊支持Mobile Service Assisted AGPS。 * **GPS_CAPABILITY_SINGLE_SHOT**:如果設置,表明GPS模塊支持單次定位。與單次定位相對應的是連續定位。 * **GPS_CAPABILITY_ON_DEMAND_TIME**:GPS在工作周期內可能需要UTC時間信息。如果設置此能力,GPS模塊在需要UTC時間信息時將主動通過相關回調函數(即代碼中的requestUtcTime函數,JNI層也會直接調用它)從GpsLP那獲取UTC時間。如果沒有設置它,則GpsLP將每隔24小時獲取UTC時間并輸入給GPS模塊。 >[info] 提示 簡單來說,GPS_CAPABILITY_SCHEDULING表示GPS模塊支持連續定位,即GPS模塊會不斷更新位置信息。而GPS_CAPABILITY_SINGLE_SHOT表示GPS模塊支持單次定位,即只GPS模塊只會通知一次位置信息。單次定位功能適用于那些無需連續獲取位置信息的應用程序。這樣,GPS通知完位置信息后即可停止工作以節省電力。另外,LocationManager中有一個requestSingleUpdate函數,其功能和單次定位類似。但由于不是所有GPS模塊都支持單次定位,所以代碼中并沒有利用GPS_CAPABILITY_SINGLE_SHOT標志。 現在,假設GPS啟動成功,接下來的工作就是啟動GPS導航功能。 **②、啟動GPS導航** 當客戶端調用LocationManager的requestLocationUpdates并設置使用GpsLP(可參考9.3.1節中的示例代碼)后,GpsLP的setRequest函數將被調用,該函數的代碼如下所示。 **GpsLocationProvider.java::setRequest** ~~~ public void setRequest(ProviderRequest request, WorkSource source) { // 發送SET_REQUEST消息 // 注意,GpsLP將把客戶端發送的ProviderRequest轉換成GpsLP使用的GpsRequest sendMessage(SET_REQUEST, 0, new GpsRequest(request, source)); } ~~~ SET_REQUEST消息將由handleSetRequest處理,其代碼如下所示。 **GpsLocationProvider.java::handleSetRequest** ~~~ private void handleSetRequest(ProviderRequest request, WorkSource source) { if (request.reportLocation) {// 該變量用于判斷客戶端是否要求接收位置更新信息 ......// 監視客戶端的用電情況。略去相關內容 mFixInterval = (int) request.interval;// 客戶端設置的位置更新間隔 if (mFixInterval != request.interval) mFixInterval = Integer.MAX_VALUE; // mStarted變量用于判斷導航是否已經開啟 if (mStarted && hasCapability(GPS_CAPABILITY_SCHEDULING)) { /* 如果GPS模塊支持定時通知位置信息,則設置其運行模式為GPS_POSITION_RECURRENCE_PERIODIC, 同時還需要將間隔時間傳遞給GPS模塊。 */ if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, mFixInterval, 0, 0)) Log.e(TAG, "set_position_mode failed in setMinTime()"); } else if (!mStarted) {// 如果之前沒有啟動導航,則此處啟動它 startNavigating();// 下文分析 } } else { updateClientUids(new int[0]);// 計算客戶端耗電情況 stopNavigating();// 停止導航 mAlarmManager.cancel(mWakeupIntent); mAlarmManager.cancel(mTimeoutIntent); } } ~~~ 假設之前沒有啟動導航,根據上述代碼可知,startNavigating將被調用,相關代碼如下所示。 **GpsLocationProvider.java::startNavigating** ~~~ private void startNavigating() { if (!mStarted) { mTimeToFirstFix = 0; mLastFixTime = 0; mStarted = true; mPositionMode = GPS_POSITION_MODE_STANDALONE; // 默認工作模式為GPS,即不使用AGPS if (Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ASSISTED_GPS_ENABLED, 1) != 0) { if (hasCapability(GPS_CAPABILITY_MSB)) // 如AGPS啟用,則設置工作模式為AGPS,并且采用MSB mPositionMode = GPS_POSITION_MODE_MS_BASED; } // 如果GPS模塊不支持定時通知位置信息,則interval取值為1秒 int interval = (hasCapability(GPS_CAPABILITY_SCHEDULING) ? mFixInterval : 1000); if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, interval, 0, 0)) { ......// 設置定位模式失敗處理 } if (!native_start()) { ......// 導航啟動失敗處理 } // 清空GPS衛星信息 updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0); mFixRequestTime = System.currentTimeMillis(); // 如果GPS模塊不支持定時通知位置信息 if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) { /* NO_FIX_TIMEOUT為60秒。對于那些不支持定時通知的GPS模塊來說,如果客戶端要求的 更新間隔大于60秒,并且在這之間沒有收到GPS的位置通知(這表明GPS還沒定位自己 的位置),則此處會設置一個超時控制以停止GPS導航。此處的代碼邏輯需要結合GpsLP 中正常的超時管理邏輯來理解。 */ if (mFixInterval >= NO_FIX_TIMEOUT) mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, mTimeoutIntent); } } } ~~~ 此處介紹GpsLP如何處理那些不能定時通知位置信息的GPS引擎。GpsLP將注冊兩個定時觸發Intent用于啟用和關閉GPS。 * ALARM_TIMEOUT:在該Intent的處理中,GpsLP將停止導航。 * ALARM_WAKEUP:在該Intent的處理中,GpsLP將啟用導航。 **③、位置信息通知處理** 當GPS模塊更新位置時,GPS JNI層將調用GpsLP的reportLocation函數,其代碼如下所示。 **GpsLocationProvider.java::reportLocation** ~~~ private void reportLocation(int flags, double latitude, double longitude, double altitude, float speed, float bearing, float accuracy, long timestamp) { synchronized (mLocation) { mLocationFlags = flags; // 標志信息 // 此次通知的位置消息是否有經緯度信息 if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { mLocation.setLatitude(latitude);// 設置經緯度及時間戳 mLocation.setLongitude(longitude); mLocation.setTime(timestamp); mLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); } // 檢查是否有海拔信息 if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) mLocation.setAltitude(altitude); else mLocation.removeAltitude(); // 檢查是否有速度信息 if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) mLocation.setSpeed(speed); else mLocation.removeSpeed(); // 檢查是否有方位信息 if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) mLocation.setBearing(bearing); else mLocation.removeBearing(); // 是否有精度信息(以米為單位) if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) mLocation.setAccuracy(accuracy); else mLocation.removeAccuracy(); mLocation.setExtras(mLocationExtras); try { // mILocationManager指向LMS,它會把此處的位置信息通知給客戶端 mILocationManager.reportLocation(mLocation, false); } ...... }// synchronized (mLocation)代碼段結束 mLastFixTime = System.currentTimeMillis(); // 計算首次定位時間,即TTFF if (mTimeToFirstFix == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { mTimeToFirstFix = (int)(mLastFixTime - mFixRequestTime); synchronized (mListeners) { int size = mListeners.size(); for (int i = 0; i < size; i++) { Listener listener = mListeners.get(i); try { /* GpsLP還支持另外一種類型的監聽者,即GpsStatusListener, 主要用來通知GPS衛星信息。客戶端通過LocationManager的 addGpsStatusListener來注冊監聽對象。 此處將調用GpsStatusListenr的onFirstFix函數。 */ listener.mListener.onFirstFix(mTimeToFirstFix); } ...... } } } ...... /* 對于不支持GPS_CAPABILITY_SCHEDULING的GPS模塊來說,當GpsLP收到一次位置通知事件后, 它將先暫停GPS導航,然后等到超時時間到達后,再啟用導航。 */ if (mStarted && mStatus != LocationProvider.AVAILABLE) { // 和startNavigating函數最后一段代碼相對應,取消在那里設置的超時監控 if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mFixInterval < NO_FIX_TIMEOUT) mAlarmManager.cancel(mTimeoutIntent);// 取消超時控制 Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION); intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, true); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); updateStatus(LocationProvider.AVAILABLE, mSvCount); } // 對于不支持GPS_CAPABILITY_SCHEDULING功能的GPS模塊,GpsLP將軟件模擬連續定位功能 if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mStarted && mFixInterval > GPS_POLLING_THRESHOLD_INTERVAL) hibernate(); // 停止導航。該函數中將設置ALARM_WAKEUP定時任務以重新啟用導航 } ~~~ GpsLP處理位置通知的相關代碼主要就在上面介紹的reportLocation中。GpsLP最終會將位置信息告訴LMS,而LMS的handleLocationChanged函數還有許多工作要接著開展。這部分內容請讀者自行閱讀。 **④、reportStatus和reportSvStatus** 除了通知位置信息外,GPS模塊還會通過reportStatus向GpsLP反饋GPS模塊的工作狀態,該函數如下所示。 **GpsLocationProvider.java::reportStatus** ~~~ private void reportStatus(int status) { synchronized (mListeners) { boolean wasNavigating = mNavigating; // 根據狀態來更新mNavigating和mEngineOn這兩個變量 switch (status) { // 下面這兩個狀態分別表示GPS導航開始和結束 case GPS_STATUS_SESSION_BEGIN: {......} case GPS_STATUS_SESSION_END: {......} // 下面這兩個狀態分別表示GPS引擎開啟和關閉 case GPS_STATUS_ENGINE_ON: {......} case GPS_STATUS_ENGINE_OFF:{......} } if (wasNavigating != mNavigating) { int size = mListeners.size(); for (int i = 0; i < size; i++) { Listener listener = mListeners.get(i); try {// 調用GpsListener的onGpsStarted函數或onGpsStopped函數 if (mNavigating) listener.mListener.onGpsStarted(); else listener.mListener.onGpsStopped(); } ...... } // 發送廣播 Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION); intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } } } ~~~ reportStatus比較簡單,讀者了解即可。另外,GPS模塊也會通過reportSvStatus返回衛星的信息,這個函數比較有意思,我們來看看。 **GpsLocationProvider.java::reportSvStatus** ~~~ private void reportSvStatus() { /* 下面這個函數用于從GPS HAL層讀取衛星的狀態,其返回值為衛星的個數。 該函數每個參數的類型都是一個int數組,其中,除最后一個參數對應的int數組元素個數為3外, 而其他參數的int數組元素個數為MAX_SVS(值為32),每個參數的作用如下。 mSvs:用于存儲衛星的編號。 mSnrs:用于存儲衛星的SNR(信號噪聲比)。 mSvElevations:存儲衛星的高度信息。 mSvAzimuths:存儲衛星的方位信息。 mSvMasks:該數組包含三個整型值,每個整型值字長32位,分別對應32顆衛星。 EPHEMERIS_MASK(值為0):該變量的每一位代表GPS模塊是否獲取了該衛星的ephemeris數據。 ALMANAC_MASK(值為1):該變量的每一位代表GPS模塊是否獲取了該衛星的almanac數據。 USED_FOR_FIX_MASK(值為2):該變量的每一位代表該衛星是否參與了上一次的定位信息計算。 */ int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks); synchronized (mListeners) { int size = mListeners.size(); for (int i = 0; i < size; i++) { Listener listener = mListeners.get(i); try {// 通知客戶端 listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK], mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]); } ...... } } updateStatus(mStatus, Integer.bitCount(mSvMasks[USED_FOR_FIX_MASK])); ...... } ~~~ 只要監聽這些信息,讀者就能實現類似GpsTestPlus應用的效果,如圖9-33所示。 :-: ![](https://box.kancloud.cn/1b6746591067a56a2b86cac634ae9363_408x595.jpg) 圖9-33 GpsTestPlus應用效果 >[info] 提示 GpsTestPlus是一個比較常用的GPS測試軟件。筆者曾經反編譯過GpsTestPlus,其中和LM相關的函數調用比較簡單,感興趣的讀者可嘗試研究其代碼。 本節主要介紹了GpsLP的主要工作流程,與之相關的知識點如下。 * GpsLP啟動GPS、啟動導航、位置信息處理、狀態和衛星信息通知等幾個主要工作的代碼分析。從流程上來說,這部分代碼難度非常小,筆者覺得大多數讀者都能學會。 * GPS工作能力等重要標志及相關處理邏輯。 * GPS Java層調用的native函數。下文還將詳細介紹這些函數的作用。 **3、AGPS工作流程分析** Android平臺中,AGPS的處理邏輯也集中在GpsLocationProvider.java中,本節就來介紹GpsLP中AGPS的相關代碼。 **①、處理網絡事件** AGPS需要使用移動網絡,所以早在GpsLP初始時,GpsLP就注冊了對CONNECTIVITY_ACTION廣播的監聽。該廣播事件由ConnectivityService發送,并且屬于Sticky的廣播。根據《深入理解Android:卷Ⅱ》6.4.1節“registerReceiver分析之二”的介紹可知,對于Sticky廣播的接收者而言,系統會立即調度一次廣播發送流程以將當前的網絡事件傳遞給監聽者。也就是說,GpsLP的onReceive函數將很快被觸發。該函數的代碼如下所示。 **GpsLocationProvider.java::onReceive** ~~~ public class GpsLocationProvider implements LocationProviderInterface { ............... private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // 我們只關注CONNECTIVITY_ACTION事件 else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { int networkState; // 獲取網絡狀態 if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) { networkState = LocationProvider.TEMPORARILY_UNAVAILABLE; } else { networkState = LocationProvider.AVAILABLE; } // 獲取網絡事件的相關信息。NetworkInfo表示此次事件主角,它描述了哪個網絡發生了什么事件 NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); ConnectivityManager connManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); info = connManager.getNetworkInfo(info.getType()); // 更新網絡狀態,其內部將發送UPDATE_NETWORK_STATE消息 updateNetworkState(networkState, info); } } }; ............... } ~~~ UPDATE_NETWORK_STATE消息最終由handleUpdateNetworkState處理,此函數的代碼如下所示。 **GpsLocationProvider.java::handleUpdateNetworkState** ~~~ public void onReceive(Context context, Intent intent) { String action = intent.getAction(); ...... // 我們只關注CONNECTIVITY_ACTION事件 } private void handleUpdateNetworkState(int state, NetworkInfo info) { mNetworkAvailable = (state == LocationProvider.AVAILABLE); if (info != null) { // 判斷系統是否允許使用移動數據 boolean dataEnabled = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.MOBILE_DATA, 1) == 1; // 假設移動數據功能已經啟用,故networkAvailable為true boolean networkAvailable = info.isAvailable() && dataEnabled; // 獲取APN,APN(Acess Point Number)用于確定使用哪種方式連接到網絡 String defaultApn = getSelectedApn(); if (defaultApn == null) defaultApn = "dummy-apn"; // 將這些信息傳遞到GPS HAL層 native_update_network_state(info.isConnected(), info.getType(), info.isRoaming(), networkAvailable, info.getExtraInfo(), defaultApn); } /* mAGpsDataConnectionState初始狀態為AGPS_DATA_CONNECTION_CLOSED。該值在reportAGpsStatus 中被修改。下面if這段代碼邏輯需要結合reportAGpsStatus來綜合理解。初次進來,if條件不滿足。 */ if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE_SUPL && mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) { /* 注意,這段代碼只有在mAGpsDataConnectionState狀態為AGPS_DATA_CONNECTION_OPENING 才起作用。 */ String apnName = info.getExtraInfo(); if (mNetworkAvailable) { if (apnName == null) apnName = "dummy-apn"; mAGpsApn = apnName; if (mAGpsDataConnectionIpAddr != 0xffffffff) { boolean route_result; // requestRouteToHost:將SUPL的數據都路由到指定的主機地址上 route_result = mConnMgr.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_SUPL, mAGpsDataConnectionIpAddr); } native_agps_data_conn_open(apnName);// 打開數據通道 mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; } // 如果當前有可用網絡,則GpsLP將獲取NTP時間以及下載Xtra數據 if (mNetworkAvailable) { if (mInjectNtpTimePending == STATE_PENDING_NETWORK) sendMessage(INJECT_NTP_TIME, 0, null);// 觸發handleInjectNtpTime函數 if (mDownloadXtraDataPending == STATE_PENDING_NETWORK) sendMessage(DOWNLOAD_XTRA_DATA, 0, null);// 觸發handleDownloadXtraData函數 } } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { int networkState; // 獲取網絡狀態 if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) networkState = LocationProvider.TEMPORARILY_UNAVAILABLE; else networkState = LocationProvider.AVAILABLE; // 獲取網絡事件的相關信息。NetworkInfo表示此次事件主角,它描述了哪個網絡發生了什么事件 NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); ConnectivityManager connManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); info = connManager.getNetworkInfo(info.getType()); // 更新網絡狀態,其內部將發送UPDATE_NETWORK_STATE消息 updateNetworkState(networkState, info); } } ~~~ handleUpdateNetworkState還算比較簡單,其主要工作如下。 * 如果GPS模塊要求開啟AGPS數據下載(這部分邏輯下節再介紹,即mAGpsDataConnectionState的值為AGPS_DATA_CONNECTION_OPENING),則handleUpdateNetwork將開展相關操作。 * 如果網絡啟用并且GpsLP之前沒有獲取過NTP時間以及下載過Xtra數據,GpsLP將通過INJECT_NTP_TIME和DOWNLOAD_XTRA_DATA兩個消息獲取NTP時間以及下載Xtra數據。 GPS模塊什么時候會要求開啟AGPS數據下載呢?相關函數集中在reportAGpsStatus中。 **②、reportAGpsStatus分析** GPS模塊將通過reportAGpsStatus和GpsLP交互,該函數代碼如下所示。 **GpsLocationProvider.java::reportAGpsStatus** ~~~ private void reportAGpsStatus(int type, int status, int ipaddr) { switch (status) { case GPS_REQUEST_AGPS_DATA_CONN:// GPS模塊要求啟用數據鏈接 /* 此處,設置mAGpsDataConnectionState為AGPS_DATA_CONNECTION_OPENING, 該狀態值表示數據鏈接處于開啟過程中。 */ mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING; /* 要求使用移動數據。startUsingNetworkFeature是ConnectivityManager中一個比較重要的函 數,其第一個參數表示此處操作的網絡是移動網絡,第二個參數表示使用SUPL相關的功能。 */ int result = mConnMgr.startUsingNetworkFeature( ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL); mAGpsDataConnectionIpAddr = ipaddr; // 保存地址 // APN_ALREADY_ACTIVE表示startUsingNetworkFeature指定的網絡及功能已經啟用 if (result == PhoneConstants.APN_ALREADY_ACTIVE) { if (mAGpsApn != null) {// mAGpsApn在onReceive函數中被設置 if (mAGpsDataConnectionIpAddr != 0xffffffff) { boolean route_result; route_result = mConnMgr.requestRouteToHost( ConnectivityManager.TYPE_MOBILE_SUPL,mAGpsDataConnectionIpAddr); } native_agps_data_conn_open(mAGpsApn);// 調用native函數表示AGPS數據鏈接 // 開啟AGPS數據鏈接開啟成功 mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; } else if (result == PhoneConstants.APN_REQUEST_STARTED) { // APN_REQUEST_STARTED表示startUsingNetworkFeature設置的請求已經發送給相關模塊處理 } else...... break; // 下面這個值表示AGPS不再需要使用數據鏈接 case GPS_RELEASE_AGPS_DATA_CONN:{......} break; // 下面這個值表示GPS模塊中AGPS數據鏈接初始化完畢 case GPS_AGPS_DATA_CONNECTED: {......} break; // 下面這個值表示GPS模塊中AGPS數據鏈接完畢 case GPS_AGPS_DATA_CONN_DONE: {......} break; // 下面這個值表示GPS模塊中AGPS數據鏈接失敗 case GPS_AGPS_DATA_CONN_FAILED: {......} break; } } } ~~~ reportAGpsStatus主要對兩個狀態進行處理,即GPS_REQUEST_AGPS_DATA_CONNGPS_RELEASE_AGPS_DATA_CONN。 總之,當網絡準備好后,GpsLP將調用native_agps_data_conn_open啟用GPS模塊中AGPS數據鏈接功能。 **③、下載Xtra數據** 最后,看看Xtra數據的下載處理,這部分功能由handleDownloadXtraData實現,代碼如下所示。 **GpsLocationProvider.java::handleDownloadXtraData** ~~~ private void handleDownloadXtraData() { if (mDownloadXtraDataPending == STATE_DOWNLOADING) return; ...... mDownloadXtraDataPending = STATE_DOWNLOADING; mWakeLock.acquire(); // 利用后臺線程下載數據 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { public void run() { GpsXtraDownloader xtraDownloader = new GpsXtraDownloader( mContext, mProperties); // 下載LTO數據 byte[] data = xtraDownloader.downloadXtraData(); // 將這些數據傳遞給GPS HAL層 if (data != null) native_inject_xtra_data(data, data.length); // 發現XTtra數據下載完畢通知 sendMessage(DOWNLOAD_XTRA_DATA_FINISHED, 0, null); ...... mWakeLock.release(); } }); } ~~~ GpsLP中的AGPS流程也比較簡單。GpsLP只要監控網絡事件以及接收來自GPS HAL層的AGPS狀態通知即可順利完成相關工作。 結合前面對GpsLP工作流程的分析可知,Android平臺中,Java層的GPS模塊和Native層(包括JNI和HAL層)GPS模塊交互非常多,下面將重點分析GPS Native層方面的知識。 **4、GPS JNI與HAL層介紹** 在GpsLocationProvider初始化一節我們曾提到說GpsLocationProvider類有一個靜態的初始化代碼,在那段代碼中,class_init_native函數將初始化JNI層相關模塊。馬上來看它。 **①、JNI與HAL層初始化** class_init_native函數對應的JNI函數如下所示: **com_android_server_location_GpsLocationProvider.cpp::android_location_GpsLocationProvider_class_init_native** ~~~ static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) { int err; hw_module_t* module; //獲取JNI回調函數集對應的Java MethodId。如果讀者不熟悉JNI,請閱讀《深入理解Android:卷1》 //第2章“深入理解JNI” method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFJ)V"); .......//JNI回調函數集的其他函數,下文會詳細介紹它們 method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(I)V"); ....... //加載GPS HAL層模塊,GPS_HARDWARE_MODULE_ID的值為“gps” err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module); if (err == 0) { hw_device_t* device; //打開GPS HAL層模塊 err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device); if (err == 0) { gps_device_t* gps_device = (gps_device_t *)device; //獲取GPS HAL層最主要的交互接口GpsInterface,它是JNI層和HAL層的重要交互 //通道 sGpsInterface = gps_device->get_gps_interface(gps_device); } } //GPS HAL模塊對外還提供了幾個重要交互接口。 if (sGpsInterface) { sGpsXtraInterface = //用來和GPS HAL層中xtra模塊交互的接口 (const GpsXtraInterface*)sGpsInterface->get_extension(GPS_XTRA_INTERFACE); sAGpsInterface = //用來和GPS HAL層AGPS模塊交互的接口 (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE); sGpsNiInterface = //用來和GPS HAL層NI(Network Initiated)模塊交互的接口 (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE); sGpsDebugInterface = //用來調試的接口 (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE); sAGpsRilInterface = //用來和GPS HAL層AGPS及RIL相關的接口 (const AGpsRilInterface*)sGpsInterface->get_extension(AGPS_RIL_INTERFACE); } } ~~~ 當GpsLP啟用后,native_init函數將被調用,它對應的JNI函數代碼如下所示: **com_android_server_location_GpsLocationProvider.cpp::android_location_GpsLocationProvider_init** ~~~ static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj) { // this must be set before calling into the HAL library if (!mCallbacksObj) mCallbacksObj = env->NewGlobalRef(obj); //向GPS HAL層設置回調函數接口。當GPS HAL層有情況需要通知JNI層時,這些回調函數 //將被調用 if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0) return false; //設置其他接口的回調函數 if (sGpsXtraInterface && sGpsXtraInterface->init(&sGpsXtraCallbacks) != 0) sGpsXtraInterface = NULL; if (sAGpsInterface) sAGpsInterface->init(&sAGpsCallbacks); if (sGpsNiInterface) sGpsNiInterface->init(&sGpsNiCallbacks); if (sAGpsRilInterface) sAGpsRilInterface->init(&sAGpsRilCallbacks); return true; } ~~~ 通過上述代碼可知,Android平臺中通過定義多個交互接口實現了GPS Java層、JNI層以及HAL層的交互問題。顯然,理解這些接口的作用將非常有助于我們學習Android平臺中GPS模塊的實現。下面將先介紹Java層與JNI層的交互接口。 **②、Java層與JNI層交互接口函數介紹** 先來看GPS Java層調用的JNI函數,它們的作用如下所示: **GpsLocationProvider.java** ~~~ //初始化JNI層相關的類型 native void class_init_native(); //判斷系統是否支持GPS native boolean native_is_supported(); //初始化GPS HAL層模塊 native boolean native_init(); //清理GPS HAL層模塊所分配的資源 native void native_cleanup(); /* 設置GPS模塊工作模式,其各個參數取值含義如下: mode:GPS工作模式,目前可取值有GPS_POSITION_MODE_STANDALONE(值為0,僅GPS工作), GPS_POSITION_MODE_MS_BASED(值為1,AGPS MSB模式),GPS_POSITION_MODE_MS_ASSISTED (值為2,AGPS MSA模式) recurrence:位置更新模式,目前可取值有GPS_POSITION_RECURRENCE_PERIODIC(值為0,連續 定位)、GPS_POSITION_RECURRENCE_SINGLE(值為1,單次定位) min_interval:最短位置更新時間,單位為毫秒 preferred_accurary: 期望的位置更新精度,單位為米 preferred_time:期望的TTFF時間,單位為毫秒 */ native boolean native_set_position_mode(int mode, int recurrence, int min_interval, int preferred_accuracy, int preferred_time); //下面這個兩個函數用于啟動和關閉導航 native boolean native_start(); native boolean native_stop(); //刪掉AGPS輔助數據,其flags參數的取值請讀者閱讀GpsLocationProvider.java的deleteAidingData函數 native void native_delete_aiding_data(int flags); //讀取衛星信息,該函數在“reportStatus和reportSvStatus介紹”一節中已介紹過了 native int native_read_sv_status(int[] svs, float[] snrs, float[] elevations, float[] azimuths, int[] masks); //讀取NMEA數據 native int native_read_nmea(byte[] buffer, int bufferSize); //輸入位置信息,在GpsLP中,該位置信息由NetworkLP提供 native void native_inject_location(double latitude, double longitude, float accuracy); /* 輸入NTP時間,其中: time:為NtpTimeTrustedTime從網絡中獲取到的NTP時間 timeReference:為設備從開機到一次NTP請求處理成功后的所耗費的時間,由SystemClock的elapsedRealtime 函數返回 uncertainty:準確度。在Android系統中,該值為NTP請求發送和接收往返時間的一般。詳情可參考 SnetpClient.java文件 */ native void native_inject_time(long time, long timeReference, int uncertainty); //GPS模塊是否支持XTRA數據 native boolean native_supports_xtra(); //輸入XTRA數據,即LTO數據 native void native_inject_xtra_data(byte[] data, int length); //用于調試,獲取GPS模塊內部狀態 native String native_get_internal_state(); //打開AGPS數據下載通道,參數apn指明了所要使用的APN native void native_agps_data_conn_open(String apn); //關閉AGPS數據下載通道 native void native_agps_data_conn_closed(); //GpsLP處理AGPS相關事宜失敗時候調用下面這個函數 native void native_agps_data_conn_failed(); //將來自數據短信或WAP推送短信得到的信息傳遞給GPS模塊。 native void native_agps_ni_message(byte [] msg, int length); //設置AGPS服務端地址,其中type取值有兩種: //AGPS_TYPE_SUPL:值為1,代表SUPL服務器 //AGPS_TYPE_C2K:值為2,代表C2K服務器 native void native_set_agps_server(int type, String hostname, int port); /* 當GPS模塊需要使用APGS時,會調用reportNiNotification函數(詳情見下文)以通知用戶。 用戶處理完后,將通過下面這個函數告知GPS模塊處理結果。注意,這部分內容涉及到OMA-SUPL相關知識,請讀者 閱讀參考資料[28]。 該函數的參數如下: notificationId:通知id,代表GPS HAL層的某一個處理請求 userResponse有三種取值,分別是GPS_NI_RESPONSE_ACCEPT(值為0,代表用戶允許相關操作)、 GPS_NI_RESPONSE_DENY(值為1,用戶拒絕相關操作)、GPS_NI_RESPONSE_NORESP (值為2,代表用戶無回應)。NI和GpsNetInitiatedHandler有關,讀者可自行研究它 */ native void native_send_ni_response(int notificationId, int userResponse); /* 設置AGPS參考位置信息,其各參數解釋如下: type:取值可為AGPS_REF_LOCATION_TYPE_GSM_CELLID(值為1,代表GMS網絡的cell id)、 AGPS_REF_LOCATION_TYPE_UMTS_CELLID(值為2,代表CDMA網絡的cell id)、 AGPS_REG_LOCATION_TYPE_MAC(值為3,代表MAC地址) mcc:Mobile Country Code(移動國家碼),由3位數字組成,唯一地識別移動用戶所屬的國家,中國為460 mnc:Mobile Network Code(移動網絡碼),用于識別移動用戶所歸屬的移動網絡。中國移動TD系統使用00, 中國聯通GSM系統使用01,中國移動GSM系統使用02,中國電信CDMA系統使用03 lac:Location Area Code(位置區碼)。移動通信中,為了確定終端臺的位置,每個移動網絡的覆蓋區都被劃分 成許多位置區,位置區碼(LAC)則用于標識不同的位置區。 cid:cell id(基站編號) */ native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc, int lac, int cid); /* 設置終端與移動網絡相關的一些參數信息,其type參數決定了setid參數的取值。type可取值有: type: AGPS_SETID_TYPE_NONE,值為0,無意義 AGPS_SETID_TYPE_IMSI,值為1,代表IMSI(international mobiles subscriber identity, 國際移動用戶號碼標識)。IMSI信息存儲在SIM卡上。 AGPS_SETID_TYPE_MSISDN,值為2,代表Mobile Subscriber ISDN(用戶號碼),即手機號碼 */ native void native_agps_set_id(int type, String setid); /* 通知GPS HAL層系統當前網絡的狀態,其各參數解釋如下: connected:網絡是否連接 type:網絡類型,可取值請參考ConnectivityManager中各網絡類型的定義情況。常用的有TYPE_MOBILE、 TYPE_WIFI等 roaming:是否處于漫游狀態 available:網絡是否可用 extraInfo:附加信息 defaultAPN:默認APN */ native void native_update_network_state(boolean connected, int type, boolean roaming, boolean available, String extraInfo, String defaultAPN); ~~~ 現在來看看JNI回調Java層的函數,如下所示: **GpsLocationProvider.java** ~~~ //GPS模塊匯報位置信息 void reportLocation(int flags, double latitude, double longitude, double altitude, float speed, float bearing, float accuracy, long timestamp): //GPS模塊通知GPS工作狀態 void reportStatus(int status): //GPS模塊通知衛星狀態 void reportSvStatus(): //GPS模塊通知AGPS狀態 void reportAGpsStatus(int type, int status, int ipaddr): //GPS模塊通知NMEA信息 void reportNmea(long timestamp): //GPS模塊通知GpsLP自己的能力 void setEngineCapabilities(int capabilities): //GPS模塊要求下載XTRA數據 void xtraDownloadRequest(): //GPS模塊通知Network Initiated通知,其各參數解釋如下: void reportNiNotification( int notificationId,//GPS HAL層分配的通知ID int niType,//NI類型,可取值請參考gps.h的GpsNiType的定義 int notifyFlags,//標志信息,可取值請參考gps.h的GpsNiNotifyFlags定義 int timeout,//等待用戶處理的超時時間 //當超時發生后,系統采用的默認處理結果。其取值和native_send_ni_respons中第二個參數一樣 int defaultResponse, String requestorId,//代表請求者的id String text, //通知信息 int requestorIdEncoding,//requestorId的編碼格式,參考gps.h GpsNiEncodingType的定義 int textEncoding,//text的編碼格式 String extras //附加信息 ): //要求獲取參考位置信息,flags參數目前沒有使用 void requestRefLocation(int flags): //要求設置移動網絡相關信息,flags參數表示要獲取什么樣的信息,可取值同native_agps_set_id的type參數 //一致 void requestSetID(int flags): //要求獲取UTC時間 void requestUtcTime(): ~~~ 接下來看GPS JNI與HAL層的交互接口。我們主要介紹JNI調用HAL層的接口。 **③、 GpsInterface及其他交互接口介紹** GpsInterface是GPS JNI與HAL層交互的主要接口。在Android平臺中,該接口定義在一個同名的結構體中,其內容如下所示: **gps.h::GpsInterface結構體** ~~~ typedef struct { size_t size;//GpsInterface結構體的長度 //初始化GPS模塊。其參數為GPS模塊所需的回調函數 int (*init)( GpsCallbacks* callbacks ); //下面這兩個函數用于開啟或關閉導航 int (*start)( void ); int (*stop)( void ); //清理GPS模塊所分配的資源 void (*cleanup)( void ); //輸入UTC時間,參數解釋同native_inject_time int (*inject_time)(GpsUtcTime time, int64_t timeReference,int uncertainty); //輸入位置信息,參數解釋同native_inject_location int (*inject_location)(double latitude, double longitude, float accuracy); //刪除賦值信息,其參數解釋同native_delete_aiding_data void (*delete_aiding_data)(GpsAidingData flags); //設置GPS模塊的工作模式,其參數解釋同native_set_position_mode int (*set_position_mode)(GpsPositionMode mode, GpsPositionRecurrence recurrence, uint32_t min_interval, uint32_t preferred_accuracy, uint32_t preferred_time); //獲取GPS模塊實現的擴展接口,AGPS擴展接口對應的name為“agps”、xtra擴展接口對應的name為“xtra” const void* (*get_extension)(const char* name); } GpsInterface; ~~~ 比較GpsInterface和Java層定義的native函數,讀者可發現二者結合非常緊密。實際上,JNI實現的那些native函數最終都會把請求通過HAL層接口交給GPS模塊去處理。 再來看gps.h定義的GpsXtraInterface接口,相關內容封裝在同名的結構體中,如下所示: **gps.h::GpsXtraInterface結構體** ~~~ typedef struct { size_t size; //初始化GPS中的xtra相關模塊 int (*init)( GpsXtraCallbacks* callbacks ); //輸入xtra數據,其參數解釋同native_inject_xtra_data int (*inject_xtra_data)( char* data, int length ); } GpsXtraInterface; ~~~ 接下來看AGpsInterface結構體,代碼如下所示: **gps.h::AGpsInterface** ~~~ typedef struct { size_t size; //初始化AGPS模塊 void (*init)( AGpsCallbacks* callbacks ); //打開AGPS數據連接,其參數解釋同native_data_conn_open int (*data_conn_open)( const char* apn ); //關閉AGPS數據連接,其參數解釋同native_data_conn_close int (*data_conn_closed)(); //AGPS數據連接操作失敗,同native_data_conn_fail int (*data_conn_failed)(); //設置AGPS服務器地址等相關信息,參數解釋同native_agps_set_server int (*set_server)( AGpsType type, const char* hostname, int port ); } AGpsInterface; ~~~ 最后來看GpsNiInterface和AGpsRilInterface接口。GpsNiInterface的代碼如下所示: **gps.h::GpsNiInterface** ~~~ typedef struct { size_t size; //初始化NI模塊 void (*init) (GpsNiCallbacks *callbacks); //發送NI回復,請參數同native_send_ni_response void (*respond) (int notif_id, GpsUserResponseType user_response); } GpsNiInterface; ~~~ 而AGpsRilInterface的代碼如下所示: **gps.h::AGpsRilInterface** ~~~ typedef struct { size_t size; //初始化AGPS Ril相關的處理模塊 void (*init)( AGpsRilCallbacks* callbacks ); //設置參考位置信息,其第一個參數類型為AGpsRefLocation,該結構體的成員與 //native_agps_set_ref_location_cellid函數的參數一一對應 void (*set_ref_location) (const AGpsRefLocation *agps_reflocation, size_t sz_struct); //設置AGPS移動網絡id信息,其參數解釋同native_agps_set_id void (*set_set_id) (AGpsSetIDType type, const char* setid); //設置NI消息,其參數解釋同native_agps_ni_message void (*ni_message) (uint8_t *msg, size_t len); //注意,下面這兩個函數的參數合起來就是native_update_network_state的參數。Java層調用 //一次native_update_network_state將觸發下面這兩個函數被調用 //更新移動網絡狀態 void (*update_network_state) (int connected, int type, int roaming, const char* extra_info); //設置網絡連接狀態 void (*update_network_availability) (int avaiable, const char* apn); } AGpsRilInterface; ~~~ 本節對Android平臺中LocationManagerService及相關模塊進行了介紹,尤其對本章的核心GpsLocationProvider及GPS各層次及之間的交互接口進行了重點講解。 和本書前面介紹的WifiService、WifiP2pService以及NfcService比起來,LMS一點也不復雜,這其中的幾個主要原因包括: - LMS提供的服務本身就比較簡單,它的核心功能就是提供位置信息。 - GpsLP通過合理的分層接口設計使得GPS HAL層之上的代碼能夠不受底層硬件的影響。 另外,筆者希望讀者在學習完本章后,對以下內容開展進一步的學習: - 研究PassiveProvider和FusedLocationProvider的內容。掌握LMS如何與位于應用進程的LP進行交互。 - 學習LocationFudger的內容,掌握如何模糊位置信息。 - 學習GeofenceManager的內容。 最后,讀者可嘗試反編譯NetworkLocation.apk,掌握NetworkLP以及Geocoder的實現原理[^①]。 [^①]:出于對版權的考慮,筆者不能在書中對NetworkLocation.apk反編譯的結果開展詳細討論。如果時機合適,筆者將在博客上對它的實現進行介紹。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看