Android|莫名Crash---目睹Baidu地图API之怪现象一

这是地图API系列的第一弹,那么首先从我经历的地图API说起。
几年前有过一次探索Google Map的经历,但是并没有深入和坚持下去。记得当时只是做一个可以定位和显示地图的Demo。
记得常用的几个类分别是MapView, MapController, Overlay, GeoPoint...


几年之后,国内的Android地图API也雨后春笋一样,遍地开花。
我心里觉得两个不错的代表就是Baidu地图和高德地图。
用朋友的话说,Baidu是家大公司,它出的API一定差不了。确实,Android版的百度地图,使用起来确实很棒,而且功能也越来越多,用起来也很容易上手。
而高德一直就是做导航的,也是Google的地图提供商之一。所以一直对其有着好感。


这一次,是要快速做一个原型Demo,提供的功能是在地图中显示当前的位置。说白了,就是和google地图那个demo类似,定位和显示地图。
首先用Baidu地图API打头阵。Baidu将Map和Location分开成两个SDK,这样每个都可以单独使用。后来发现,高德地图也是如此。
看了一阵API文档, 感到不明觉厉啊。
整体上看,共8个包。而其中最重要的(我按照自己需求而定)是:
com.baidu.mapapi.map地图图层相关,包括地图控制,图层上自定义图标、图形等
com.baidu.mapapi.search信息查询和路径规划相关
而常见的那些类(上述google地图中的)在这里都可以看到。这反映了同一领域的知识是相通的,看了高德的api更是如此感慨。


为了实现我想要的功能,只需将BaiduMapsApiDemo中的LocationOverlayDemo单独抽出来就可以。但我还是喜欢一切自己从头开始。
按照开发指南 中的Hello world开始,一步一步加入MyLocationOverlay等内容。其实一切是非常简单的,代码我就不列出了。
但是编译、手机中运行。App 死掉了!很干脆的死掉了。我从log里根本找不到一丝线索。
也许是我在加载地图和定位so和jar时出错了?
再按照文档一步一步重来,还是挂掉。浪费几个小时无果后,决定不能这样了。快速建立原型的宗旨是,要快速要出一个可以运行的程序。
好吧,直接在官方demo上修改吧。
一路重构下来,最后demo中只剩下LocationOverlayDemo和DemoApplication了。
我发现LocationOverlayDemo类中还有可重构的余地,比如这个类我没有必要使用的:

