Gears Android WIFI/基站定位源代碼分析

Broncho A1還不支持基站和WIFI定位,Android的老版本里是有NetworkLocationProvider的,它實現了基站和WIFI定位,但從 android 1.5之後就被移除了。本來想在broncho A1裏自己實現NetworkLocationProvider的,但一直沒有時間去研究。我知道 gears(http://code.google.com/p/gears/)是有提供類似的功能,昨天研究了一下Gears的代碼,看能不能移植到 android中來。
[b]1.下載源代碼[/b]
[url]svn checkout http://gears.googlecode.com/svn/trunk/ gears-read-only[/url]

定位相關的源代碼在gears/geolocation目錄中。

[b]2.關注android平臺中的基站位置變化。[/b]

JAVA類AndroidRadioDataProvider是 PhoneStateListener的子類,用來監聽Android電話的狀態變化。當服務狀態、信號強度和基站變化時,就會用下面代碼獲取小區信息:

RadioData radioData = new RadioData();
GsmCellLocation gsmCellLocation = (GsmCellLocation) cellLocation;

// Extract the cell id, LAC, and signal strength.
radioData.cellId = gsmCellLocation.getCid();
radioData.locationAreaCode = gsmCellLocation.getLac();
radioData.signalStrength = signalStrength;

// Extract the home MCC and home MNC.
String operator = telephonyManager.getSimOperator();
radioData.setMobileCodes(operator, true);

if (serviceState != null) {
// Extract the carrier name.
radioData.carrierName = serviceState.getOperatorAlphaLong();

// Extract the MCC and MNC.
operator = serviceState.getOperatorNumeric();
radioData.setMobileCodes(operator, false);
}

// Finally get the radio type.
int type = telephonyManager.getNetworkType();
if (type == TelephonyManager.NETWORK_TYPE_UMTS) {
radioData.radioType = RADIO_TYPE_WCDMA;
} else if (type == TelephonyManager.NETWORK_TYPE_GPRS
|| type == TelephonyManager.NETWORK_TYPE_EDGE) {
radioData.radioType = RADIO_TYPE_GSM;
}


然後調用用C代碼實現的onUpdateAvailable函數。

[b]2.Native函數onUpdateAvailable是在 radio_data_provider_android.cc裏實現的。[/b]

聲明Native函數
JNINativeMethod AndroidRadioDataProvider::native_methods_[] = {
{"onUpdateAvailable",
"(L" GEARS_JAVA_PACKAGE "/AndroidRadioDataProvider$RadioData;J)V",
reinterpret_cast<void*>(AndroidRadioDataProvider::OnUpdateAvailable)
},
};


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);
}
}


先判斷基站信息有沒有變化,如果有變化則通知相關的監聽者。
void AndroidRadioDataProvider::NewRadioDataAvailable(
RadioData* new_radio_data) {
bool is_update_available = false;
data_mutex_.Lock();
if (new_radio_data && !radio_data_.Matches(*new_radio_data)) {
radio_data_ = *new_radio_data;
is_update_available = true;
}
// Avoid holding the mutex locked while notifying observers.
data_mutex_.Unlock();

if (is_update_available) {
NotifyListeners();
}
}


接下來的過程,基站定位和WIFI定位是一樣的,後面我們再來介紹。下面我們先看 WIFI定位。

[b]3.關注android平臺中的WIFI變化。[/b]

JAVA類AndroidWifiDataProvider擴展了 BroadcastReceiver類,它關注WIFI掃描結果:
IntentFilter filter = new IntentFilter();
filter.addAction(mWifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
mContext.registerReceiver(this, filter, null, handler);


當收到WIFI掃描結果後,調用Native函數 onUpdateAvailable,並把WIFI的掃描結果傳遞過去。
 public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(
mWifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
if (Config.LOGV) {
Log.v(TAG, "Wifi scan resulst available");
}
onUpdateAvailable(mWifiManager.getScanResults(), mNativeObject);
}
}


[b]4.Native函數onUpdateAvailable是在 wifi_data_provider_android.cc裏實現的。[/b]
JNINativeMethod AndroidWifiDataProvider::native_methods_[] = {
{"onUpdateAvailable",
"(Ljava/util/List;J)V",
reinterpret_cast<void*>(AndroidWifiDataProvider::OnUpdateAvailable)
},
};

