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测试的位置关联起来。