class MyLocationMapView extends MapView{ static PopupOverlaypop= null; //弹出泡泡图层,点击图标使用 public MyLocationMapView(Context context) { super(context); // TODO Auto-generated constructor stub } public MyLocationMapView(Context context, AttributeSet attrs){ super(context,attrs); } public MyLocationMapView(Context context, AttributeSet attrs, int defStyle){ super(context, attrs, defStyle); } @Override public boolean onTouchEvent(MotionEvent event){ if (!super.onTouchEvent(event)){ //消隐泡泡 if (pop != null && event.getAction() == MotionEvent.ACTION_UP) pop.hidePop(); } return true; } }

因为现在没有对pop使用的需求,那么直接使用MapView就可以的,没有必要自己写这个类。
将程序中的MyLocationMapView替换为MapView,从常识上讲,这是可行的。但是编译、运行后,程序又挂了,跟之前的现象一样。
这次我没有着急,肯定是重构出问题了,而问题的root cause就是这个MyLocationMapView。
反复调试,我发现这么个怪现象:
只要我的地图包下有这样一个类继承自MapView,那么程序就不会Crash。下面是修改字官方Demo的源码,十分奇怪的源码:

package baidumapsdk.demo; import android.app.Activity; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.RadioGroup; import android.widget.RadioGroup.OnCheckedChangeListener; import android.widget.TextView; import android.widget.Toast; import com.baidu.location.BDLocation; import com.baidu.location.BDLocationListener; import com.baidu.location.LocationClient; import com.baidu.location.LocationClientOption; import com.baidu.mapapi.map.LocationData; import com.baidu.mapapi.map.MapController; import com.baidu.mapapi.map.MapView; import com.baidu.mapapi.map.MyLocationOverlay; import com.baidu.mapapi.map.MyLocationOverlay.LocationMode; import com.baidu.mapapi.map.PopupClickListener; import com.baidu.mapapi.map.PopupOverlay; import com.baidu.platform.comapi.basestruct.GeoPoint; /** * 此demo用来展示如何结合定位SDK实现定位,并使用MyLocationOverlay绘制定位位置 * 同时展示如何使用自定义图标绘制并点击时弹出泡泡 * */ public class LocationOverlayDemo extends Activity { private enum E_BUTTON_TYPE { LOC, COMPASS, FOLLOW } private E_BUTTON_TYPE mCurBtnType; // 定位相关 LocationClient mLocClient; LocationData locData = https://www.it610.com/article/null; public MyLocationListenner myListener = new MyLocationListenner(); //定位图层 locationOverlay myLocationOverlay = null; //弹出泡泡图层 private PopupOverlaypop= null; //弹出泡泡图层,浏览节点时使用 private TextViewpopupText = null; //泡泡view private View viewCache = null; //地图相关,使用继承MapView的MyLocationMapView目的是重写touch事件实现泡泡处理 //如果不处理touch事件,则无需继承,直接使用MapView即可 // MyLocationMapView mMapView = null; // 地图View MapView mMapView = null; // 地图View private MapController mMapController = null; //UI相关 OnCheckedChangeListener radioButtonListener = null; Button requestLocButton = null; boolean isRequest = false; //是否手动触发请求定位 boolean isFirstLoc = true; //是否首次定位 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_locationoverlay); CharSequence titleLable="定位功能"; setTitle(titleLable); requestLocButton = (Button)findViewById(R.id.button1); mCurBtnType = E_BUTTON_TYPE.LOC; OnClickListener btnClickListener = new OnClickListener() { public void onClick(View v) { switch (mCurBtnType) { case LOC: //手动定位请求 requestLocClick(); break; case COMPASS: myLocationOverlay.setLocationMode(LocationMode.NORMAL); requestLocButton.setText("定位"); mCurBtnType = E_BUTTON_TYPE.LOC; break; case FOLLOW: myLocationOverlay.setLocationMode(LocationMode.COMPASS); requestLocButton.setText("罗盘"); mCurBtnType = E_BUTTON_TYPE.COMPASS; break; } } }; requestLocButton.setOnClickListener(btnClickListener); RadioGroup group = (RadioGroup)this.findViewById(R.id.radioGroup); radioButtonListener = new OnCheckedChangeListener() {@Override public void onCheckedChanged(RadioGroup group, int checkedId) { if (checkedId == R.id.defaulticon){ //传入null则,恢复默认图标 modifyLocationOverlayIcon(null); } if (checkedId == R.id.customicon){ //修改为自定义marker modifyLocationOverlayIcon(getResources().getDrawable(R.drawable.icon_geo)); } } }; group.setOnCheckedChangeListener(radioButtonListener); //地图初始化 //mMapView = (MyLocationMapView)findViewById(R.id.bmapView); mMapView = (MapView)findViewById(R.id.bmapView); mMapController = mMapView.getController(); mMapView.getController().setZoom(14); mMapView.getController().enableClick(true); mMapView.setBuiltInZoomControls(true); //创建 弹出泡泡图层 createPaopao(); //定位初始化 mLocClient = new LocationClient( this ); locData = https://www.it610.com/article/new LocationData(); mLocClient.registerLocationListener( myListener ); LocationClientOption option = new LocationClientOption(); option.setOpenGps(true); //打开gps option.setCoorType("bd09ll"); //设置坐标类型 option.setScanSpan(1000); mLocClient.setLocOption(option); mLocClient.start(); //定位图层初始化 myLocationOverlay = new locationOverlay(mMapView); //设置定位数据 myLocationOverlay.setData(locData); //添加定位图层 mMapView.getOverlays().add(myLocationOverlay); myLocationOverlay.enableCompass(); //修改定位数据后刷新图层生效 mMapView.refresh(); } /** * 手动触发一次定位请求 */ public void requestLocClick(){ isRequest = true; mLocClient.requestLocation(); Toast.makeText(LocationOverlayDemo.this, "正在定位……", Toast.LENGTH_SHORT).show(); } /** * 修改位置图标 * @param marker */ public void modifyLocationOverlayIcon(Drawable marker){ //当传入marker为null时,使用默认图标绘制 myLocationOverlay.setMarker(marker); //修改图层,需要刷新MapView生效 mMapView.refresh(); } /** * 创建弹出泡泡图层 */ public void createPaopao(){ viewCache = getLayoutInflater().inflate(R.layout.custom_text_view, null); popupText =(TextView) viewCache.findViewById(R.id.textcache); //泡泡点击响应回调 PopupClickListener popListener = new PopupClickListener(){ @Override public void onClickedPopup(int index) { Log.v("click", "clickapoapo"); } }; pop = new PopupOverlay(mMapView,popListener); //MyLocationMapView.pop = pop; } /** * 定位SDK监听函数 */ public class MyLocationListenner implements BDLocationListener {@Override public void onReceiveLocation(BDLocation location) { if (location == null) return ; locData.latitude = location.getLatitude(); locData.longitude = location.getLongitude(); //如果不显示定位精度圈,将accuracy赋值为0即可 locData.accuracy = location.getRadius(); // 此处可以设置 locData的方向信息, 如果定位 SDK 未返回方向信息,用户可以自己实现罗盘功能添加方向信息。 locData.direction = location.getDerect(); //更新定位数据 myLocationOverlay.setData(locData); //更新图层数据执行刷新后生效 mMapView.refresh(); //是手动触发请求或首次定位时,移动到定位点 if (isRequest || isFirstLoc){ //移动地图到定位点 Log.d("LocationOverlay", "receive location, animate to it"); mMapController.animateTo(new GeoPoint((int)(locData.latitude* 1e6), (int)(locData.longitude *1e6))); isRequest = false; myLocationOverlay.setLocationMode(LocationMode.FOLLOWING); requestLocButton.setText("跟随"); mCurBtnType = E_BUTTON_TYPE.FOLLOW; } //首次定位完成 isFirstLoc = false; }public void onReceivePoi(BDLocation poiLocation) { if (poiLocation == null){ return ; } } }//继承MyLocationOverlay重写dispatchTap实现点击处理 public class locationOverlay extends MyLocationOverlay{public locationOverlay(MapView mapView) { super(mapView); // TODO Auto-generated constructor stub } @Override protected boolean dispatchTap() { // TODO Auto-generated method stub //处理点击事件,弹出泡泡 popupText.setBackgroundResource(R.drawable.popup); popupText.setText("我的位置"); pop.showPopup(BMapUtil.getBitmapFromView(popupText), new GeoPoint((int)(locData.latitude*1e6), (int)(locData.longitude*1e6)), 8); return true; }}@Override protected void onPause() { mMapView.onPause(); super.onPause(); }@Override protected void onResume() { mMapView.onResume(); super.onResume(); }@Override protected void onDestroy() { //退出时销毁定位 if (mLocClient != null) mLocClient.stop(); mMapView.destroy(); super.onDestroy(); }@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mMapView.onSaveInstanceState(outState); }@Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); mMapView.onRestoreInstanceState(savedInstanceState); }@Override public boolean onCreateOptionsMenu(Menu menu) { //getMenuInflater().inflate(R.menu.activity_main, menu); return true; }}class TestMapView extends MapView{ public TestMapView(Context context) { super(context); // TODO Auto-generated constructor stub } public TestMapView(Context context, AttributeSet attrs){ super(context,attrs); } }


从代码中可以看出,TestMapView这个类我是根本没有用的,但是我删掉它,程序就会Crash。
好吧,我现在还是没有找到root cause,只能这样先用着。
大家也可以体验一样,并帮我找找原因。

【Android|莫名Crash---目睹Baidu地图API之怪现象一】

    推荐阅读