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

转载时请注明出处和作者联系方式
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静 <xianjimli at hotmail dot com>

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
wifi定位技术: http://blog.csdn.net/NewMap/archive/2009/03/17/3999337.aspx

Gears Android WIFI/基站定位源代码分析相关推荐

  1. Android 基站定位源代码

    经过几天的调研以及测试,终于解决了联通2G.移动2G.电信3G的基站定位代码.团队里面只有这些机器的制式了.下面就由我来做一个详细的讲解吧. 1 相关技术内容 Google Android Api里面 ...

  2. Android 简单基站定位程序

    原帖地址:http://www.cnblogs.com/rayee/archive/2012/02/02/2336101.html 声明 本系列文章不是教程,仅为笔记,如有不当之处请指正. 欢迎转载, ...

  3. android wifi ap 定位,通过 RTT 确定 Wi-Fi 位置信息

    您可以利用 Wi-Fi RTT(往返时间)API 提供的 Wi-Fi 位置功能测量距附近支持 RTT 的 Wi-Fi 接入点和 Wi-Fi 感知对等设备的距离. 如果您测量与三个或更多接入点的距离,可 ...

  4. Android内存泄漏定位、分析、解决全方案

    为什么会发生内存泄漏 内存空间使用完毕之后未回收, 会导致内存泄漏.有人会问:Java不是有垃圾自动回收机制么?不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑(logical leak).虽然 ...

  5. Android wifi sniffer log总结分析

    1. wifi sniffer原理 使用工作在混杂模式(promiscuous mode)的无线网卡,监听并抓取环境中收发的802.11帧,这样得到的就是wifi空口包,wifi领域也习惯称为snif ...

  6. 更新Android版GPS定位源代码

    2011年我写的这个程序做了一些简单的改动,增加了卫星数量和定位卫星数量的显示,修改了定位精度,使手机通AGPS定位能更快一点 新的下载地址:http://download.csdn.net/deta ...

  7. android gsm基站定位,通过SIM卡获取GPS,android基站定位原理

    TelephonyManager telManager=(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); GsmCellLo ...

  8. Android wifi扫描机制(Android O)

    版权声明:本文为博主原创文章,博客地址:https://blog.csdn.net/h784707460/article/details/79658950,未经博主允许不得转载. 一. Android ...

  9. Android附近基站+Wifi+IP+GPS多渠道定位方案

    wifi定位wiki:https://developers.google.com/maps/documentation/geolocation/intro 前言: 在移动客户端的开发中,地理位置定位是 ...

  10. android 定位服务和wifi,android WIFI定位和基站定位实现

    android WIFI定位和基站定位实现 来源:互联网 作者:佚名 时间:2015-04-01 13:38 关于定位原理网上很多,这里就不多说了.下面说怎么实现的,直接贴代码如下:首先是Util类: ...

最新文章

  1. proftpd的安装配置实例
  2. 新手向:Vue 2.0 的建议学习顺序
  3. Mybatis学习第一天——Mybatis的安装配置以及基本CURD操作
  4. python中与label类似的控件是_Python高级进阶教程021期 pyqt5label控件进阶使用,设置兄弟控件,广告植入...
  5. 第一章初始mybatis框架
  6. 安卓PopupWindow使用详解与源码分析(附项目实例)
  7. CKEditor 4编辑器已与Vue.js集成
  8. Flink 消息聚合处理方案
  9. Java文件上传【通用】
  10. 如何测试短信接口调用代码
  11. 自然辩证法概论国科大开卷考试
  12. 慕测安居客功能测试答案
  13. Springboot+基于知识图谱的短视频推荐系统设计与实现 毕业设计-附源码231115
  14. Python:实现gnome sortt侏儒排序算法(附完整源码)
  15. #日常---恒权码与变权码
  16. Axure中的登陆界面和动画轮播
  17. CSS子元素撑满父元素(height: 100%无效)
  18. 防火墙——GRE隧道讲解
  19. 初学者怎么记‘A‘,‘a‘,空格的ascii码?
  20. MySQL数据库软件介绍

热门文章

  1. Autodesk Inventor探索——齿轮参数化建模
  2. 大数据第一季--java基础(day5)-徐培成-专题视频课程
  3. QT软件开发:基于libVLC内核设计视频播放器
  4. php开发h5游戏教程,HTML5游戏框架cnGameJS开发实录-实现动画原理
  5. 高精度三维扫描仪用于运动鞋逆向建模
  6. 逆向-IDA工具的基本使用
  7. FlashFXP连接linux服务器(centos7环境)提示连接失败 (Unable to access SFTP sub-system, operation failed.)
  8. STEAM 正在检查可用更新 ,失败
  9. vscode的pip安装
  10. IGBT失效模式和失效现象