基于百度地图sdk的地图app开发(七)——导航和模拟导航

这是基于百度地图sdk的地图app开发系列博客第七篇 代码仓库位置:https://github.com/YanhuiLu89/lmap.git 上一篇 基于百度地图sdk的地图app开发(六)—...

这是基于百度地图sdk的地图app开发系列博客第七篇

代码仓库位置:https://github.com/YanhuiLu89/lmap.git

上一篇 基于百度地图sdk的地图app开发(六)——路线规划

因为本人是做C++开发,android和java都不熟,这方面知识有说错或者不好的习惯,欢迎赐较。

官方参考文档:Android导航SDK

一、工程配置

1.1 在AndroidMainfest.xml中加入以下权限

(指导文档里的权限比这多,前面几个权限在前面几篇文章里,使用百度地图sdk时已经加入了,所以这里只需要加下面这3个就好)

官方文档中授权key配置,在第一篇基于百度地图sdk的地图app开发(一)——开发环境配置与sdk下载里已经做了,所以下面直接到导航SDK核心集成

1.2 导航SDK集成和添加依赖库

将基于百度地图sdk的地图app开发(一)——开发环境配置与sdk下载中下载的onsdk_all.aar、和NaviTts.aar两个文件拷贝到app\libs下,

在build.gradle中添加以下代码

android {

// apache包

useLibrary 'org.apache.http.legacy'

……

}

dependencies {

implementation files('libs\\NaviTts.aar')

implementation files('libs\\onsdk_all.aar')

implementation 'androidx.appcompat:appcompat:1.0.0'

implementation 'androidx.cardview:cardview:1.0.0'

implementation 'androidx.recyclerview:recyclerview:1.0.0'

implementation 'com.android.support.constraint:constraint-layout:1.1.3'

implementation 'android.arch.lifecycle:extensions:1.1.1'

}

1.3 TTS授权申请

SDK授权申请后,可以继续申请TTS授权。

1. 以基于百度地图sdk的地图app开发(一)——开发环境配置与sdk下载中提到的SDK授权申请时相同账号登录 http://yuyin.baidu.com/,点击右上角的“控制台“,进入控制台界面,点击左侧导航栏“语音技术”,然后点击创建应用。

2. 按步骤填入应用名称、包名等信息。

3. 点击立即创建会生成App ID,就是在开发中初始化TTS能力时传入的参数之一

至此,和导航SDK集成相关的授权申请就完成了。

虽然做了配置,导航没有声音原因目前未知

1.4so库的集成

在第一篇基于百度地图sdk的地图app开发(一)——开发环境配置与sdk下载中已经下载了so库(在arm64-v8a、armeabi和armeabi-v7a文件夹中),并且放置到了app/libs目录下

这里只需要在app的Build.gradle里增加以下红框中设置

另外也可以像官方文档中那样。在 main 目录下创建文件夹 jniLibs (如果有就不需要创建了),将下载文件的 armeabi 等相关文件夹复制到这个目录下,这时候就不需要上面红框的设置了。

1.5 Gradle配置

2为了避免Android "64K 引用限制"引起的异常,在app的build.gradle中需要引入multidex包,并进行相关配置,并且导航SDK内部使用了annotationProcessor,同样需要在build.gradle中配置,如下。

defaultConfig {

……

// 避免"64K 引用限制"

multiDexEnabled true

// 导航SDK内部使用了annotationProcessor,需要添加下面代码,防止编译异常

javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }

}

二、导航SDK初始化

2.1、初始化导航sdk

导航sdk初始化接口详细说明

初始化接口init详细说明如:

/**

* 初始化百度导航.

*

* @param context 建议是应用的context

* @param sdcardRootPath 系统SD卡根目录路径

* @param appFolderName 应用在SD卡中的目录名

* @param naviInitListener 百度导航初始化监听器

*/

void init(final Context context,

final String sdcardRootPath,

final String appFolderName,

final INaviInitListener naviInitListener);

在MainActivity的OnCreate函数中添加以下代码进行初始化

//导航初始化

File sdDir = null;

boolean sdCardExist = Environment.getExternalStorageState()

.equals(android.os.Environment.MEDIA_MOUNTED);//判断sd卡是否存在

if(sdCardExist)

{

sdDir = Environment.getExternalStorageDirectory();//获取跟目录

}

BaiduNaviManagerFactory.getBaiduNaviManager().init(getApplicationContext(), sdDir.toString(), "lmap",

new IBaiduNaviManager.INaviInitListener() {

@Override

public void onAuthResult(int i, String s) {

if(i==0)

{

Toast.makeText(MainActivity.this, "key校验成功!", Toast.LENGTH_SHORT).show();

}

else if(i==1)

{

Toast.makeText(MainActivity.this, "key校验失败, " + s, Toast.LENGTH_SHORT).show();

}

}

@Override

public void initStart() {

}

@Override

public void initSuccess() {

Toast.makeText(MainActivity.this, "百度导航引擎初始化成功", Toast.LENGTH_SHORT).show();

// 初始化tts

initTTs();

}

@Override

public void initFailed(int i) {

Toast.makeText(MainActivity.this, "百度导航引擎初始化失败", Toast.LENGTH_SHORT).show();

}

});

