浅谈百度地图的简单开发最后收官之实现导航功能(五)
百度地图,Android,导航2016-05-27
这篇是高仿百度地图的最后一篇了,今天主要来实现百度地图的导航的功能,并且该导航还自带语音播报功能,然后最后对整个百度地图开发过程遇到的问题进行一些列举,并给出一些解决的办法,可能总结的不是很齐全,希望大家能多多给出宝贵建议,希望能共同进步。那就开始我们今天最后一篇有关百度地图的导航功能的实现开发吧。
一、要想使用内置的ttf语音播报的功能则需要通过百度地图官方的认证才可以,所以接下来我们先去通过认证一下,开启内置的ttf语音播报的导航功能,申请具体步骤如下:
1、这是申请认证的地址:http://app.navi.baidu.com/ttsregister/appinfo,并按照要求填写MD5数字签名,APPKEY,包名即可。
到这里关于语音播报导航的认证就申请成功了。
二、导航顾名思义肯定必须要指定一个起点和一个终点,所以我模仿百度地图写了一个布局界面,用于填写起点和终点。那我们如何通过一个起点和一个终点的名称来得到该点的地理位置的信息呢?导航实际上必须要拿到起点和终点的地理位置信息,比如经纬度信息。
选择地点的界面布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <include android:id="@+id/include_navi" layout="@layout/header_navi" /> <RelativeLayout android:id="@+id/relative_select_address" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#FFF" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="5dp" android:src="@drawable/change" /> <LinearLayout android:layout_width="250dp" android:layout_height="wrap_content" android:layout_centerInParent="true" android:orientation="vertical" > <TextView android:id="@+id/start" android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableLeft="@drawable/start_point" android:drawablePadding="5dp" android:padding="10dp" android:text="输入起点" android:textColor="#ccc" android:textSize="16sp" /> <View android:layout_width="match_parent" android:layout_height="0.1dp" android:background="#22000000" /> <TextView android:id="@+id/end" android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableLeft="@drawable/end_point" android:drawablePadding="5dp" android:padding="10dp" android:text="输入终点" android:textColor="#ccc" android:textSize="16sp" /> </LinearLayout> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="10dp" android:src="@drawable/voice" /> </RelativeLayout> </LinearLayout>
搜索地点名称的布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#FFF" android:padding="10dp" > <ImageView android:id="@+id/search_back" android:layout_width="20dp" android:layout_height="20dp" android:scaleType="center" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:src="@drawable/search_back" /> <EditText android:id="@+id/search_content" android:layout_width="220dp" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:layout_toRightOf="@+id/search_back" android:background="@drawable/edit_bg" android:drawableLeft="@drawable/search2" android:drawablePadding="5dp" android:padding="10dp" /> <Button android:id="@+id/ok_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:layout_marginRight="5dp" android:layout_toRightOf="@+id/search_content" android:background="@drawable/button_bg" android:text="确定" android:textColor="#FFF" /> </RelativeLayout> <ListView android:id="@+id/show_search_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:divider="#22000000" android:dividerHeight="0.1dp" > </ListView> </LinearLayout>
三、实际上导航只需要获取到起点和终点的经纬度信息,然后分别把起点和终点当做这条导航路线的两个节点,并把这两个节点加入到一个节点集合中去,然后再把该集合传入到一个路线规划监听接口类中,该接口类会根据内部封装的路径算法,然后再把各个途径各个节点加入到该集合中。但是有一点如何通过我填入的一个地点就能得到该地点的经纬度等信息呢?这个就要用上一讲的如何获得地点接口信息,主要是通过一个Web PlaceAPI接口通过HttpClient的get请求从而得到网络返回JSON数据,然后去解析这些JSON数据然后将这些信息封装到StartNodeInfo起点Bean和EndNodeInfo的Bean中,下次就可以直接从这些Bean中得到起点和终点的经纬度信息了。
得到起点和终点信息的JSON数据以及解析JSON数据的SelectAddressActivity:
package com.zhongqihong.mymap; import java.io.IOException; import java.util.ArrayList; import org.apache.http.client.ClientProtocolException; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; import android.view.Window; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.Toast; import com.zhongqihong.adapter.MySearchContentListAdapter; import com.zhongqihong.beans.EndInfo; import com.zhongqihong.beans.SearchInfo; import com.zhongqihong.beans.StartInfo; import com.zhongqihong.tools.HttpUtils; public class SelectAddressActivity extends Activity implements OnClickListener{ private EditText mSearchContent; private Button mOkBtn; private ArrayList<SearchInfo> searchInfoLists; private ListView mSearchContentList; private Handler handler=new Handler(){ public void handleMessage(Message msg) { if (msg.what==0x123) { JSONObject object=(JSONObject) msg.obj; //toast("json:----->"+object.toString()); //解析开始:然后把每一个地点信息封装到SearchInfo类中 try { JSONArray array=object.getJSONArray("results"); for (int i = 0; i < array.length(); i++) { JSONObject joObject=array.getJSONObject(i); String name=joObject.getString("name"); JSONObject object2=joObject.getJSONObject("location"); double lat=object2.getDouble("lat"); double lng=object2.getDouble("lng"); String address=joObject.getString("address"); String streetIds=joObject.getString("street_id"); String uids=joObject.getString("uid"); SearchInfo mInfo=new SearchInfo(name, lat, lng, address, streetIds, uids); searchInfoLists.add(mInfo); } } catch (JSONException e) { e.printStackTrace(); } } mSearchContentList.setAdapter(new MySearchContentListAdapter(searchInfoLists,SelectAddressActivity.this)); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_select_address); initSelectAddress(); } private void initSelectAddress() { registerAllViewId(); reggisterAllViewEvent(); } private void reggisterAllViewEvent() { mOkBtn.setOnClickListener(this); mSearchContentList.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Intent intent=getIntent(); intent.putExtra("info", searchInfoLists.get(position)); setResult(0,intent); finish(); } }); } private void registerAllViewId() { mSearchContent=(EditText) findViewById(R.id.search_content); mOkBtn=(Button) findViewById(R.id.ok_btn); mSearchContentList=(ListView) findViewById(R.id.show_search_content); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.ok_btn: searchInfoLists=new ArrayList<SearchInfo>(); getSearchDataFromNetWork(); break; default: break; } } /** * @author zhongqihong * 根据输入搜索的信息,从网络获得的JSON数据 * 开启一个线程去获取网络数据 * getSearchDataFromNetWork * */ private void getSearchDataFromNetWork() { new Thread(new Runnable() { @Override public void run() { try { JSONObject jsonObject=HttpUtils.send(mSearchContent.getText().toString(), null); Message msg=new Message(); msg.obj=jsonObject; msg.what=0x123; handler.sendMessage(msg); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } } }).start(); } public void toast(String str){ Toast.makeText(SelectAddressActivity.this, str, 0).show(); } }
然后根据起点和终点的信息就可以进行有关于起点和终点的路线规划,通过路线规划接口类得到途径的所有中间节点,然后将得到的所有的节点加入到集合中去,然后将该集合传入到导航Activity中即可,最后导航Activity根据这些节点进行在地图上进行导航。并且在进行导航时还得需要key认证以及初始化百度导航的引擎,。因为百度地图的导航开发是利用百度地图的服务来开发的,所以首先需要开启百度导航引擎。
package com.zhongqihong.mymap; import java.io.File; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.baidu.mapapi.SDKInitializer; import com.baidu.mapapi.map.MapView; import com.baidu.navisdk.adapter.BNOuterTTSPlayerCallback; import com.baidu.navisdk.adapter.BNRoutePlanNode; import com.baidu.navisdk.adapter.BNRoutePlanNode.CoordinateType; import com.baidu.navisdk.adapter.BaiduNaviManager; import com.baidu.navisdk.adapter.BaiduNaviManager.NaviInitListener; import com.baidu.navisdk.adapter.BaiduNaviManager.RoutePlanListener; import com.zhongqihong.beans.EndInfo; import com.zhongqihong.beans.SearchInfo; import com.zhongqihong.beans.StartInfo; import com.zhongqihong.tools.SystemStatusManager; public class NaViPathActivity extends Activity implements android.view.View.OnClickListener{ private String mSdcardPath=null; private static final String APP_FOLDER_NAME="mikyouPath"; public static final String ROUTE_PLAN_NODE = "routePlanNode"; private String authinfo = null; private TextView mStartTv; private TextView mEndTv; private SearchInfo info=null; private StartInfo sInfo=new StartInfo(); private EndInfo eInfo=new EndInfo(); // private MapView pathMapView; private TextView startSearch; private RelativeLayout selectAddressRelativeLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setTranslucentStatus(); SDKInitializer.initialize(getApplicationContext()); setContentView(R.layout.activity_na_vi_path); initSdcardPath();//先获得SD卡的路径 initView(); } private void initView() { registerAllViewId(); registerAllViewEvent(); } private void registerAllViewEvent() { mStartTv.setOnClickListener(this); mEndTv.setOnClickListener(this); startSearch.setOnClickListener(this); } private void registerAllViewId() { mStartTv=(TextView) findViewById(R.id.start); mEndTv=(TextView) findViewById(R.id.end); startSearch=(TextView) findViewById(R.id.include_navi).findViewById(R.id.start_search); selectAddressRelativeLayout=(RelativeLayout) findViewById(R.id.relative_select_address); } private void initSdcardPath() { if (initDir()) { initNaviPath(); } } private boolean initDir() {//创建一个文件夹用于保存在路线导航过程中语音导航语音文件的缓存,防止用户再次开启同样的导航直接从缓存中读取即可 if (Environment.getExternalStorageState().equalsIgnoreCase(Environment.MEDIA_MOUNTED)) { mSdcardPath=Environment.getExternalStorageDirectory().toString(); }else{ mSdcardPath=null; } if (mSdcardPath==null) { return false; } File file=new File(mSdcardPath,APP_FOLDER_NAME); if (!file.exists()) { try { file.mkdir(); } catch (Exception e) { e.printStackTrace(); return false; } } Toast.makeText(NaViPathActivity.this, mSdcardPath, 0).show(); return true; } private void initNaviPath() {//初始化导航路线的导航引擎 BNOuterTTSPlayerCallback ttsCallback = null; BaiduNaviManager.getInstance().init(NaViPathActivity.this, mSdcardPath, APP_FOLDER_NAME, new NaviInitListener() { @Override public void onAuthResult(int status, String msg) { if (status==0) { authinfo = "key校验成功!"; }else{ authinfo = "key校验失败!"+msg; } NaViPathActivity.this.runOnUiThread(new Runnable() { public void run() { Toast.makeText(NaViPathActivity.this, authinfo, Toast.LENGTH_LONG).show(); } }); } @Override public void initSuccess() { Toast.makeText(NaViPathActivity.this, "百度导航引擎初始化成功", Toast.LENGTH_LONG).show(); } @Override public void initStart() { Toast.makeText(NaViPathActivity.this, "百度导航引擎初始化开始", Toast.LENGTH_LONG).show(); } @Override public void initFailed() { Toast.makeText(NaViPathActivity.this, "百度导航引擎初始化失败", Toast.LENGTH_LONG).show(); } }, ttsCallback); } private void setTranslucentStatus() { if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT){ Window win=getWindow(); WindowManager.LayoutParams winParams=win.getAttributes(); final int bits=WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; winParams.flags |=bits; win.setAttributes(winParams); } SystemStatusManager tintManager = new SystemStatusManager(this); tintManager.setStatusBarTintEnabled(true); tintManager.setStatusBarTintResource(0); tintManager.setNavigationBarTintEnabled(true); } @Override public void onClick(View v) { Intent intent=new Intent(NaViPathActivity.this, SelectAddressActivity.class); switch (v.getId()) { case R.id.start: startActivityForResult(intent, 0);//用于得到另外一个Activity返回起点地点信息 break; case R.id.end: startActivityForResult(intent,1);//用于得到另外一个Activity返回终点地点信息 break; case R.id.start_search: if (sInfo!=null&&eInfo!=null) {//如果起点和终点信息都不为空的时候开启路线规划得到最佳路径途径的所有节点信息 initBNRoutePlan(sInfo,eInfo); } break; default: break; } } private void initBNRoutePlan(StartInfo mySInfo,EndInfo myEndInfo) { BNRoutePlanNode startNode=new BNRoutePlanNode(mySInfo.getLongtiude(), mySInfo.getLatitude(), mySInfo.getDesname(), null, CoordinateType.BD09LL);//根据得到的起点的信息创建起点节点 BNRoutePlanNode endNode=new BNRoutePlanNode(myEndInfo.getLongtiude(), myEndInfo.getLatitude(), myEndInfo.getDesname(),null, CoordinateType.BD09LL);//根据得到的终点的信息创建终点节点 if (startNode!=null&&endNode!=null) { ArrayList<BNRoutePlanNode>list=new ArrayList<BNRoutePlanNode>(); list.add(startNode);//将起点和终点加入节点集合中 list.add(endNode); BaiduNaviManager.getInstance().launchNavigator(NaViPathActivity.this, list, 1, true, new MyRoutePlanListener(list) ); } } class MyRoutePlanListener implements RoutePlanListener{//路线规划监听器接口类 private ArrayList<BNRoutePlanNode>mList=null; public MyRoutePlanListener(ArrayList<BNRoutePlanNode> list) { mList = list; } @Override public void onJumpToNavigator() { Intent intent=new Intent(NaViPathActivity.this, PathGuideActivity.class); intent.putExtra(ROUTE_PLAN_NODE, mList);//将得到所有的节点集合传入到导航的Activity中去 startActivity(intent); } @Override public void onRoutePlanFailed() { // TODO Auto-generated method stub } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode==0&&resultCode==0) {//设置起点 SearchInfo myInfo= (SearchInfo) data.getSerializableExtra("info"); sInfo.setAddress(myInfo.getAddress()); sInfo.setDesname(myInfo.getDesname()); sInfo.setLatitude(myInfo.getLatitude()); sInfo.setLongtiude(myInfo.getLongtiude()); sInfo.setStreetId(myInfo.getStreetId()); sInfo.setUid(myInfo.getUid()); mStartTv.setText(myInfo.getDesname()); } if (requestCode==1&&resultCode==0) {//设置终点 SearchInfo myInfo= (SearchInfo) data.getSerializableExtra("info"); eInfo.setAddress(myInfo.getAddress()); eInfo.setDesname(myInfo.getDesname()); eInfo.setLatitude(myInfo.getLatitude()); eInfo.setLongtiude(myInfo.getLongtiude()); eInfo.setStreetId(myInfo.getStreetId()); eInfo.setUid(myInfo.getUid()); mEndTv.setText(myInfo.getDesname()); } } }
四、最后通过导航实现导航功能,个人感觉这里有点水了,因为感觉这个导航传入节点集合后完全就是直接调用百度地图官方的API了,甚至连导航的界面都是通过官方给出的,并且有一点感觉很坑呀,根据官方的API自定义图层写的,那个中间的导航图标就是修改不了,不过最后导航的功能可以实现,可以任意输入两个地点,然后就会给出导航路线并进行语音播报。
package com.zhongqihong.mymap; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Toast; import com.baidu.navisdk.adapter.BNRouteGuideManager; import com.baidu.navisdk.adapter.BNRouteGuideManager.CustomizedLayerItem; import com.baidu.navisdk.adapter.BNRouteGuideManager.OnNavigationListener; import com.baidu.navisdk.adapter.BNRoutePlanNode; import com.baidu.navisdk.adapter.BNRoutePlanNode.CoordinateType; import com.zhongqihong.tools.SystemStatusManager; public class PathGuideActivity extends Activity { private BNRoutePlanNode mBNRoutePlanNode = null; private Handler handler=null; private static final int MSG_SHOW = 1; private static final int MSG_HIDE = 2; private static final int MSG_RESET_NODE =3; private ArrayList<BNRoutePlanNode>list=null; @SuppressWarnings("unchecked") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setTranslucentStatus(); getHandler(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {} View view = BNRouteGuideManager.getInstance().onCreate(PathGuideActivity.this, new OnNavigationListener() { @Override public void onNaviGuideEnd() { finish(); } @Override public void notifyOtherAction(int arg0, int arg1, int arg2, Object arg3) { } }); if ( view != null ) { setContentView(view); } Intent intent=getIntent(); if (intent != null) { list=(ArrayList<BNRoutePlanNode>)intent.getSerializableExtra(NaViPathActivity.ROUTE_PLAN_NODE);//接收到路线规划得到的节点集合 mBNRoutePlanNode=list.get(0);//先取得起点节点 } } private void setTranslucentStatus() { if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT){ Window win=getWindow(); WindowManager.LayoutParams winParams=win.getAttributes(); final int bits=WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; winParams.flags |=bits; win.setAttributes(winParams); } SystemStatusManager tintManager = new SystemStatusManager(this); tintManager.setStatusBarTintEnabled(true); tintManager.setStatusBarTintResource(0); tintManager.setNavigationBarTintEnabled(true); } /** * @mikyou * 管理导航功能操作生命周期将它与Activity的生命周期绑定在一起即可 * */ @Override protected void onResume() { BNRouteGuideManager.getInstance().onResume(); super.onResume(); if(handler != null){ handler.sendEmptyMessageAtTime(MSG_SHOW,2000); } } protected void onPause() { super.onPause(); BNRouteGuideManager.getInstance().onPause(); }; @Override protected void onDestroy() { BNRouteGuideManager.getInstance().onDestroy(); super.onDestroy(); } @Override protected void onStop() { BNRouteGuideManager.getInstance().onStop(); super.onStop(); } @Override public void onBackPressed() { BNRouteGuideManager.getInstance().onBackPressed(false); } public void onConfigurationChanged(android.content.res.Configuration newConfig) { BNRouteGuideManager.getInstance().onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig); }; private void getHandler() { if (handler==null) { handler=new Handler(getMainLooper()){ @Override public void handleMessage(Message msg) { if (msg.what==MSG_SHOW) { addCustomizedLayerItems(); }else if (msg.what==MSG_HIDE) { BNRouteGuideManager.getInstance().showCustomizedLayer(false); }else if (msg.what==MSG_RESET_NODE) { BNRouteGuideManager.getInstance().resetEndNodeInNavi(list.get(1)); } } }; } } private void addCustomizedLayerItems() {//添加自定义的图层,可以更换导航的中心的图标 List<CustomizedLayerItem> items = new ArrayList<CustomizedLayerItem>(); CustomizedLayerItem item1 = null; if (mBNRoutePlanNode != null) { toast("getLongtiude: "+mBNRoutePlanNode.getLongitude()+" name: "+mBNRoutePlanNode.getName()); item1 = new CustomizedLayerItem(mBNRoutePlanNode.getLongitude(), mBNRoutePlanNode.getLatitude(), CoordinateType.BD09LL, getResources().getDrawable(R.drawable.ic_launcher), CustomizedLayerItem.ALIGN_CENTER); items.add(item1); BNRouteGuideManager.getInstance().setCustomizedLayerItems(items); } BNRouteGuideManager.getInstance().showCustomizedLayer(true); } public void toast(String str){ Toast.makeText(PathGuideActivity.this, str, 0).show(); } }
最后实现的效果:
然后,再和大家谈谈开发过程遇到的问题及解决的办法:
1、发现自己在Eclipse工程中的代码一不小心给删除了,如何恢复??
解决办法:选中该项目工程,然后右击选择restore from local history,即可恢复
2、在百度地图开发过程中,实现定位功能的过程中,发现定位的图标不能显示,而其他则是正常。
解决办法:最小的sdk版本太低了,要在API-10以上
3、如果你的Eclipse的SHA1 fingerprint发生了改变,那么你运行原来的map中的APPKEY就失效了,无法加载出地图
解决的办法:就是重新用新的SHA1 fingerprint创建出一个新的应用
4、Dex Loader] Unable to execute dex: Multiple dex files define Lcom/baidu...错误
是因为百度导航 和 百度地图POI搜索的 库整合 发生冲突
解决办法:去百度地图开发者中心官网统一将所有功能的jar全部下载下来即可
5、也许你在模拟器上无法加载出你的定位信息,或者其他服务的信息,而一些APPKEY,代码等设置都没错的话
可能是你的模拟器不是arm平台,可能是Intel平台的,因为在libs有一个armabi文件夹,放入的是只支持arm平台的jar包
导致无法加载其他服务,所以需要在模拟器上换成arm平台的即可
6、就是在编写地图导航模块的时候,在初始化验证验证key以及初始化百度地图导航的引擎的时候,会一直
出现“初始化百度导航引擎失败”。
原因:第一种是相应的jar包没有导入完全,第二种就是在assets文件夹内的资源文件没有
解决办法:1、补全相应的jar包;2、把官方demo中的assets文件夹中的资源文件全部拷贝到项目工程中。
好了,最后就到这了,这就是一个系列的简单的百度地图开发自己的一些感悟吧,最后还是要感谢Hyman大神,可以说我学Android有好多思想都是来自于Hyman大神,感谢这位一直无私奉献的大神,谢谢大家,望大家多多支持!