zoukankan      html  css  js  c++  java
  • Gears Android WIFI/基站定位源代码分析

    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

  • 相关阅读:
    2019-08-10T12:18:27.745963Z 7 [Note] Slave I/O thread for channel '': connected to master 'repl_user@192.168.43.81:3306',replication started in log 'mysql-bin.000001' at position 154 2019-08-10T12:18:
    yum安装的mysql 目录结构
    Starting MySQL.. ERROR! The server quit without updating PID file (/db/data/110.pid).
    CentOS7修改主机名
    使用ssh登陆远程主机
    traceroute命令
    Linux设置开机启动
    检查是否安装服务包
    CSS之盒子模型
    BFC块级格式化上下文
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167424.html
Copyright © 2011-2022 走看看