void AndroidWifiDataProvider::OnUpdateAvailable(JNIEnv* /* env */,
jclass /* cls */,
jobject wifi_data,
jlong self) {
assert(self);
AndroidWifiDataProvider *self_ptr =
reinterpret_cast<AndroidWifiDataProvider*>(self);
WifiData new_wifi_data;
if (wifi_data) {
InitFromJava(wifi_data, &new_wifi_data);
}
// We notify regardless of whether new_wifi_data is empty
// or not. The arbitrator will decide what to do with an empty
// WifiData object.
self_ptr->NewWifiDataAvailable(&new_wifi_data);
}

void AndroidWifiDataProvider::NewWifiDataAvailable(WifiData* new_wifi_data) {
assert(supported_);
assert(new_wifi_data);
bool is_update_available = false;
data_mutex_.Lock();
is_update_available = wifi_data_.DiffersSignificantly(*new_wifi_data);
wifi_data_ = *new_wifi_data;
// Avoid holding the mutex locked while notifying observers.
data_mutex_.Unlock();

if (is_update_available) {
is_first_scan_complete_ = true;
NotifyListeners();
}

#if USING_CCTESTS
// This is needed for running the WiFi test on the emulator.
// See wifi_data_provider_android.h for details.
if (!first_callback_made_ && wifi_data_.access_point_data.empty()) {
first_callback_made_ = true;
NotifyListeners();
}
#endif
}


從以上代碼可以看出,WIFI定位和基站定位的邏輯差不多,只是前者獲取的WIFI的掃描結果,而後者獲取的基站信息。後面代碼的基本上就統一起來了,接下來我們繼續看。

[b]5.把變化(WIFI/基站)通知給相應的監聽者。[/b]
AndroidWifiDataProvider和AndroidRadioDataProvider都是繼承了DeviceDataProviderImplBase,DeviceDataProviderImplBase的主要功能就是管理所有Listeners。

static DeviceDataProvider *Register(ListenerInterface *listener) {
MutexLock mutex(&instance_mutex_);
if (!instance_) {
instance_ = new DeviceDataProvider();
}
assert(instance_);
instance_->Ref();
instance_->AddListener(listener);
return instance_;
}

static bool Unregister(ListenerInterface *listener) {
MutexLock mutex(&instance_mutex_);
if (!instance_->RemoveListener(listener)) {
return false;
}
if (instance_->Unref()) {
delete instance_;
instance_ = NULL;
}
return true;
}


[b]6.誰在監聽變化(WIFI/基站)[/b]

NetworkLocationProvider在監聽變化(WIFI/基站):

radio_data_provider_ = RadioDataProvider::Register(this);
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:
void NetworkLocationProvider::DeviceDataUpdateAvailableImpl() {
timestamp_ = GetCurrentTimeMillis();

// Signal to the worker thread that new data is available.
is_new_data_available_ = true;
thread_notification_event_.Signal();
}


這裏面只是發了一個signal,通知另外一個線程去處理。

[b]7.誰在等待thread_notification_event_[/b]

線程函數NetworkLocationProvider::Run在一個循環中等待 thread_notification_event,當有變化(WIFI/基站)時,就準備請求服務器查詢位置。

先等待:
if (remaining_time > 0) {
thread_notification_event_.WaitWithTimeout(
static_cast<int>(remaining_time));
} else {
thread_notification_event_.Wait();
}


準備請求:
    if (make_request) {
MakeRequest();
remaining_time = 1;
}


再來看MakeRequest的實現:

先從cache中查找位置:
 const Position *cached_position =
position_cache_->FindPosition(radio_data_, wifi_data_);
data_mutex_.Unlock();
if (cached_position) {
assert(cached_position->IsGoodFix());
// Record the position and update its timestamp.
position_mutex_.Lock();
position_ = *cached_position;
position_.timestamp = timestamp_;
position_mutex_.Unlock();

// Let listeners know that we now have a position available.
UpdateListeners();
return true;
}


如果找不到,再做實際的請求
  return request_->MakeRequest(access_token,
radio_data_,
wifi_data_,
request_address_,
address_language_,
kBadLatLng, // We don't have a position to pass
kBadLatLng, // to the server.
timestamp_);


