BDLocationListener中更新UI出错分析

使用百度地图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中添加如下代码:
 
  
long uId=Thread.currentThread().getId();
        Log.d("MainActivity", "onCreate: current thread id-- "+uId);

在onReceiveLocation中添加如下代码:

 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。

你可能感兴趣的:(Android)