使用百度地图API进行定位,当接收到定位信息后,为LocationClient对象注册的BDLocationListener会回调onReceiveLocation方法。在onReceiveLocation的BDLocation对象中获得地理信息后,将其设置到TextView上显示。程序并没有报错,但是显示发生了异常。
代码如下:
public void onReceiveLocation(BDLocation bdLocation) {
final StringBuilder sb=new StringBuilder();
sb.append("纬度: ").append(bdLocation.getLatitude()).append("\n");
sb.append("经度: ").append(bdLocation.getLongitude()).append("\n");
sb.append("定位方式: ");
if (bdLocation.getLocType()==BDLocation.TypeGpsLocation){
sb.append("GPS");
}else if (bdLocation.getLocType()==BDLocation.TypeNetWorkLocation){
sb.append("网络");
}
textView.setText(sb.toString());
}
初次检查后,发现代码没有大问题,于是我开始怀疑onReceiveLocation的执行线程,于是添加了如下输出。
在Fragment(活动中内嵌Fragment,而TextView是放在Fragment的布局文件中的)的onCreateView中添加如下代码:
long uId=Thread.currentThread().getId();
Log.d("Fragment", "onCreateView: current Thread id--"+uId);
在主活动的onCreate中添加如下代码:
在onReceiveLocation中添加如下代码:long uId=Thread.currentThread().getId(); Log.d("MainActivity", "onCreate: current thread id-- "+uId);
long uId=Thread.currentThread().getId();
String tmpName=Thread.currentThread().getClass().getName();
Log.d(TAG, "onReceiveLocation: current thread id--"+uId);
Log.d(TAG, "onReceiveLocation: current thread name--"+tmpName);
执行程序,观察控制台,有如下的输出:
D/MainActivity: onCreate: current thread id-- 1
D/PositionFragment: onCreateView: current Thread id--1
D/PositionFragment: onReceiveLocation: current thread id--93
D/PositionFragment: onReceiveLocation: current thread name--android.os.HandlerThread
可以看到,在onReceiveLocation中,执行线程并不是当前的主线程。因此,在该线程中更新UI当然会发生异常了。
为什么onReceiveLocation不执行在主线程当中,这里通过分析其调用过程和源码,得到答案。
当程序执行到onReceiveLocation时,其调用过程按如下的顺序:
1、HandlerThread.run()
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
即进入了HandlerThread这个线程中执行任务;
2、Looper.loop()
public static void loop() {
//…………..
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//…………………
其中,执行至msg.target.dispatchMessage(msg),其中target的值为Handler (com.baidu.location.LocationClient$a) {cc944e0};
3、Handler.dispatchMessage
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);//LocationClient.a
}
}
将执行至handleMessage(msg),可以从注释看到,此时执行handleMessage方法的对象类型为LocationClient.a
4、LocationClient.a.handleMessage
public void handleMessage(Message var1) {
//……………..
case 21:
Bundle var2 = var1.getData();
var2.setClassLoader(BDLocation.class.getClassLoader());
BDLocation var3 = (BDLocation)var2.getParcelable("locStr");
if(LocationClient.this.serverFirst || !LocationClient.this.clientFirst || var3.getLocType() != 66) {
if(!LocationClient.this.serverFirst && LocationClient.this.clientFirst) {
LocationClient.this.serverFirst = true;
} else {
if(!LocationClient.this.serverFirst) {
LocationClient.this.serverFirst = true;
}
LocationClient.this.onNewLocation(var1, 21);
}
}
break;
//………………………..
}
handleMessage的处理逻辑是一个switch( var1.what),因此来到上述代码部分,最终会执行LocationClient.this.onNewLocation( var1 , 21)
5、之后,程序会执行至LocationClient.onNewLocation上,(中间有一步进行了跳跃,从LocationClient.a跳跃至LocationClient对象上,没搞懂,还要继续研究)
private void onNewLocation(Message var1, int var2) {
if(this.mIsStarted) {
try {
Bundle var3 = var1.getData();
var3.setClassLoader(BDLocation.class.getClassLoader());
this.mLastLocation = (BDLocation)var3.getParcelable("locStr");
if(this.mLastLocation.getLocType() == 61) {
this.lastReceiveGpsTime = System.currentTimeMillis();
}
this.callListeners(var2);
} catch (Exception var4) {
;
}
}
}
可以看到,这里调用了this.callListener。
6、LocationClient.callListener;
LocationClient. callListener
private void callListeners(int var1) {
//…………
//………….
if(this.isWaitingForLocation || this.mOption.location_change_notify && this.mLastLocation.getLocType() == 61 || this.mLastLocation.getLocType() == 66 || this.mLastLocation.getLocType() == 67 || this.inDoorState || this.mLastLocation.getLocType() == 161) {
if(this.mLocationListeners != null) {
Iterator var2 = this.mLocationListeners.iterator();
while(var2.hasNext()) {
BDLocationListener var3 = (BDLocationListener)var2.next();
var3.onReceiveLocation(this.mLastLocation);
}
}
//……………….
this.isWaitingForLocation = false;
this.lastReceiveLocationTime = System.currentTimeMillis();
}
}
该方法中,会进入while,通过iterator对各个注册的Listener进行迭代,并调用Listener的onReceiveLocation方法。
综上,程序如何调用onReceiveLocation的流程已经展示完毕。
可以看到,消息循环是通过LocationClient.a对象(实现了Handler)来发出消息并实现了回调,并最终调用了onReceiveLocation方法。因此看一下LocationClinet.a,如下:
private class a extends Handler {
a(Looper var2) {
super(var2);
}
public void handleMessage(Message var1) {
switch(var1.what) {
//..................
//..................
}
}
}
该对象是LocationClient的内部类。那么看一下LocationClient是如何初始化该对象的。
//...........
//...........
private HandlerThread mThread;
private LocationClient.a mHandler;//...........//...........
public LocationClient(Context var1) {
this.mContext = var1;
this.mOption = new LocationClientOption();
this.mThread = new HandlerThread("LocationClient");
this.mThread.start();
this.mHandler = new LocationClient.a(this.mThread.getLooper());
this.mMessenger = new Messenger(this.mHandler);
}
//...................
//..................
从上述程序中可以看到。LocationClient的构造器中,初始化了一个新的线程HandlerThread处理消息;
mHandler对象是通过HandlerThread的Looper来进行初始化的,这意味着mHandler被绑定至了HandlerThread线程上,该对象属于HandlerThread线程。
而onReceiveLocation是通过Handler触发的,因此onReceiveLocation不在主线程中执行。
解决方案:
public void onReceiveLocation(BDLocation bdLocation) {
final StringBuilder sb=new StringBuilder();
sb.append("纬度: ").append(bdLocation.getLatitude()).append("\n");
sb.append("经度: ").append(bdLocation.getLongitude()).append("\n");
sb.append("定位方式: ");
if (bdLocation.getLocType()==BDLocation.TypeGpsLocation){
sb.append("GPS");
}else if (bdLocation.getLocType()==BDLocation.TypeNetWorkLocation){
sb.append("网络");
}
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
TextView mTmp=(TextView) getActivity().findViewById(R.id.locatingresultview);
mTmp.setText(sb.toString());
}
});
}
使用了Activity.runOnUiThread,在UI线程中设置TextView。