欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android开发笔记之简单基站定位程序的实现

程序员文章站 2024-02-12 14:48:16
经过学习,已经对android程序的开发流程有了个大体的了解,为了提高我们的学习兴趣,在这一节我们将编写一个简单的基站定位程序。现在lbs(location based s...

经过学习,已经对android程序的开发流程有了个大体的了解,为了提高我们的学习兴趣,在这一节我们将编写一个简单的基站定位程序。现在lbs(location based service,基于位置的服务)移动应用相当流行(如:微信,切客,嘀咕,街旁等),基站定位是这类程序用到的关键性技术之一,我们来揭开它的神秘面纱吧。

在这一节里,我们会接触到事件、telephonymanager、http通信、json的使用等知识点。

在android操作系统下,基站定位其实很简单,先说一下实现流程:

调用sdk中的api(telephonymanager)获得mcc、mnc、lac、cid等信息,然后通过google的api获得所在位置的经纬度,最后再通过google map的api获得实际的地理位置。(google真牛!)

有同学会问:mnc、mcc、lac、cid都是些什么东西?google又怎么通过这些东西就获得经纬度了呢?

我们一起来学习一下:

mcc,mobile country code,移动国家代码(中国的为460);

mnc,mobile network code,移动网络号码(中国移动为00,中国联通为01);

lac,location area code,位置区域码;

cid,cell identity,基站编号,是个16位的数据(范围是0到65535)。

了解了这几个名词的意思,相信有些朋友已经知道后面的事了:google存储了这些信息,直接查询就能得到经纬度了。(至于google怎么得到移动、联通的基站信息,这就不得而知了,反正google免费提供接口,直接调用就是)

下面开始动手。

一、设置界面

我们在上一节的程序的基础上进行开发,在demoactivity的界面上实现这个功能。

首先我们将demoactivity使用的布局修改一下:

Android开发笔记之简单基站定位程序的实现

第1行为textview,显示提示文字;第2行为一个button,触发事件;第3行、第4行分别显示基站信息和地理位置(现在为空,看不到)。

layout/main.xml文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical" >
 
  <textview
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="please click the button below to get your location" />
 
  <button
    android:id="@+id/button1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="click me" />
 
  <textview
    android:id="@+id/celltext"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="" />
 
  <textview
    android:id="@+id/lacationtext"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="" />
 
</linearlayout>

接下来我们打开demoactivity.java编写代码。

二、为按钮绑定事件

我们在activity创建时绑定事件,将以下代码添加到setcontentview(r.layout.main);后:

/** 为按钮绑定事件 */
button btngetlocation = (button)findviewbyid(r.id.button1);
btngetlocation.setonclicklistener(new onclicklistener() {
  @override
  public void onclick(view arg0) {
    // todo auto-generated method stub
    onbtnclick();
  }
});

同时还需要在头部import相关组件:

import android.view.view;
import android.widget.button;
import android.view.view.onclicklistener;

我们来分析一下这段代码:

首先我们通过findviewbyid(r.id.button1)找到按钮这个对象,前面加(button)表示显示的转换为button对象;

然后设置按钮点击事件的监听器,参数为onclicklistener对象,再重载这个类的onclick方法,调用onbtnclick方法(这个方法得由我们自己去写,他在点击按钮时被调用)。

好了,调用方法写好了,我们来写实现(调用后需要做什么事)。动手编码之前先在脑中整理好思路,养成好习惯。

我们需要在demoactivty类中添加如下私有方法:

我们需要刚刚提到的onbtnclick回调方法,被调用时实现取得基站信息、获取经纬度、获取地理位置、显示的功能。但是很显然,全部揉到一个方法里面并不是个好主意,我们将它分割为几个方法;

  • 添加获取基站信息的方法getcellinfo,返回基站信息;
  • 添加获取经纬度的方法getitude,传入基站信息,返回经纬度;
  • 添加获取地理位置的方法getlocation,传入经纬度,返回地理位置;
  • 添加显示结果的方法showresult,传入得到的信息在界面上显示出来。

好了,先将方法添上,完整代码如下:

package com.android.demo;
 
import android.r.bool;
import android.r.integer;
import android.app.activity;
import android.os.bundle;
import android.view.view;
import android.widget.button;
import android.view.view.onclicklistener;
 
public class demoactivity extends activity {
  /** called when the activity is first created. */
  @override
  public void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.main);
     
    /** 为按钮绑定事件 */
    button btngetlocation = (button)findviewbyid(r.id.button1);
    btngetlocation.setonclicklistener(new onclicklistener() {
      @override
      public void onclick(view arg0) {
        // todo auto-generated method stub
        onbtnclick();
      }
    });
  }
   
  /** 基站信息结构体 */
  public class scell{
    public int mcc;
    public int mnc;
    public int lac;
    public int cid;
  }
   
  /** 经纬度信息结构体 */
  public class situde{
    public string latitude;
    public string longitude;
  }
   
  /** 按钮点击回调函数 */
  private void onbtnclick(){
     
  }
   
  /** 获取基站信息 */
  private scell getcellinfo(){
 
  }
   
  /** 获取经纬度 */
  private situde getitude(scell cell){
     
  }
   
  /** 获取地理位置 */
  private string getlocation(situde itude){
     
  }
   
  /** 显示结果 */
  private void showresult(scell cell, string location){
     
  }
}

