BDLocationListener中更新UI出错分析

来源:互联网 发布:java replace函数 正则 编辑:程序博客网 时间:2024/05/29 07:00

使用百度地图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. callListenerprivate 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。

原创粉丝点击