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中添加如下代码:
在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. 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。
- BDLocationListener中更新UI出错分析
- GCD在子线程中更新UI出错记录
- 线程中更新ui
- RecycleView中UI更新
- 线程中更新UI
- Android 非UI线程中更新UI
- Android 非UI线程中更新UI
- c# 线程中更新UI
- Android 线程中更新UI
- 工作线程中更新UI
- 子线程中更新UI
- Android中handler更新UI
- 在service 中更新UI
- 线程中修改ui,解决线程修改UI时出错
- android 不能在子线程中更新ui的讨论和分析
- android 不能在子线程中更新ui的讨论和分析
- Android 不能在子线程中更新ui的讨论和分析
- 短信ui分析--短信界面更新
- iOS攻防
- (字符串操作)LeetCode#5. Longest Palindromic Substring
- Swift 3.0 集成极光推送
- ajaxfileupload异步上传文件
- 前言
- BDLocationListener中更新UI出错分析
- 二进制文件查看其,看到的内容,解析,说明
- C/C++直接输出一个数据的十六进制、八进制和二进制
- HTML(5) 样式指南和代码约定
- 解决WordPress无法在线安装主题与插件
- 《Android源码设计模式解析与实战》读书笔记(十七)——中介者模式
- 1158: 防御导弹
- linux内核调优参数对比和解释
- ubuntu下gcc链接静态库.a文件