现在在onbtnclick方法中编码,依次调用后面几个方法,代码如下:

/** 按钮点击回调函数 */
private void onbtnclick(){
  /** 弹出一个等待状态的框 */
  progressdialog mprogressdialog = new progressdialog(this);
  mprogressdialog.setmessage("正在获取中...");
  mprogressdialog.setprogressstyle(progressdialog.style_spinner);
  mprogressdialog.show();
   
  try {
    /** 获取基站数据 */
    scell cell = getcellinfo();
     
    /** 根据基站数据获取经纬度 */
    situde itude = getitude(cell);
     
    /** 获取地理位置 */
    string location = getlocation(itude);
     
    /** 显示结果 */
    showresult(cell, location);
     
    /** 关闭对话框 */
    mprogressdialog.dismiss();
  }catch (exception e) {
    /** 关闭对话框 */
    mprogressdialog.dismiss();
    /** 显示错误 */
    textview celltext = (textview)findviewbyid(r.id.celltext);
    celltext.settext(e.getmessage());
  }
}

按钮相关的工作就完成了,接下来编写获取基站信息的方法。

三、获取基站信息

获取基站信息我们需要调用sdk提供的api中的telephonymanager,需要在文件头部引入:

import android.telephony.telephonymanager;
import android.telephony.gsm.gsmcelllocation;

完整代码为:

/**
 * 获取基站信息
 * 
 * @throws exception
 */
private scell getcellinfo() throws exception {
  scell cell = new scell();
 
  /** 调用api获取基站信息 */
  telephonymanager mtelnet = (telephonymanager) getsystemservice(context.telephony_service);
  gsmcelllocation location = (gsmcelllocation) mtelnet.getcelllocation();
  if (location == null)
    throw new exception("获取基站信息失败");
 
  string operator = mtelnet.getnetworkoperator();
  int mcc = integer.parseint(operator.substring(0, 3));
  int mnc = integer.parseint(operator.substring(3));
  int cid = location.getcid();
  int lac = location.getlac();
 
  /** 将获得的数据放到结构体中 */
  cell.mcc = mcc;
  cell.mnc = mnc;
  cell.lac = lac;
  cell.cid = cid;
 
  return cell;
}

如果获得的位置信息为null将抛出错误,不再继续执行。最后将获取的基站信息封装为结构体返回。

四、获取经纬度

在这一步,我们需要采用http调用google的api以获取基站所在的经纬度。

android作为一款互联网手机,联网的功能必不可少。android提供了多个接口供我们使用,这里我们使用defaulthttpclient。

完整的方法代码如下:

/**
 * 获取经纬度
 * 
 * @throws exception
 */
private situde getitude(scell cell) throws exception {
  situde itude = new situde();
 
  /** 采用android默认的httpclient */
  httpclient client = new defaulthttpclient();
  /** 采用post方法 */
  httppost post = new httppost("http://www.google.com/loc/json");
  try {
    /** 构造post的json数据 */
    jsonobject holder = new jsonobject();
    holder.put("version", "1.1.0");
    holder.put("host", "maps.google.com");
    holder.put("address_language", "zh_cn");
    holder.put("request_address", true);
    holder.put("radio_type", "gsm");
    holder.put("carrier", "htc");
 
    jsonobject tower = new jsonobject();
    tower.put("mobile_country_code", cell.mcc);
    tower.put("mobile_network_code", cell.mnc);
    tower.put("cell_id", cell.cid);
    tower.put("location_area_code", cell.lac);
 
    jsonarray towerarray = new jsonarray();
    towerarray.put(tower);
    holder.put("cell_towers", towerarray);
 
    stringentity query = new stringentity(holder.tostring());
    post.setentity(query);
 
    /** 发出post数据并获取返回数据 */
    httpresponse response = client.execute(post);
    httpentity entity = response.getentity();
    bufferedreader buffreader = new bufferedreader(new inputstreamreader(entity.getcontent()));
    stringbuffer strbuff = new stringbuffer();
    string result = null;
    while ((result = buffreader.readline()) != null) {
      strbuff.append(result);
    }
 
    /** 解析返回的json数据获得经纬度 */
    jsonobject json = new jsonobject(strbuff.tostring());
    jsonobject subjosn = new jsonobject(json.getstring("location"));
 
    itude.latitude = subjosn.getstring("latitude");
    itude.longitude = subjosn.getstring("longitude");
     
    log.i("itude", itude.latitude + itude.longitude);
     
  } catch (exception e) {
    log.e(e.getmessage(), e.tostring());
    throw new exception("获取经纬度出现错误:"+e.getmessage());
  } finally{
    post.abort();
    client = null;
  }
   
  return itude;
}