2.2 初始化tts

增加函数initTTs(),其调用时机为导航sdk初始化成功时调用,见上图initSuccess()接口中,其函数中先初始化,再 注册同步内置tts状态回调

private void initTTs() {

File sdDir = null;

boolean sdCardExist = Environment.getExternalStorageState()

.equals(android.os.Environment.MEDIA_MOUNTED);//判断sd卡是否存在

if(sdCardExist)

{

sdDir = Environment.getExternalStorageDirectory();//获取跟目录

}

BNTTsInitConfig.Builder builder=new BNTTsInitConfig.Builder();

builder.context(getApplicationContext()).sdcardRootPath(sdDir.toString()).appFolderName("lmap").appId(("填入1.3步骤申请的app id"));

BaiduNaviManagerFactory.getTTSManager().initTTS( builder.build());

// 注册同步内置tts状态回调

BaiduNaviManagerFactory.getTTSManager().setOnTTSStateChangedListener(

new IBNTTSManager.IOnTTSPlayStateChangedListener() {

@Override

public void onPlayStart() {

Log.e("lmap", "ttsCallback.onPlayStart");

}

@Override

public void onPlayEnd(String speechId) {

Log.e("lmap", "ttsCallback.onPlayEnd");

}

@Override

public void onPlayError(int code, String message) {

Log.e("lmap", "ttsCallback.onPlayError");

}

}

);

}

三、增加导航功能入口

完成了一二步的配置和初始化,就可以开始使用导航功能了

3.1增加开始导航和模拟导航按钮

增加开始导航和模拟导航两个按钮,其布局代码如下

android:layout_width="match_parent"

android:layout_height="90dp"

android:layout_marginTop="630dp"

android:orientation="horizontal">

android:id="@+id/startnavi"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:layout_weight="1"

android:background="@color/banv_poi_search_text"

android:text="开始导航"

android:textSize="30sp"

android:visibility="gone" />

android:id="@+id/simnavi"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:layout_weight="1"

android:background="@color/banv_poi_search_text"

android:text="模拟导航"

android:textSize="30sp"

android:visibility="gone" />

在上一章基于百度地图sdk的地图app开发(六)——路线规划,算路完成描画路线后,设置按钮可见,即在onGetDrivingRouteResult函数最后,添加以下代码

//显示开始导航按钮

Button startNav=findViewById(R.id.startnavi);

startNav.setVisibility(View.VISIBLE);

Button simNav=findViewById(R.id.simnavi);

simNav.setVisibility(View.VISIBLE);

算路完成显示效果如下

3.2开始导航和模拟导航按钮的响应函数中启动导航

我的本意是想使用上一章 基于百度地图sdk的地图app开发(六)——路线规划 算出来的路线结果DrivingRouteResult,来进行导航。客户点击选择哪条路线,就把哪条路线传给导航,开始导航。(我之前的开发经验大部分都是这样。)但是看IBNRoutePlanManager只接受点再重新算路,开始导航,并不接受DrivingRouteResult。所以只能画路线前,保存下来目的地,点击开始导航时再重新算一次。

实现步骤,在MainActivity中增加成员变量mDestation

private PoiInfo mDestation=null;

增加设置mDestation的接口

public void setDestation(PoiInfo poi){mDestation=poi;}

在 上一章基于百度地图sdk的地图app开发(六)——路线规划提到的去这里按钮,点击函数中调用设置mDestation的接口

最后在开始导航按钮的click函数调用startNavi参数传true即启动真实导航,在模拟导航按钮的响应函数中调用startNavi,参数传false,即启动模拟导航。代码如下

//增加开始导航按钮的响应

Button startNav=findViewById(R.id.startnavi);

startNav.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

startNavi(true);

}

});

Button simNav=findViewById(R.id.simnavi);

simNav.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

startNavi(false);

}

});

startNavi接口的实现如下

