Android系列之Wifi定位html
Broncho A1還不支持基站和WIFI定位,Android的老版本里是有NetworkLocationProvider的,它實現了基站和WIFI定位,但從 android 1.5以後就被移除了。原本想在broncho A1裏本身實現NetworkLocationProvider的,但一直沒有時間去研究。我知道 gears(http://code.google.com/p/gears/)是有提供相似的功能,昨天研究了一下Gears的代碼,看能不能移植到 android中來java
1.下載源代碼
[url]svn checkout http://gears.googlecode.com/svn/trunk/ gears-read-only[/url]
定位相關的源代碼在gears/geolocation目錄中。android
2.關注android平臺中的基站位置變化git
JAVA類AndroidRadioDataProvider是 PhoneStateListener的子類,用來監聽Android電話的狀態變化。當服務狀態、信號強度和基站變化時,json
就會用下面代碼獲取小區信息:服務器
1RadioData radioData =new RadioData(); 2 GsmCellLocation gsmCellLocation = (GsmCellLocation) cellLocation; 3 4// Extract the cell id, LAC, and signal strength. 5 radioData.cellId = gsmCellLocation.getCid(); 6 radioData.locationAreaCode = gsmCellLocation.getLac(); 7 radioData.signalStrength = signalStrength; 8 9// Extract the home MCC and home MNC. 10 String operator= telephonyManager.getSimOperator(); 11 radioData.setMobileCodes(operator, true); 12 13if (serviceState !=null) { 14// Extract the carrier name. 15 radioData.carrierName = serviceState.getOperatorAlphaLong(); 16 17// Extract the MCC and MNC. 18operator= serviceState.getOperatorNumeric(); 19 radioData.setMobileCodes(operator, false); 20 } 21 22// Finally get the radio type. 23int type = telephonyManager.getNetworkType(); 24if (type == TelephonyManager.NETWORK_TYPE_UMTS) { 25 radioData.radioType = RADIO_TYPE_WCDMA; 26 } elseif (type == TelephonyManager.NETWORK_TYPE_GPRS 27|| type == TelephonyManager.NETWORK_TYPE_EDGE) { 28 radioData.radioType = RADIO_TYPE_GSM; 29 } 30
而後再調用用C代碼實現的onUpdateAvailable函數。
2.Native函數onUpdateAvailable是在 radio_data_provider_android.cc裏實現的。
聲明Native函數 cookie
1JNINativeMethod AndroidRadioDataProvider::native_methods_[] = { 2 {"onUpdateAvailable", 3"(L" GEARS_JAVA_PACKAGE "/AndroidRadioDataProvider$RadioData;J)V", 4 reinterpret_cast<void*>(AndroidRadioDataProvider::OnUpdateAvailable) 5 }, 6}; 7
JNI調用好像只能調用靜態成員函數,把對象自己用一個參數傳進來,而後再調用對象的成員函數。網絡
void AndroidRadioDataProvider::OnUpdateAvailable(JNIEnv* env, jclass cls, jobject radio_data, jlong self) { assert(radio_data); assert(self); AndroidRadioDataProvider *self_ptr = reinterpret_cast<AndroidRadioDataProvider*>(self); RadioData new_radio_data; if (InitFromJavaRadioData(env, radio_data, &new_radio_data)) { self_ptr->NewRadioDataAvailable(&new_radio_data); } }
先判斷基站信息有沒有變化,若是有變化則通知相關的監聽者。less
1void AndroidRadioDataProvider::NewRadioDataAvailable( 2 RadioData* new_radio_data) { 3bool is_update_available =false; 4 data_mutex_.Lock(); 5if (new_radio_data &&!radio_data_.Matches(*new_radio_data)) { 6 radio_data_ =*new_radio_data; 7 is_update_available =true; 8 } 9// Avoid holding the mutex locked while notifying observers. 10 data_mutex_.Unlock(); 11 12if (is_update_available) { 13 NotifyListeners(); 14 } 15}
接下來的過程,在基站定位和WIFI定位是同樣的,後面咱們再來介紹。下面咱們先看 WIFI定位異步
3.關注android平臺中的WIFI變化。
JAVA類AndroidWifiDataProvider擴展了 BroadcastReceiver類,它關注WIFI掃描結果:
1IntentFilter filter =new IntentFilter(); 2 filter.addAction(mWifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 3 mContext.registerReceiver(this, filter, null, handler);
當收到WIFI掃描結果後,調用Native函數 onUpdateAvailable,並把WIFI的掃描結果傳遞過去。
1publicvoid onReceive(Context context, Intent intent) { 2if (intent.getAction().equals( 3 mWifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { 4if (Config.LOGV) { 5 Log.v(TAG, "Wifi scan resulst available"); 6 } 7 onUpdateAvailable(mWifiManager.getScanResults(), mNativeObject); 8 } 9 }
Native函數onUpdateAvailable是在 wifi_data_provider_android.cc裏實現的。
1JNINativeMethod AndroidWifiDataProvider::native_methods_[] = { 2{"onUpdateAvailable", 3"(Ljava/util/List;J)V", 4reinterpret_cast<void*>(AndroidWifiDataProvider::OnUpdateAvailable) 5}, 6}; 7 8void AndroidWifiDataProvider::OnUpdateAvailable(JNIEnv*/* env */, 9jclass /* cls */, 10jobject wifi_data, 11jlong self) { 12assert(self); 13AndroidWifiDataProvider *self_ptr =14reinterpret_cast<AndroidWifiDataProvider*>(self); 15WifiData new_wifi_data; 16if (wifi_data) { 17InitFromJava(wifi_data, &new_wifi_data); 18} 19// We notify regardless of whether new_wifi_data is empty 20// or not. The arbitrator will decide what to do with an empty 21// WifiData object. 22self_ptr->NewWifiDataAvailable(&new_wifi_data); 23} 24 25void AndroidWifiDataProvider::NewWifiDataAvailable(WifiData* new_wifi_data) { 26assert(supported_); 27assert(new_wifi_data); 28bool is_update_available =false; 29data_mutex_.Lock(); 30is_update_available = wifi_data_.DiffersSignificantly(*new_wifi_data); 31wifi_data_ =*new_wifi_data; 32// Avoid holding the mutex locked while notifying observers. 33data_mutex_.Unlock(); 34 35if (is_update_available) { 36is_first_scan_complete_ =true; 37NotifyListeners(); 38} 39 40#if USING_CCTESTS 41// This is needed for running the WiFi test on the emulator. 42// See wifi_data_provider_android.h for details. 43if (!first_callback_made_ && wifi_data_.access_point_data.empty()) { 44first_callback_made_ =true; 45NotifyListeners(); 46} 47#endif 48} 49 50JNINativeMethod AndroidWifiDataProvider::native_methods_[] = {51{"onUpdateAvailable",52"(Ljava/util/List;J)V",53reinterpret_cast<void*>(AndroidWifiDataProvider::OnUpdateAvailable)54},55};56 57void AndroidWifiDataProvider::OnUpdateAvailable(JNIEnv*/* env */,58jclass /* cls */,59jobject wifi_data,60jlong self) {61assert(self);62AndroidWifiDataProvider *self_ptr =63reinterpret_cast<AndroidWifiDataProvider*>(self);64WifiData new_wifi_data;65if (wifi_data) {66InitFromJava(wifi_data, &new_wifi_data);67}68// We notify regardless of whether new_wifi_data is empty69// or not. The arbitrator will decide what to do with an empty70// WifiData object. 71self_ptr->NewWifiDataAvailable(&new_wifi_data);72}73 74void AndroidWifiDataProvider::NewWifiDataAvailable(WifiData* new_wifi_data) {75assert(supported_);76assert(new_wifi_data);77bool is_update_available =false;78data_mutex_.Lock();79is_update_available = wifi_data_.DiffersSignificantly(*new_wifi_data);80wifi_data_ =*new_wifi_data;81// Avoid holding the mutex locked while notifying observers. 82data_mutex_.Unlock();83 84if (is_update_available) {85is_first_scan_complete_ =true;86NotifyListeners();87}88 89#if USING_CCTESTS90// This is needed for running the WiFi test on the emulator.91// See wifi_data_provider_android.h for details. 92if (!first_callback_made_ && wifi_data_.access_point_data.empty()) {93first_callback_made_ =true;94NotifyListeners();95}96#endif 97}98
從以上代碼能夠看出,WIFI定位和基站定位的邏輯差很少,只是前者獲取的WIFI的掃描結果,然後者獲取的基站信息。
後面代碼的基本上就統一塊兒來了,接下來咱們繼續看。
5.把變化(WIFI/基站)通知給相應的監聽者。
1AndroidWifiDataProvider和AndroidRadioDataProvider都是繼承了DeviceDataProviderImplBase,DeviceDataProviderImplBase的主要功能就是管理全部Listeners。 2 3static DeviceDataProvider *Register(ListenerInterface *listener) { 4 MutexLock mutex(&instance_mutex_); 5if (!instance_) { 6 instance_ =new DeviceDataProvider(); 7 } 8 assert(instance_); 9 instance_->Ref(); 10 instance_->AddListener(listener); 11return instance_; 12 } 13 14staticbool Unregister(ListenerInterface *listener) { 15 MutexLock mutex(&instance_mutex_); 16if (!instance_->RemoveListener(listener)) { 17returnfalse; 18 } 19if (instance_->Unref()) { 20 delete instance_; 21 instance_ = NULL; 22 } 23returntrue; 24 } 25
6.誰在監聽變化(WIFI/基站)
NetworkLocationProvider在監聽變化(WIFI/基站):
1radio_data_provider_ = RadioDataProvider::Register(this); 2 wifi_data_provider_ = WifiDataProvider::Register(this);
當有變化時,會調用函數DeviceDataUpdateAvailable:
// DeviceDataProviderInterface::ListenerInterface implementation. void NetworkLocationProvider::DeviceDataUpdateAvailable( RadioDataProvider *provider) { MutexLock lock(&data_mutex_); assert(provider == radio_data_provider_); is_radio_data_complete_ = radio_data_provider_->GetData(&radio_data_); DeviceDataUpdateAvailableImpl(); }void NetworkLocationProvider::DeviceDataUpdateAvailable( WifiDataProvider *provider) { assert(provider == wifi_data_provider_); MutexLock lock(&data_mutex_); is_wifi_data_complete_ = wifi_data_provider_->GetData(&wifi_data_); DeviceDataUpdateAvailableImpl(); }
不管是WIFI仍是基站變化,最後都會調用 DeviceDataUpdateAvailableImpl:
1void NetworkLocationProvider::DeviceDataUpdateAvailableImpl() {2 timestamp_ = GetCurrentTimeMillis();3 4// Signal to the worker thread that new data is available. 5 is_new_data_available_ =true;6 thread_notification_event_.Signal();7}
這裏面只是發了一個signal,通知另一個線程去處理。
7.誰在等待thread_notification_event_
線程函數NetworkLocationProvider::Run在一個循環中等待 thread_notification_event,當有變化(WIFI/基站)時,就準備請求服務器查詢位置。
先等待:
1if (remaining_time >0) {2 thread_notification_event_.WaitWithTimeout(3 static_cast<int>(remaining_time));4 } else {5 thread_notification_event_.Wait();6 }
準備請求:
1if (make_request) { 2 MakeRequest(); 3 remaining_time =1; 4}
再來看MakeRequest的實現:
先從cache中查找位置:
1const Position *cached_position = 2 position_cache_->FindPosition(radio_data_, wifi_data_); 3 data_mutex_.Unlock(); 4if (cached_position) { 5 assert(cached_position->IsGoodFix()); 6// Record the position and update its timestamp. 7 position_mutex_.Lock(); 8 position_ =*cached_position; 9 position_.timestamp = timestamp_;10 position_mutex_.Unlock();11 12// Let listeners know that we now have a position available. 13 UpdateListeners();14returntrue;15 }
若是找不到,再作實際的請求
1return request_->MakeRequest(access_token,2 radio_data_,3 wifi_data_,4 request_address_,5 address_language_,6 kBadLatLng, // We don't have a position to pass 7 kBadLatLng, // to the server. 8 timestamp_);
7.客戶端協議包裝
前面的request_是NetworkLocationRequest實例,先看 MakeRequest的實現:
先對參數進行打包:
1if (!FormRequestBody(host_name_, access_token, radio_data, wifi_data,2 request_address, address_language, latitude, longitude,3 is_reverse_geocode_, &post_body_)) {4returnfalse;5 }
通知負責收發的線程
1thread_event_.Signal();
8.負責收發的線程
1void NetworkLocationRequest::Run() { 2while (true) { 3 thread_event_.Wait(); 4if (is_shutting_down_) { 5break; 6 } 7 MakeRequestImpl(); 8 } 9}10 11void NetworkLocationRequest::MakeRequestImpl() {12 WebCacheDB::PayloadInfo payload;
把打包好的數據經過HTTP請求,發送給服務器
1 scoped_refptr<BlobInterface> payload_data; 2bool result = HttpPost(url_.c_str(), 3false, // Not capturing, so follow redirects 4 NULL, // reason_header_value 5 HttpConstants::kMimeApplicationJson, // Content-Type 6 NULL, // mod_since_date 7 NULL, // required_cookie 8true, // disable_browser_cookies 9 post_body_.get(),10&payload,11&payload_data,12 NULL, // was_redirected 13 NULL, // full_redirect_url 14 NULL); // error_message 15 16 MutexLock lock(&is_processing_response_mutex_);17// is_aborted_ may be true even if HttpPost succeeded. 18if (is_aborted_) {19 LOG(("NetworkLocationRequest::Run() : HttpPost request was cancelled.\n"));20return;21 }22if (listener_) {23 Position position;24 std::string response_body;25if (result) {26// If HttpPost succeeded, payload_data is guaranteed to be non-NULL. 27 assert(payload_data.get());28if (!payload_data->Length() ||29!BlobToString(payload_data.get(), &response_body)) {30 LOG(("NetworkLocationRequest::Run() : Failed to get response body.\n"));31 }32 }
解析出位置信息
1std::string16 access_token;2 GetLocationFromResponse(result, payload.status_code, response_body,3 timestamp_, url_, is_reverse_geocode_,4&position, &access_token);
通知位置信息的監聽者
1bool server_error =2!result || (payload.status_code >=500&& payload.status_code <600);3 listener_->LocationResponseAvailable(position, server_error, access_token);4 }5}
有人會問,請求是發哪一個服務器的?固然是google了,缺省的URL是:
1staticconst char16 *kDefaultLocationProviderUrl =2 STRING16(L"https://www.google.com/loc/json");
回過頭來,咱們再總結一下:
1.WIFI和基站定位過程以下:
2.NetworkLocationProvider和 NetworkLocationRequest各有一個線程來異步處理請求。
3.這裏的NetworkLocationProvider與android中的 NetworkLocationProvider並非同一個東西,這裏是給gears用的,要在android的google map中使用,還得包裝成android中的NetworkLocationProvider的接口。
4.WIFI和基站定位與平臺無關,只要你能拿到WIFI掃描結果或基站信息,並且能訪問google的定位服務器,無論你是Android平臺,Windows Mobile平臺仍是傳統的feature phone,你均可以實現WIFI和基站定位。
附: WIFI和基站定位原理
不管是WIFI的接入點,仍是移動網絡的基站設備,它們的位置基本上都是固定的。設備端(如手機)能夠找到它們的ID,如今的問題就是如何經過這些ID找到對應的位置。網上的流行的說法是開車把全部每一個位置都跑一遍,把這些設備的位置與 GPS測試的位置關聯起來。