在这里采用post方法将json数据发送到googleapi,google返回json数据,我们得到数据后解析,得到经纬度信息。

五、获取物理位置

得到经纬度后,我们将之转换为物理地址。

我们仍然使用defaulthttpclient来调用google地图的api,获得物理信息,不过在这里我们使用get方法。

完整的方法代码如下:

/**
 * 获取地理位置
 * 
 * @throws exception
 */
private string getlocation(situde itude) throws exception {
  string resultstring = "";
 
  /** 这里采用get方法,直接将参数加到url上 */
  string urlstring = string.format("http://maps.google.cn/maps/geo?key=abcdefg&q=%s,%s", itude.latitude, itude.longitude);
  log.i("url", urlstring);
 
  /** 新建httpclient */
  httpclient client = new defaulthttpclient();
  /** 采用get方法 */
  httpget get = new httpget(urlstring);
  try {
    /** 发起get请求并获得返回数据 */
    httpresponse response = client.execute(get);
    httpentity entity = response.getentity();
    bufferedreader buffreader = new bufferedreader(new inputstreamreader(entity.getcontent()));
    stringbuffer strbuff = new stringbuffer();
    string result = null;
    while ((result = buffreader.readline()) != null) {
      strbuff.append(result);
    }
    resultstring = strbuff.tostring();
 
    /** 解析json数据,获得物理地址 */
    if (resultstring != null && resultstring.length() > 0) {
      jsonobject jsonobject = new jsonobject(resultstring);
      jsonarray jsonarray = new jsonarray(jsonobject.get("placemark").tostring());
      resultstring = "";
      for (int i = 0; i < jsonarray.length(); i++) {
        resultstring = jsonarray.getjsonobject(i).getstring("address");
      }
    }
  } catch (exception e) {
    throw new exception("获取物理位置出现错误:" + e.getmessage());
  } finally {
    get.abort();
    client = null;
  }
 
  return resultstring;
}

get方法就比post方法简单多了,得到的数据同样为json格式,解析一下得到物理地址。

六、显示结果

好了,我们已经得到我们想要的信息了,我们把它显示出来,方法代码如下:

/** 显示结果 */
private void showresult(scell cell, string location) {
  textview celltext = (textview) findviewbyid(r.id.celltext);
  celltext.settext(string.format("基站信息:mcc:%d, mnc:%d, lac:%d, cid:%d",
      cell.mcc, cell.mnc, cell.lac, cell.cid));
 
  textview locationtext = (textview) findviewbyid(r.id.lacationtext);
  locationtext.settext("物理位置:" + location);
}

七、运行程序

我们的编码工作已经完成了。在上面的代码中有些地方需要的引入代码没有提到,下面把完整的代码贴出来:

package com.android.demo;
 
import java.io.bufferedreader;
import java.io.inputstreamreader;
 
import org.apache.http.httpentity;
import org.apache.http.httpresponse;
import org.apache.http.client.httpclient;
import org.apache.http.client.methods.httpget;
import org.apache.http.client.methods.httppost;
import org.apache.http.entity.stringentity;
import org.apache.http.impl.client.defaulthttpclient;
 
import org.json.jsonarray;
import org.json.jsonobject;
 
import android.app.activity;
import android.app.progressdialog;
import android.content.context;
import android.os.bundle;
import android.telephony.telephonymanager;
import android.telephony.gsm.gsmcelllocation;
import android.util.log;
import android.view.view;
import android.widget.button;
import android.widget.textview;
import android.view.view.onclicklistener;
 