private void startNavi(boolean isRealNavi) {

if(mCurLocation==null||mDestation==null)

return;

BNRoutePlanNode sNode = new BNRoutePlanNode.Builder()

.latitude(mCurLocation.getLatitude())

.longitude(mCurLocation.getLongitude())

.name("我的位置")

.description("我的位置")

.build();

BNRoutePlanNode eNode = new BNRoutePlanNode.Builder()

.latitude(mDestation.getLocation().latitude)

.longitude(mDestation.getLocation().longitude)

.name(mDestation.name)

.description(mDestation.name)

.build();

List list = new ArrayList<>();

list.add(sNode);

list.add(eNode);

BaiduNaviManagerFactory.getRoutePlanManager().routePlanToNavi(

list,

IBNRoutePlanManager.RoutePlanPreference.ROUTE_PLAN_PREFERENCE_DEFAULT,

null,

new Handler(Looper.getMainLooper()) {

@Override

public void handleMessage(Message msg) {

switch (msg.what) {

case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_START:

Toast.makeText(MainActivity.this.getApplicationContext(),

"算路开始", Toast.LENGTH_SHORT).show();

break;

case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_SUCCESS:

Toast.makeText(MainActivity.this.getApplicationContext(),

"算路成功", Toast.LENGTH_SHORT).show();

break;

case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_FAILED:

Toast.makeText(MainActivity.this.getApplicationContext(),

"算路失败", Toast.LENGTH_SHORT).show();

break;

case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_TO_NAVI:

Toast.makeText(MainActivity.this.getApplicationContext(),

"算路成功准备进入导航", Toast.LENGTH_SHORT).show();

Intent intent = new Intent(MainActivity.this,

DemoGuideActivity.class);

intent.putExtra("isRealNavi",isRealNavi);

startActivity(intent);

break;

default:

// nothing

break;

}

}

});

}

以上“算路成功准备进入导航”用到的DemoGuideActivity在第4节部分实现

四、DemoGuideActivity的实现

新增Activity,继承自FragmentActivity(这一点很重要,如果按照默认继承自AppCompatActivity,在调用BaiduNaviManagerFactory.getRouteGuideManager().onCreate(this, config)会崩溃)。

4.1管理百度专业导航生命周期

在MainActivity的onCreate中调用RouteGuideManager的onCreate函数并将返回的view,设置成MainActivity的ContentView,代码如下

//管理专业导航生命周期

Bundle bundle = new Bundle();

// IS_REALNAVI代表导航类型,true表示真实导航,false表示模拟导航,默认是true

boolean isRealNavi=getIntent().getBooleanExtra("isRealNavi",true);

bundle.putBoolean(BNaviCommonParams.ProGuideKey.IS_REALNAVI,isRealNavi);

// IS_SUPPORT_FULL_SCREEN代表是否沉浸式,默认是true

bundle.putBoolean(BNaviCommonParams.ProGuideKey.IS_SUPPORT_FULL_SCREEN, true);

BNGuideConfig config = new BNGuideConfig.Builder().params(bundle).build();

View view=BaiduNaviManagerFactory.getRouteGuideManager().onCreate(this, config);

if (view != null) {

setContentView(view);

}

在onStart、onResume、onPause、onStop、onDestroy接口中,分别增加以下代码

BaiduNaviManagerFactory.getRouteGuideManager().onStart();

BaiduNaviManagerFactory.getRouteGuideManager().onResume();

BaiduNaviManagerFactory.getRouteGuideManager().onPause();

BaiduNaviManagerFactory.getRouteGuideManager().onStop();

BaiduNaviManagerFactory.getRouteGuideManager().onDestroy(false);

4.2设置导航监听,增加结束导航处理

设置导航事件监听,在onNaviGuideEnd()中结束当前activity代码如下,其余接口暂时空实现

//设置导航事件监听

BaiduNaviManagerFactory.getRouteGuideManager().setNaviListener(new IBNaviListener() {

//..其余一些空实现接口

@Override

public void onNaviGuideEnd() {

finish();

}

});

设置导航view监听,在onNaviBackClick()内终止导航,其余接口空实现

//设置导航视图监听

BaiduNaviManagerFactory.getRouteGuideManager().setNaviViewListener(new IBNaviViewListener() {

//。。。。一些空实现接口。。。。

@Override

public void onNaviBackClick() {

BaiduNaviManagerFactory.getRouteGuideManager().stopNavi();

}

//。。。一些空实现接口。。。。。

});

程序效果,以下分别为真实导航、模拟导航、退出导航的效果

五、解决遇到的问题

5.1 jni接口找不到实现No implementation found for......

错误log如下

No implementation found for boolean com.baidu.navisdk.jni.nativeif.JNIGuidanceControl.SetRotateMode(int) (tried Java_com_baidu_navisdk_jni_nativeif_JNIGuidanceControl_SetRotateMode and Java_com_baidu_navisdk_jni_nativeif_JNIGuidanceControl_SetRotateMode__I) at com.baidu.navisdk.jni.nativeif.JNIGuidanceControl.SetRotateMode(Native Method)

如果集成so库没有问题,出现这个问题多半是要更新库,我这里是更新库之后ok.

5.2 BaiduNaviManagerFactory.getRouteGuideManager().onCreate(this, config)崩溃

崩溃log如下

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.ViewGroup.findViewById(int)' on a null object reference

原因:DemoGuideActivity不能继承自AppCompatActivity

修改:改成public class DemoGuideActivity extends FragmentActivity ,崩溃问题解决

六、遗留问题没有声音

导航没有声音