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


转载时请注明出处和作者联系方式


Broncho A1还不支持基站和WIFI定位,Android的老版本里是有NetworkLocationProvider的,它实现了基站和WIFI定位,但从 android 1.5之后就被移除了。本来想在broncho A1里自己实现NetworkLocationProvider的,但一直没有时间去研究。我知道 gears(http://code.google.com/p/gears/)是有提供类似的功能,昨天研究了一下Gears的代码,看能不能移植到 android中来。


1.下载源代码

svn checkout http://gears.googlecode.com/svn/trunk/ gears-read-only


定位相关的源代码在gears/geolocation目录中。


2.关注android平台中的基站位置变化。


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函数。


2.Native函数onUpdateAvailable是在radio_data_provider_android.cc里实现的。


声明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定位。


3.关注android平台中的WIFI变化。


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

    }

  }


4.Native函数onUpdateAvailable是在wifi_data_provider_android.cc里实现的。


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的扫描结果,而后者获取的基站信息。后面代码的基本上就统一起来了,接下来我们继续看。


5.把变化(WIFI/基站)通知给相应的监听者。


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;

  }


6.谁在监听变化(WIFI/基站)


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,通知另外一个线程去处理。


7.谁在等待thread_notification_event_


线程函数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_);


7.客户端协议包装


前面的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();


8.负责收发的线程


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


回过头来,我们再总结一下:


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


参考资料:

Gears: http://gears.googlecode.com/

Google 地图 API: http://code.google.com/intl/zh-CN/apis/maps/documentation/reference.html