public class demoactivity extends activity {
  /** called when the activity is first created. */
  @override
  public void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.main);
 
    /** 为按钮绑定事件 */
    button btngetlocation = (button) findviewbyid(r.id.button1);
    btngetlocation.setonclicklistener(new onclicklistener() {
      @override
      public void onclick(view arg0) {
        // todo auto-generated method stub
        onbtnclick();
      }
    });
  }
   
  /** 基站信息结构体 */
  public class scell{
    public int mcc;
    public int mnc;
    public int lac;
    public int cid;
  }
   
  /** 经纬度信息结构体 */
  public class situde{
    public string latitude;
    public string longitude;
  }
   
  /** 按钮点击回调函数 */
  private void onbtnclick() {
    /** 弹出一个等待状态的框 */
    progressdialog mprogressdialog = new progressdialog(this);
    mprogressdialog.setmessage("正在获取中...");
    mprogressdialog.setprogressstyle(progressdialog.style_spinner);
    mprogressdialog.show();
 
    try {
      /** 获取基站数据 */
      scell cell = getcellinfo();
 
      /** 根据基站数据获取经纬度 */
      situde itude = getitude(cell);
 
      /** 获取地理位置 */
      string location = getlocation(itude);
 
      /** 显示结果 */
      showresult(cell, location);
 
      /** 关闭对话框 */
      mprogressdialog.dismiss();
    } catch (exception e) {
      /** 关闭对话框 */
      mprogressdialog.dismiss();
      /** 显示错误 */
      textview celltext = (textview) findviewbyid(r.id.celltext);
      celltext.settext(e.getmessage());
      log.e("error", e.getmessage());
    }
  }
   
  /**
   * 获取基站信息
   * 
   * @throws exception
   */
  private scell getcellinfo() throws exception {
    scell cell = new scell();
 
    /** 调用api获取基站信息 */
    telephonymanager mtelnet = (telephonymanager) getsystemservice(context.telephony_service);
    gsmcelllocation location = (gsmcelllocation) mtelnet.getcelllocation();
    if (location == null)
      throw new exception("获取基站信息失败");
 
    string operator = mtelnet.getnetworkoperator();
    int mcc = integer.parseint(operator.substring(0, 3));
    int mnc = integer.parseint(operator.substring(3));
    int cid = location.getcid();
    int lac = location.getlac();
 
    /** 将获得的数据放到结构体中 */
    cell.mcc = mcc;
    cell.mnc = mnc;
    cell.lac = lac;
    cell.cid = cid;
 
    return cell;
  }
   
  /**
   * 获取经纬度
   * 
   * @throws exception
   */
  private situde getitude(scell cell) throws exception {
    situde itude = new situde();
 
    /** 采用android默认的httpclient */
    httpclient client = new defaulthttpclient();
    /** 采用post方法 */
    httppost post = new httppost("http://www.google.com/loc/json");
    try {
      /** 构造post的json数据 */
      jsonobject holder = new jsonobject();
      holder.put("version", "1.1.0");
      holder.put("host", "maps.google.com");
      holder.put("address_language", "zh_cn");
      holder.put("request_address", true);
      holder.put("radio_type", "gsm");
      holder.put("carrier", "htc");
 
      jsonobject tower = new jsonobject();
      tower.put("mobile_country_code", cell.mcc);
      tower.put("mobile_network_code", cell.mnc);
      tower.put("cell_id", cell.cid);
      tower.put("location_area_code", cell.lac);
 
      jsonarray towerarray = new jsonarray();
      towerarray.put(tower);
      holder.put("cell_towers", towerarray);
 
      stringentity query = new stringentity(holder.tostring());
      post.setentity(query);
 
      /** 发出post数据并获取返回数据 */
      httpresponse response = client.execute(post);
      httpentity entity = response.getentity();
      bufferedreader buffreader = new bufferedreader(new inputstreamreader(entity.getcontent()));
      stringbuffer strbuff = new stringbuffer();
      string result = null;
      while ((result = buffreader.readline()) != null) {
        strbuff.append(result);
      }
 
      /** 解析返回的json数据获得经纬度 */
      jsonobject json = new jsonobject(strbuff.tostring());
      jsonobject subjosn = new jsonobject(json.getstring("location"));
 
      itude.latitude = subjosn.getstring("latitude");
      itude.longitude = subjosn.getstring("longitude");
       
      log.i("itude", itude.latitude + itude.longitude);
       
    } catch (exception e) {
      log.e(e.getmessage(), e.tostring());
      throw new exception("获取经纬度出现错误:"+e.getmessage());
    } finally{
      post.abort();
      client = null;
    }
     
    return itude;
  }
   
  /**
   * 获取地理位置
   * 
   * @throws exception
   */
  private string getlocation(situde itude) throws exception {
    string resultstring = "";
 
    /** 这里采用get方法,直接将参数加到url上 */
    string urlstring = string.format("http://maps.google.cn/maps/geo?key=abcdefg&q=%s,%s", itude.latitude, itude.longitude);
    log.i("url", urlstring);
 
    /** 新建httpclient */
    httpclient client = new defaulthttpclient();
    /** 采用get方法 */
    httpget get = new httpget(urlstring);
    try {
      /** 发起get请求并获得返回数据 */
      httpresponse response = client.execute(get);
      httpentity entity = response.getentity();
      bufferedreader buffreader = new bufferedreader(new inputstreamreader(entity.getcontent()));
      stringbuffer strbuff = new stringbuffer();
      string result = null;
      while ((result = buffreader.readline()) != null) {
        strbuff.append(result);
      }
      resultstring = strbuff.tostring();
 
      /** 解析json数据,获得物理地址 */
      if (resultstring != null && resultstring.length() > 0) {
        jsonobject jsonobject = new jsonobject(resultstring);
        jsonarray jsonarray = new jsonarray(jsonobject.get("placemark").tostring());
        resultstring = "";
        for (int i = 0; i < jsonarray.length(); i++) {
          resultstring = jsonarray.getjsonobject(i).getstring("address");
        }
      }
    } catch (exception e) {
      throw new exception("获取物理位置出现错误:" + e.getmessage());
    } finally {
      get.abort();
      client = null;
    }
 
    return resultstring;
  }
   
  /** 显示结果 */
  private void showresult(scell cell, string location) {
    textview celltext = (textview) findviewbyid(r.id.celltext);
    celltext.settext(string.format("基站信息:mcc:%d, mnc:%d, lac:%d, cid:%d",
        cell.mcc, cell.mnc, cell.lac, cell.cid));
 
    textview locationtext = (textview) findviewbyid(r.id.lacationtext);
    locationtext.settext("物理位置:" + location);
  }
}

