Android 基站定位源代码
经过几天的调研以及测试,终于解决了联通2G、移动2G、电信3G的基站定位代码。团队里面只有这些机器的制式了。下面就由我来做一个详细的讲解吧。
1 相关技术内容
Google Android Api里面的TelephonyManager的管理。
联通、移动、电信不同制式在获取基站位置的代码区别。
通过基站的基本信息,通过Google Gears获取对应的GPS经纬度。
通过Google Map API根据GPS经纬度获取当前位置。
2 目前存在的几个问题
由于得到的GPS经纬度在Google Map上面显示需要偏移,这块暂时没有进行处理。
没有使用PhoneStateListener来对状态实时进行更新。
没有使用线程异步获取数据
没有使用服务的方式来实时获取数据
所以如果是商业使用的话,还需进一步修改。
3
当然本部分代码已经移植到我们的家庭卫士的项目中了,2提到的问题全部解决了。
下面我针对第一部分的四大内容进行代码注解。
1
Google Android Api里面的TelephonyManager的管理。
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
通过这个方式就可以得到TelephonyManager接口。
这个接口的源代码可以通过设置在项目里面查看,这里不具体附上了。
得到TelephonyManager后,由于针对不同的运营商,代码有所不同,所以需要判断getNetworkType()
在源代码里面有如下的类型定义
01 |
/**
Network type is unknown */ |
02 |
public static final int NETWORK_TYPE_UNKNOWN
= 0 ; |
03 |
/**
Current network is GPRS */ |
04 |
public static final int NETWORK_TYPE_GPRS
= 1 ; |
05 |
/**
Current network is EDGE */ |
06 |
public static final int NETWORK_TYPE_EDGE
= 2 ; |
07 |
/**
Current network is UMTS */ |
08 |
public static final int NETWORK_TYPE_UMTS
= 3 ; |
09 |
/**
Current network is CDMA: Either IS95A or IS95B*/ |
10 |
public static final int NETWORK_TYPE_CDMA
= 4 ; |
11 |
/**
Current network is EVDO revision 0*/ |
12 |
public static final int NETWORK_TYPE_EVDO_0
= 5 ; |
13 |
/**
Current network is EVDO revision A*/ |
14 |
public static final int NETWORK_TYPE_EVDO_A
= 6 ; |
15 |
/**
Current network is 1xRTT*/ |
16 |
public static final int NETWORK_TYPE_1xRTT
= 7 ; |
17 |
/**
Current network is HSDPA */ |
18 |
public static final int NETWORK_TYPE_HSDPA
= 8 ; |
19 |
/**
Current network is HSUPA */ |
20 |
public static final int NETWORK_TYPE_HSUPA
= 9 ; |
21 |
/**
Current network is HSPA */ |
22 |
public static final int NETWORK_TYPE_HSPA
= 10 ; |
2 联通、移动、电信不同制式在获取基站位置的代码区别。
这部分是我实际测试出来的,经过无数次的拆机,放卡,才实现了不同制式的完美实现。
代码如下:
01 |
TelephonyManager
tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); |
02 |
int type
= tm.getNetworkType(); |
04 |
//NETWORK_TYPE_EVDO_A是中国电信3G的getNetworkType |
05 |
//NETWORK_TYPE_CDMA电信2G是CDMA |
06 |
if (type
== TelephonyManager.NETWORK_TYPE_EVDO_A || type == TelephonyManager.NETWORK_TYPE_CDMA || type ==TelephonyManager.NETWORK_TYPE_1xRTT) |
10 |
//type
= NETWORK_TYPE_EDGE |
11 |
else if (type
== TelephonyManager.NETWORK_TYPE_EDGE) |
14 |
//联通的2G经过测试
China Unicom 1 NETWORK_TYPE_GPRS |
15 |
else if (type
== TelephonyManager.NETWORK_TYPE_GPRS) |
21 |
tv.setText( "Current
Not Support This Type." ); |
3 通过基站的基本信息,通过Google
Gears获取对应的GPS经纬度。
这部分前面的两篇文章都有提到,代码参考了网友们的代码,感谢感谢。
01 |
private Location
callGear(ArrayList cellID) { |
02 |
if (cellID
== null ) return null ; |
03 |
DefaultHttpClient
client = new DefaultHttpClient(); |
04 |
HttpPost
post = new HttpPost( |
05 |
"http://www.google.com/loc/json" ); |
06 |
JSONObject
holder = new JSONObject(); |
08 |
holder.put( "version" , "1.1.0" ); |
09 |
holder.put( "host" , "maps.google.com" ); |
10 |
holder.put( "home_mobile_country_code" ,
cellID.get( 0 ).mobileCountryCode); |
11 |
holder.put( "home_mobile_network_code" ,
cellID.get( 0 ).mobileNetworkCode); |
12 |
holder.put( "radio_type" ,
cellID.get( 0 ).radioType); |
13 |
holder.put( "request_address" , true ); |
14 |
if ( "460" .equals(cellID.get( 0 ).mobileCountryCode)) |
15 |
holder.put( "address_language" , "zh_CN" ); |
17 |
holder.put( "address_language" , "en_US" ); |
18 |
JSONObject
data,current_data; |
19 |
JSONArray
array = new JSONArray(); |
20 |
current_data
= new JSONObject(); |
21 |
current_data.put( "cell_id" ,
cellID.get( 0 ).cellId); |
22 |
current_data.put( "location_area_code" ,
cellID.get( 0 ).locationAreaCode); |
23 |
current_data.put( "mobile_country_code" ,
cellID.get( 0 ).mobileCountryCode); |
24 |
current_data.put( "mobile_network_code" ,
cellID.get( 0 ).mobileNetworkCode); |
25 |
current_data.put( "age" , 0 ); |
26 |
array.put(current_data); |
27 |
if (cellID.size()
> 2 )
{ |
28 |
for ( int i
= 1 ;
i < cellID.size(); i++) { |
29 |
data
= new JSONObject(); |
30 |
data.put( "cell_id" ,
cellID.get(i).cellId); |
31 |
data.put( "location_area_code" ,
cellID.get(i).locationAreaCode); |
32 |
data.put( "mobile_country_code" ,
cellID.get(i).mobileCountryCode); |
33 |
data.put( "mobile_network_code" ,
cellID.get(i).mobileNetworkCode); |
38 |
holder.put( "cell_towers" ,
array); |
39 |
StringEntity
se = new StringEntity(holder.toString()); |
40 |
Log.e( "Location
send" ,
holder.toString()); |
42 |
HttpResponse
resp = client.execute(post); |
43 |
HttpEntity
entity = resp.getEntity(); |
45 |
BufferedReader
br = new BufferedReader( |
46 |
new InputStreamReader(entity.getContent())); |
47 |
StringBuffer
sb = new StringBuffer(); |
48 |
String
result = br.readLine(); |
49 |
while (result
!= null )
{ |
50 |
Log.e( "Locaiton
receive" ,
result); |
52 |
result
= br.readLine(); |
54 |
if (sb.length() return null ; |
55 |
data
= new JSONObject(sb.toString()); |
56 |
data
= (JSONObject) data.get( "location" ); |
58 |
Location
loc = new Location(LocationManager.NETWORK_PROVIDER); |
59 |
loc.setLatitude((Double)
data.get( "latitude" )); |
60 |
loc.setLongitude((Double)
data.get( "longitude" )); |
61 |
loc.setAccuracy(Float.parseFloat(data.get( "accuracy" ).toString())); |
62 |
loc.setTime(GetUTCTime()); |
64 |
} catch (JSONException
e) { |
66 |
} catch (UnsupportedEncodingException
e) { |
68 |
} catch (ClientProtocolException
e) { |
70 |
} catch (IOException
e) { |
4 通过Google Map API根据GPS经纬度获取当前位置。
本部分代码参考了 简单基站定位程序 ,感谢雷一兄这么好的文章。同时雷一兄的排版真的非常好看,清晰明了。
01 |
private String
getLocation(Location itude) throws Exception
{ |
02 |
String
resultString = "" ; |
04 |
/**
这里采用get方法,直接将参数加到URL上 */ |
05 |
String
urlString = String.format( "http://maps.google.cn/maps/geo?key=abcdefg&q=%s,%s" ,
itude.getLatitude(), itude.getLongitude()); |
06 |
Log.i( "URL" ,
urlString); |
09 |
HttpClient
client = new DefaultHttpClient(); |
11 |
HttpGet
get = new HttpGet(urlString); |
14 |
HttpResponse
response = client.execute(get); |
15 |
HttpEntity
entity = response.getEntity(); |
16 |
BufferedReader
buffReader = new BufferedReader( new InputStreamReader(entity.getContent())); |
17 |
StringBuffer
strBuff = new StringBuffer(); |
19 |
while ((result
= buffReader.readLine()) != null )
{ |
20 |
strBuff.append(result); |
22 |
resultString
= strBuff.toString(); |
24 |
/**
解析JSON数据,获得物理地址 */ |
25 |
if (resultString
!= null &&
resultString.length() > 0 )
{ |
26 |
JSONObject
jsonobject = new JSONObject(resultString); |
27 |
JSONArray
jsonArray = new JSONArray(jsonobject.get( "Placemark" ).toString()); |
29 |
for ( int i
= 0 ;
i < jsonArray.length(); i++) { |
30 |
resultString
= jsonArray.getJSONObject(i).getString( "address" ); |
33 |
} catch (Exception
e) { |
34 |
throw new Exception( "获取物理位置出现错误:" +
e.getMessage()); |
5
最关键的出来了,附上代码吧。
AndroidPosition
补充一下:
在AndroidMenifest.xml里面需要加上
android.permission.INTERNET、android.permission.ACCESS_COARSE_LOCATION、android.permission.READ_PHONE_STATE权限,否则会出错。
放在Application包前面。
6
图片看一下效果吧。
7
另外在提交数据到Google Gears的时候,格式如下
发送到Google的数据格式:
02-24 18:08:20.550: E/Location send(12892): {“address_language”:”zh_CN”,”host”:”maps.google.com”,”radio_type”:”cdma”,”home_mobile_country_code”:”460″,”home_mobile_network_code”:”13965″,”cell_towers”:[{"mobile_network_code":"13965","location_area_code":11,"cell_id":1985,"age":0,"mobile_country_code":"460"}],”request_address”:true,”version”:”1.1.0″}
接收到Google的数据格式:
02-24 18:08:22.975: E/Locaiton receive(12892): {“location”:{“latitude”:43.8595097,”longitude”:125.3355736,”address”: {“country”:”中国”,”country_code”:”CN”,”region”:”吉林省”,”city”:”长春市”,”street”:”文昌路”,”street_number”:”"},”accuracy”:1815.0},”access_token”:”2:_Kpk9mOFMgyWgLai:8iWlDpBYZsp4_VxO”}
-End-
前言
经过前面几节的学习,我们已经对Android程序的开发流程有了个大体的了解,为了提高我们的学习兴趣,在这一节我们将编写一个简单的基站定位程序。现在LBS(Location Based Service,基于位置的服务)移动应用相当流行(如:微信,切客,嘀咕,街旁等),基站定位是这类程序用到的关键性技术之一,我们来揭开它的神秘面纱吧。
在这一节里,我们会接触到事件、TelephonyManager、HTTP通信、JSON的使用等知识点。
声明
本系列文章不是教程,仅为笔记,如有不当之处请指正。
欢迎转载,转载请保留原出处:http://www.cnblogs.com/rayee
目录
一、设置界面
二、为按钮绑定事件
三、获取基站信息
四、获取经纬度
五、获取物理位置
六、显示结果
七、运行程序
八、总结
九、程序代码
正文
在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的界面上实现这个功能。(没有代码的同学可点击这里下载,感谢yuejianjun同学的建议,以后我会在每一节的最后把例子打包提供下载)
首先我们将DemoActivity使用的布局修改一下:
第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数据,我们得到数据后解析,得到经纬度信息。
关于google 基站信息API的官方说明>>请到这里查看。
五、获取物理位置
得到经纬度后,我们将之转换为物理地址。
我们仍然使用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”,出错了!
详细的错误信息为: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”后程序会卡住,等待一段时间才有反应,取决于网络情况):
成功啦!
可能有的同学还是出现错误,没有成功:
█ 提示“www.google.com…”什么的错误
请确认你的手机能访问互联网,调用google的API是必须联网的。
█ 提示获取不到基站信息
你确定你是在手机上测试的吗?模拟器可不行哦。或者你的手机使用的CMDA网络?这个例子只支持GSM网络…
█ 获取不到经纬度
很有可能你中奖了,你所在的基站还没纳入google的数据库…(话说我之前也遇到过,怎么查就是查不出经纬度来,返回数据为空)
█ 获取到的地理地址不正确
这个可能程序出错了,可能google出错了?
其实google map API返回的数据中还包含了很多其他信息,我们可以用来开发一些更有趣的功能,如制作我们专属的地图软件、足迹记录软件等,充分发挥你的创造力:)
八、总结
这个程序基本实现了基站定位功能,但还有很多问题,如:点击了按钮后界面会卡住(访问网络时阻塞了进程)、未对异常进一步处理、不兼容CMDA网络等。
另外这个程序的精度也不够,获得的位置实际上是基站的物理位置,与人所在的位置还有一定差距。在城市里面,一般采用密集型的小功率基站,精度一般在几百米范围内,而在郊区常为大功率基站,密度很小,精度一般在几千米以上。
想要取得更高的精度需要通过一些其他的算法来实现,如果大家有兴趣的话我们可以一起来研究一下,再专门写篇笔记。
可见写一段程序和做一个实际的产品是有很大差别的。
九、程序代码
这一节完整程序的请点击这里下载。
结尾
这一节基本实现了最简单的基站定位,只是作为学习的例子,远远达不到产品的要求,请大家见谅。
我们进一步熟悉了JAVA编码,之前没怎么接触JAVA看起来有点吃力的同学建议找点JAVA基础的书来看看。
话说我这段代码也是写得乱七八糟,JAVA没系统学习过,不知道JAVA编程的习惯,命名规则等,大家见笑了。
相对前面几节来说这一节没那么详细了,我也很困惑:详细点呢进度很慢,进度提上去了又怕不够详细,看看大家的意见,请留言哈。
下一节我们回归理论知识,一起深入学习Android的Activity组件。
如果你喜欢看到这些文字,请点击右下角的“推荐”支持我一下,谢谢!
安卓手机定位研究,布布扣,bubuko.com
时间: 2024-09-30 09:57:26