从前天学习GPS定位开始,这两天断断续续都在学习Android的三种基本定位方式。
1.GPS定位(基本Android机上都会有,缺点是必须在空旷的地方才有用)
2.网络定位(NetWork,这个很多手机上没有这个功能,比如我的联想机就没有这个功能)
4.基站定位(获取基站信息,然后向谷歌发送解析要求,解析地址)
在编写代码时,遇到了很多问题,大多数时间都是花费在一些不太注意的小问题上了,而且最后基站定位也还是有BUG(Http post不会执行)
遇到的问题:
1.权限没有添加,定位时需要的权限
1.1:
1.2:
1.3:
1.4
2.GPS定位和网络定位时,没有添加监听函数:requestLocationUpdates().
正确的做法应该是在 onResume里面注册监听函数GPS位置监听和GPS状态监听(NetWork位置监听),然后在onPause()里面取消对应的监听
3.抛出错误:ERROR/LocationManagerService(1236): java.lang.IllegalArgumentException: provider=network(这是由于手机上不支持NetWork定位功能,没办法)
4.基站定位:程序执行到HttpResponse resp = client.execute(post)时会自动抛出错误,而且Catch捕获的错误是null.
解决方法1:在主线程里面使用了耗时操作,Android4.0以后不被允许,加上一下代码即可解决:
(但是这个方法会将程序一直卡着,虽然没出错,但是我的手机运行这个程序后直接就死掉了)
/**解决4.0中不能进行网络操作要求
* 暴力方法
* 其实最好是另外单独使用一个线程,完了后告诉主线程
if (Build.VERSION.SDK_INT >= 11) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads ().detectDiskWrites().detectNetwork().penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects().detectLeakedClosableObjects().penaltyLog().penaltyDeath().build());
}
*/
解决方法2(Thread+Handler):重新开一个线程MyThread,然后在MyThread里面执行耗时操作,将结果通知MyHandler,其中创建MyHandler时,将MyHandler与主线程的Looper绑定,所以在子线程MyThread里面通过MyHandler即可发送消息给主线程,主线程只需要重写handleMessage(Message msg)函数即可接收相应的消息并进行处理。这是异步线程交互的一种最常用的方式。
***但是问题来了:在我用了(Thread+Handler)的方式后,程序执行到HttpResponse resp = client.execute(post)时还是会自动抛出错误,而且捕获不到错误(如果尝试输出错误信息又会抛出新的错误(java.lang.NullPointerException:println needs a message),因为捕获到的是null)。目前还不知道为什么会这样。
一些小结:
1.在是线程间传送消息时,用到了bundle,关于bundle的使用
1.1bundle可以直接设置一些常用的类型,String,int,boolean等
1.2当需要自定义类型Object时,需要将Object类序列化(可以Serializable也可以Parcelable,其中实现Parcelable时需要重写几个方法),需要注意的时,父类序列化时,子类也是默认序列化的。
2.关于Toast的显示,Toast是依附于Activity的,所以在Activity中的子线程里面最好不要使用Toast,如果需要显示,则有下列几种解决方法
2.1:Looper.prepare(); Toast显示内容; Looper.loop();(注意,Toast显示一定要用 .show())
2.2:使用线程见的通信方式,子线程通过MyHandle发送消息给主线程,然后在主线程里面选择对应的消息类型然后进行相应的处理。
3.子线程访问主线程中的UI界面,这里介绍两种方法:
3.1:通过MyHandler.post(new Thread())其实是只调用线程的run()方法,然不是调用start()方法来启动一个新线程,其本质是在主线程里面调用了另外一个线程的run()方法,而不是新开一个线程,所以还是不能做耗时操作。
3.2new Thread(new Runnable()).start(),新开一个线程后,通过MyHandler来给主线程发送消息,主线程发送消息后在进行相应的处理,新开了一个线程,异步执行,所以可以做耗时操作。
4.在创建Activity对应的Handler时,可以使用弱引用,避免Handler内存泄漏(OOM):
如MyHandler(Activity activity) this.activity=new WeakReference
为什么会内存泄漏:当创建一个Handler对象时,Handler对象会隐式的持有一个外部对象(通常是一个Activity的引用),而Handler通常会伴随着一个耗时的后台线程(这个后台线程在任务执行完毕,之后,通过消息机制通知Handler,然后Handler把图片更新到界面。)然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到线程结束。(如果打开一个Activiity,关闭它内存泄漏,然后继续打开,关闭。。。那么内存占用就很夸张了)
什么是WeakReference:它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向,该对象就会在被GC检查到时回收掉。比
如用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。
用到了判断用户网络状态,根据不同网络状态做不同操作,比如WIFI时加载广告,GPRS时没有广告:
代码:
/**检查网络连接状态*/
private void checkNetworkState()
{
if(LocationUtils.isNetworkAvaliable(mConnecManager))
{
//网络连接正常,判断网络状态
judgeNetworkState();
}
else//网络没有连接,进行网络连接设置
{
setNetwork();
}
}
/**网络未连接状态下,调用设置网络连接*/
private void setNetwork()
{
Toast.makeText(this, "网络连接不可用!", Toast.LENGTH_SHORT)
.show();
//下面调用设置Dialog
AlertDialog.Builder builder=new AlertDialog.Builder(this);
builder.setIcon(R.drawable.ic_notify);
builder.setTitle("网络提示信息!");
builder.setMessage("网络不可用,如果继续,请先设置网络!");
builder.setPositiveButton("设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = null;
/**
* 判断手机系统的版本!如果API大于10 就是3.0+
* 因为3.0以上的版本的设置和3.0以下的设置不一样,调用的方法不同
*/
if (android.os.Build.VERSION.SDK_INT > 10) {
intent = new Intent(android.provider.Settings.ACTION_WIFI_SETTINGS);
} else {
intent = new Intent();
ComponentName component = new ComponentName(
"com.android.settings",
"com.android.settings.WirelessSettings");
intent.setComponent(component);
intent.setAction("android.intent.action.VIEW");
}
startActivity(intent);
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.create();
builder.show();
}
/**网络已经连接,判断是wifi还是GPRS
* 设置一些自己的逻辑调用
* */
private void judgeNetworkState()
{
State gprs = mConnecManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState();
State wifi = mConnecManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState();
//如果是gprs连接
if(gprs==State.CONNECTED||gprs==State.CONNECTING)
{
Toast.makeText(this, "网络连接可用! GPRS模式!", Toast.LENGTH_SHORT)
.show();
}
//如果是wifi状态,一般wifi状态可以做一些其它的事情(如加载广告)
if(wifi==State.CONNECTED|| wifi == State.CONNECTING)
{
Toast.makeText(this, "网络连接可用! WIFI模式!", Toast.LENGTH_SHORT)
.show();
//加载广告
loadADmod();
}
}/**得到基站位置的函数*/
private void onGetTelephonyPosition()
{
Log.i(TAG,"进行基站定位!");
txt_TelephonyPosition.setText("正在通过基站获取定位!");
//开启一个获取基站定位的线程,获取成功后会自动通知Activity
//启动runnable的线程必须这样
//因为实现Runnable接口没有start方法
MyThread myTelephony=new MyThread();
new Thread(myTelephony).start();
//updateUIToNewLocation(currentLocation,myLocation,currentCellInfo);
}
/**判断网络是否连接诶*/
public static boolean isNetworkAvaliable(ConnectivityManager mConnecManager)
{
boolean flag = false;
//去进行判断网络是否连接
if (mConnecManager.getActiveNetworkInfo() != null) {
flag = mConnecManager.getActiveNetworkInfo().isAvailable();
}
return flag;
}
目前没解决的问题,代码如下:(程序执行到HttpResponse resp = client.execute(post)时还是会自动抛出错误)
线程开启方式,TelephonyDemoActivity里面的一个按钮监听函数onGetTelephonyPosition()
/**得到基站位置的函数*/
private void onGetTelephonyPosition()
{
Log.i(TAG,"进行基站定位!");
txt_TelephonyPosition.setText("正在通过基站获取定位!");
//开启一个获取基站定位的线程,获取成功后会自动通知Activity
//启动runnable的线程必须这样
//因为实现Runnable接口没有start方法
MyThread myTelephony=new MyThread();
new Thread(myTelephony).start();
}
线程类:
private class MyThread implements Runnable
{
@Override
public void run() {
//从消息池中取出一个message
Message msg = myHandler.obtainMessage();
//Bundle是message中的数据
Bundle b = new Bundle();
//设置一些数据,如Location,等等
CellIdInfoManager cellIdInfoManager;
cellIdInfoManager=new CellIdInfoManager(getBaseContext());
List currentCellInfo=cellIdInfoManager.getCellInfo();
Location currentLocation = null;
String myLocation="";
try {
currentLocation=TelePhonyNetWorkLocationManager
.getBaseStationLocation(currentCellInfo);
myLocation=TelePhonyNetWorkLocationManager.getLocation(currentLocation);
msg.arg1=1;
} catch (Exception e) {
//设置msg为解析地址错误
Log.e(TAG,"地址解析错误!");
msg.arg1=0;
}
//设置数据
b.putString("PhysicLocation", myLocation);
b.putSerializable("Location", (Serializable) currentLocation);
b.putSerializable("CellInfo", (Serializable) currentCellInfo);
msg.setData(b);
Log.i(TAG,"基站获取地址完毕,向主线程发送对应消息!");
myHandler.sendMessage(msg); // 向Handler发送消息,更新UI
}
}
/**
* 通过得到的基站信息,发送给谷歌服务器,解析并取得结果(经纬度)
*/
public static Location getBaseStationLocation(List cellID) {
if (cellID == null) {
Log.i("TAG", "cellId is null.");
return null;
}
Log.i("TAG","基站数量为:"+cellID.size());
Log.i("TAG","向google服务器解析基站地址!");
//默认的heep客户端
DefaultHttpClient client = new DefaultHttpClient();
//提出请求
HttpPost post = new HttpPost("");
//得到JSONObject 对象,并设置好各种参数
JSONObject holder = new JSONObject();
try {
Log.i("TAG","得到第一个基站!");
CellInfo info = cellID.get(0);
holder.put("version", "1.1.0");
holder.put("host", "maps.google.com");
holder.put("home_mobile_country_code", info.getMobileCountryCode());
holder.put("home_mobile_network_code", info.getMobileNetworkCode());
holder.put("request_address", true);
holder.put("radio_type", info.getRadio_type());
if ("460".equals(info.getMobileCountryCode())) {
holder.put("address_language", "zh_CN");
} else {
holder.put("address_language", "en_US");
}
//设置数据
JSONObject data, current_data;
JSONArray array = new JSONArray();
current_data = new JSONObject();
Log.i("TAG","设置第一个基站的数据!");
current_data.put("cell_id", info.getCellId());
current_data.put("location_area_code", info.getLocationAreaCode());
current_data.put("mobile_country_code", info.getMobileCountryCode());
current_data.put("mobile_network_code", info.getMobileNetworkCode());
current_data.put("age", 0);
array.put(current_data);
Log.i("TAG","第一个基站的数据设置完毕!");
if (cellID.size() > 2) {
for (int i = 1; i < cellID.size(); i++) {
Log.i("TAG","设置第"+i+1+"个基站的数据!");
data = new JSONObject();
data.put("cell_id", info.getCellId());
data.put("location_area_code", info.getLocationAreaCode());
data.put("mobile_country_code", info.getMobileCountryCode());
data.put("mobile_network_code", info.getMobileNetworkCode());
data.put("age", 0);
array.put(data);
}
}
//放置数据
holder.put("cell_towers", array);
Log.i("TAG","将基站信息放到解析对象中!");
StringEntity se = new StringEntity(holder.toString());
Log.i("TAG","得到holder的信息!"+se);
post.setEntity(se);
Log.i("TAG","设置post请求内容!"+se);
/** 发出POST数据并获取返回数据
* HttpResponse resp = client.execute(post); 这句话出问题了
* 因为Android4.0中,网络操作不能在UI线程中
* 解决方法-开启新的线程,用来处理网络连接
* 也就是说,这个函数实在新的线程里面运行
* 但是,在新的Thread里面执行这个函数,这句话也会出错。。。Why?
* */
HttpResponse resp = client.execute(post);
int state = resp.getStatusLine().getStatusCode();
Log.i("TAG","得到系统响应的状态!"+state);
if (state == HttpStatus.SC_OK) {
HttpEntity entity = resp.getEntity();
if (entity != null) {
BufferedReader br = new BufferedReader(
new InputStreamReader(entity.getContent()));
//存放结果的字符串,sb
StringBuffer sb = new StringBuffer();
String resute = "";
while ((resute = br.readLine()) != null) {
sb.append(resute);
}
br.close();
data = new JSONObject(sb.toString());
data = (JSONObject) data.get("location");
Location loc = new Location(
android.location.LocationManager.NETWORK_PROVIDER);
loc.setLatitude((Double) data.get("latitude"));
loc.setLongitude((Double) data.get("longitude"));
loc.setAccuracy(Float.parseFloat(data.get("accuracy")
.toString()));
loc.setTime(System.currentTimeMillis());
return loc;
} else {
return null;
}
}//正常的http相应处理完毕
else {
Log.v("TAG", state + "");
return null;
}
} catch (Exception e) {
// Log.e("TAG", e.getMessage());
Log.e("TAG", "未知错误");
return null;
}
}
最后,MyHandler类代码(继承Handler):在Activity的onCreate里面调用 myHandler=new MyHandler(TelephonyDemoActivity.this);
/**myHandler类,继承Handler,重写了handleMessage方法*/
private class MyHandler extends Handler
{
private Activity activity;
public MyHandler(Activity activity)
{
this.activity=new WeakReference(activity).get();//使用弱引用避免Handler内存泄露
}
//重写handleMessage,用于处理message
@Override
public void handleMessage(Message msg) {
Log.i(TAG,"主线程接收到消息!");
//选择不同的口令类型
//0-获取地址失败 1-获取地址成功
switch(msg.arg1)
{
case 0:
Toast.makeText(activity.getApplicationContext(), "解析地址出错,不能将经纬度解析为正确地址!", Toast.LENGTH_SHORT)
.show();
Update(msg);
break;
case 1:
Toast.makeText(activity.getApplicationContext(), "地址获取成功!", Toast.LENGTH_SHORT)
.show();
Update(msg);
break;
default:
Toast.makeText(activity.getApplicationContext(), "解析地址出错,未知错误!", Toast.LENGTH_SHORT)
.show();
Update(msg);
break;
}
}
/**更新UI方法*/
private void Update(Message msg)
{
//这里用于更新网络信息---更新Location
Bundle b=msg.getData();
//记得设置bundle时,将Location 键的值设为对应的location
Location currentLocation=(Location) b.getSerializable("Location");
String myLocation=b.getString("PhysicLocation");
List currentCellInfo=(List) b.getSerializable("CellInfo");
TelephonyDemoActivity.this.updateUIToNewLocation(currentLocation, myLocation, currentCellInfo);
}
}
那位大侠能解决这个问题。。。感激不尽!
完整工程路径:http://download.csdn.net/detail/u010979495/8106861