我们连上手机在手机上运行程序看看。

不出意外的话程序运行起来了,自动跳转到了主界面。点击“click me”,出错了!

Android开发笔记之简单基站定位程序的实现

详细的错误信息为:neither user 10078 nor current process has android.permission.access_coarse_location.

原来是没有权限,经过前面的学习,我们知道android在应用的安全上下了一番功夫,要用一些特殊功能必须先报告,安装应用的时候列给用户看,必须要得到用户的允许。这里我们用了获取基站信息的功能,涉及到用户的隐私了,所以我们必须申明一下。

打开androidmanifest.xml配置文件,在里面添加相应的配置信息:

<uses-permission android:name="android.permission.access_fine_location"></uses-permission>

我们继续把网络连接的权限申明也加上:

<uses-permission android:name="android.permission.internet"></uses-permission>

再编译运行看看(点击“click me”后程序会卡住,等待一段时间才有反应,取决于网络情况):

Android开发笔记之简单基站定位程序的实现

成功啦!

可能有的同学还是出现错误,没有成功:

█ 提示“www.google.com…”什么的错误

请确认你的手机能访问互联网,调用google的api是必须联网的。

█ 提示获取不到基站信息

你确定你是在手机上测试的吗?模拟器可不行哦。或者你的手机使用的cmda网络?这个例子只支持gsm网络…

█ 获取不到经纬度

很有可能你中奖了,你所在的基站还没纳入google的数据库…(话说我之前也遇到过,怎么查就是查不出经纬度来,返回数据为空)

█ 获取到的地理地址不正确

这个可能程序出错了,可能google出错了?

其实google map api返回的数据中还包含了很多其他信息,我们可以用来开发一些更有趣的功能,如制作我们专属的地图软件、足迹记录软件等,充分发挥你的创造力:)

八、总结

这个程序基本实现了基站定位功能,但还有很多问题,如:点击了按钮后界面会卡住(访问网络时阻塞了进程)、未对异常进一步处理、不兼容cmda网络等。

另外这个程序的精度也不够,获得的位置实际上是基站的物理位置,与人所在的位置还有一定差距。在城市里面,一般采用密集型的小功率基站,精度一般在几百米范围内,而在郊区常为大功率基站,密度很小,精度一般在几千米以上。

想要取得更高的精度需要通过一些其他的算法来实现,如果大家有兴趣的话我们可以一起来研究一下,再专门写篇笔记。

可见写一段程序和做一个实际的产品是有很大差别的。

结尾
这一节基本实现了最简单的基站定位,只是作为学习的例子,远远达不到产品的要求,请大家见谅。

我们进一步熟悉了java编码,之前没怎么接触java看起来有点吃力的同学建议找点java基础的书来看看。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。