[b]7.客戶端協議包裝[/b]

前面的request_是NetworkLocationRequest實例,先看 MakeRequest的實現:

先對參數進行打包:
  if (!FormRequestBody(host_name_, access_token, radio_data, wifi_data,
request_address, address_language, latitude, longitude,
is_reverse_geocode_, &post_body_)) {
return false;
}


通知負責收發的線程
  thread_event_.Signal();


[b]8.負責收發的線程[/b]
void NetworkLocationRequest::Run() {
while (true) {
thread_event_.Wait();
if (is_shutting_down_) {
break;
}
MakeRequestImpl();
}
}

void NetworkLocationRequest::MakeRequestImpl() {
WebCacheDB::PayloadInfo payload;


把打包好的數據通過HTTP請求,發送給服務器
 scoped_refptr<BlobInterface> payload_data;
bool result = HttpPost(url_.c_str(),
false, // Not capturing, so follow redirects
NULL, // reason_header_value
HttpConstants::kMimeApplicationJson, // Content-Type
NULL, // mod_since_date
NULL, // required_cookie
true, // disable_browser_cookies
post_body_.get(),
&payload,
&payload_data,
NULL, // was_redirected
NULL, // full_redirect_url
NULL); // error_message

MutexLock lock(&is_processing_response_mutex_);
// is_aborted_ may be true even if HttpPost succeeded.
if (is_aborted_) {
LOG(("NetworkLocationRequest::Run() : HttpPost request was cancelled.\n"));
return;
}
if (listener_) {
Position position;
std::string response_body;
if (result) {
// If HttpPost succeeded, payload_data is guaranteed to be non-NULL.
assert(payload_data.get());
if (!payload_data->Length() ||
!BlobToString(payload_data.get(), &response_body)) {
LOG(("NetworkLocationRequest::Run() : Failed to get response body.\n"));
}
}


解析出位置信息
std::string16 access_token;
GetLocationFromResponse(result, payload.status_code, response_body,
timestamp_, url_, is_reverse_geocode_,
&position, &access_token);


通知位置信息的監聽者。
 bool server_error =
!result || (payload.status_code >= 500 && payload.status_code < 600);
listener_->LocationResponseAvailable(position, server_error, access_token);
}
}


有人會問,請求是發哪個服務器的?當然是google了,缺省的URL是:
static const char16 *kDefaultLocationProviderUrl =
STRING16(L"https://www.google.com/loc/json");


回過頭來,我們再總結一下:

[b]1.WIFI和基站定位過程如下:[/b]
[img]http://www.limodev.cn/gallery/albums/blog-pictures/gears_geo.JPG[/img]

[b]2.NetworkLocationProvider和 NetworkLocationRequest各有一個線程來異步處理請求。[/b]

[b]3.這裏的NetworkLocationProvider與android中的 NetworkLocationProvider並不是同一個東西,這裏是給gears用的,要在android的google map中使用,還得包裝成android中的NetworkLocationProvider的接口。[/b]

[b]4.WIFI和基站定位與平臺無關,只要你能拿到WIFI掃描結果或基站信息,而且能訪問google的定位服務器,不管你是Android平臺,Windows Mobile平臺還是傳統的feature phone,你都可以實現WIFI和基站定位。[/b]


[i]附: WIFI和基站定位原理[/i]

[i]無論是WIFI的接入點,還是移動網絡的基站設備,它們的位置基本上都是固定的。設備端(如手機)可以找到它們的ID,現在的問題就是如何通過這些ID找到對應的位置。網上的流行的說法是開車把所有每個位置都跑一遍,把這些設備的位置與 GPS測試的位置關聯起來。
[/i]

[i]參考資料:[/i]
[i]Gears:[/i][url] http://gears.googlecode.com/[/url]
[i]Google 地圖 API:[/i][url] http://code.google.com/intl/zh-CN/apis/maps/documentation/reference.html[/url]
[i]wifi定位技術:[/i][url] http://blog.csdn.net/NewMap/archive/2009/03/17/3999337.aspx
[/url]
發佈了43 篇原創文章 · 獲贊 0 · 訪問量 2670
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章