一些地图相关的app一般都需要集成导航的功能方便用户使用,但实际上我们很多时候仅仅只是希望提供这样一个贴心的功能把用户送到目的地,而并不会在导航过程中进行自己的业务的处理,这样把导航的功能完全独立出来了,像这样的需求我们就不用在app里自己去集成导航的功能,而是启调用户手机里已有的导航工具这样不是更好吗,像购物类的应用只需要关心导航的结果把用户送到商城,而不关心其过程。调启用户已安装地图工具来导航用户自然也更加熟悉自己常用的地图工具。这样反而方便了用户也简单了我们的应用。那么如何在自己app内检测安装的地图工具并启调呢:
由于目前提供地图工具的各种三方的启调方法和参数都不一样,所有不能直接使用资源管理器来打开地图工具,只能自己去用户手机里检测地图工具,并更具用户的选择不同而使用不同的启调方式来启调地图工具。
我做了一个demo,可以启调百度和高度地图,由于腾讯地图没有提供启调接口所以并不支持,如果用户手机没有安装地图工具会提示在浏览器中启调百度提供的web导航:
下面是下载地址:
调启百度、高德地图导航Demo
先说一下在自己工程中的使用方法,在解释实现方法
把下面的所有类和layout下的所有部件文件拷贝到自己工程:
然后在需要启调的地方执行下面代码弹出选择启调app的对话框就可以了,该对话框内部会自动检测用户手机应用,决定是打开地图工具还是启用web导航:
NativeDialog msgDialog = new NativeDialog(this, loc_now, loc_end);
msgDialog.show();
这样就可以运行了,使用方法很简单,所有逻辑都封装在NativeDialog对话框里处理了。
功能上主要分成2个部分:
首先,获取地图工具app没有直接的方法,尝试了各种通过过滤来筛选的方法都达不到想要的效果,而且并不是每一种地图工具都能支持启调,比如搜狗地图。目前市面主流的就百度和高德支持,至于腾讯地图官网给的说法是即将支持。这样我们就只能获取手机中所有app的信息来进行筛选,我们只筛选高德和百度,那么我们怎么来筛选呢。每个app都有一个唯一的包名,我们通过对比包名来筛选app,首先我们定义一个集合里面存放需要匹配的应用的包名:
public static String[] paks = new String[]{"com.baidu.BaiduMap", //百度
"com.autonavi.minimap"}; //高德
通过getInstalledPackages方法可以获取到应用所有的app信息:
List<PackageInfo> packageInfos = context.getPackageManager().getInstalledPackages(0);
我们可以先定义一个方法通过包名来筛选app:
/** * 通过包名获取应用信息 * @param context * @param packageName * @return */
public static AppInfo getAppInfoByPak(Context context, String packageName){
PackageManager packageManager = context.getPackageManager();
List<PackageInfo> packageInfos = packageManager.getInstalledPackages(0);
for (PackageInfo packageInfo : packageInfos) {
if (packageName.equals(packageInfo.packageName)) {
AppInfo tmpInfo =new AppInfo();
tmpInfo.setAppName(packageInfo.applicationInfo
.loadLabel(packageManager).toString());
tmpInfo.setPackageName(packageInfo.packageName);
tmpInfo.setVersionName(packageInfo.versionName);
tmpInfo.setVersionCode(packageInfo.versionCode);
tmpInfo.setAppIcon(packageInfo.applicationInfo.loadIcon(packageManager));
return tmpInfo;
}
}
return null;
}
然后调用上面的方法把集合中的报名逐个进行匹配:
/** * 返回当前设备上的地图应用集合 * @param context * @return */
public static List<AppInfo> getMapApps(Context context) {
LinkedList<AppInfo> apps = new LinkedList<AppInfo>();
for (String pak : paks) {
AppInfo appinfo = getAppInfoByPak(context,pak);
if (appinfo!=null) {
apps.add(appinfo);
}
}
return apps;
}
然后在初始化对话框的时候根据匹配的结果动态生成布局,给弹出窗设置应用的名称和图标:
for (int i = 0; i < apps.size(); i++) {
AppInfo app = apps.get(i);
//定义左右边距15
LinearLayout.LayoutParams para = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
para.setMargins(15, 0, 15, 0); // left,top,right, bottom
para.gravity = Gravity.CENTER;
TextView textView = new TextView(context);
textView.setCompoundDrawablesWithIntrinsicBounds(null,app.getAppIcon(), null, null); //设置图标
textView.setText(app.getAppName()); //设置文字
textView.setTextAppearance(context, R.style.text_small_dark); //设置风格
textView.setLayoutParams(para); //设置边距
textView.setGravity(Gravity.CENTER_HORIZONTAL); //设置图标文字水平居中
textView.setSingleLine(true); //设置单行显示
textView.setEllipsize(TruncateAt.END); //设置超出长度显示省略…
textView.setMaxEms(6); //设置最大长度
textView.setTag(app.getPackageName()); //设置包名为tag
textView.setOnClickListener(applistener); //设置监听
views.add(textView);
...
}
这样就完成了第一步。现在获取到了应用的信息,那么如何启调呢。
每个三方应用的启调方式都不同,这就要查阅官方文档,然后制订相应的方法来启调它,这里我总结了目前市面上可启调的地图应用,把启调方法都封装在一个工具类里AppUtil,里面给出了百度高德和web浏览器的几种启调方法:
这里贴上高德和百度地图的通过Intent启调的方法:
public static void startNative_Baidu(Context context,Location loc1,Location loc2){
if (loc1==null || loc2==null) {
return;
}
if (loc1.getAddress()==null || "".equals(loc1.getAddress())) {
loc1.setAddress("我的位置");
}
if (loc2.getAddress()==null || "".equals(loc2.getAddress())) {
loc2.setAddress("目的地");
}
try {
Intent intent = Intent.getIntent("intent://map/direction?origin=latlng:"+loc1.getStringLatLng()+"|name:"+loc1.getAddress()+"&destination=latlng:"+loc2.getStringLatLng()+"|name:"+loc2.getAddress()+"&mode=driving&src=重庆快易科技|CC房车-车主#Intent;scheme=bdapp;package=com.baidu.BaiduMap;end");
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(context, "地址解析错误", Toast.LENGTH_SHORT).show();
}
}
public static void startNative_Gaode(Context context,Location loc){
if (loc==null) {
return;
}
if (loc.getAddress()==null || "".equals(loc.getAddress())) {
loc.setAddress("目的地");
}
try {
Intent intent = new Intent("android.intent.action.VIEW",
android.net.Uri.parse("androidamap://navi?sourceApplication=CC房车-车主&poiname=重庆快易科技&lat="+loc.getLat()+"&lon="+loc.getLng()+"&dev=1&style=2"));
intent.setPackage("com.autonavi.minimap");
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(context, "地址解析错误", Toast.LENGTH_SHORT).show();
}
}
其中传入的参数Location类型是封装坐标的实体类,如下:
public class Location implements Serializable{
private double lat; //坐标纬度
private double lng; //坐标经度
private String address; //地址
//get&set方法
}
注意高德地图参数只需要终点坐标就可以了,而百度地图启调导航需要一个起始坐标和终点坐标,起始坐标就是你的当前位置你要自行获取。
实际运行后发现,高德地图启调后直接进入导航,而百度地图会先进入路径规划页面,下面有个开始导航的按钮点击后才会开始导航:
虽然要多点一下,但事实上用户并不喜欢这样,难度百度地图就不能直接进入导航吗,自行研究发现百度地图也可以启调后直接进入导航,不过要使用SDK提供的方法来进行启调,这就必须在工程中导入百度的SDK,导入后直接使用下面的方法就可以了:
//######################################
//通过SDK接口启调地图工具
//######################################
public static void startNativeBySDK_Baidu(Context context,Location loc1,Location loc2){
if (loc1==null || loc2==null) {
return;
}
if (loc1.getAddress()==null || "".equals(loc1.getAddress())) {
loc1.setAddress("我的位置");
}
if (loc2.getAddress()==null || "".equals(loc2.getAddress())) {
loc2.setAddress("目的地");
}
NaviParaOption para = new NaviParaOption().startPoint(MyDistanceUtil.entity2Baidu(loc1))
.startName(loc1.getAddress())
.endPoint(MyDistanceUtil.entity2Baidu(loc2))
.endName(loc2.getAddress());
try {
BaiduMapNavigation.openBaiduMapNavi(para, context);
} catch (BaiduMapAppNotSupportNaviException e) {
e.printStackTrace();
Toast.makeText(context, "地址解析错误", Toast.LENGTH_SHORT).show();
}
}
使用这种方式就可以直接进入导航,但是麻烦的是必须导入SDK,真是不知道百度这样设计的用意是…
如果用户手机没有装地图工具,那么就启调默认浏览器调用web导航,目前就只有百度提供了在浏览器中的导航支持,同样的也有两种启调方式,一种外部直接调,一种使用SDK调用…
//###########################################
//第一种方式:这种方式需要导入百度sdk,才能进行启调,如果没导入会找不到类
//建议使用这种方式,对浏览器的兼容更好。
//注释掉这里段代码,取消下面第二种方式的注释可以启用第二种方式
NaviParaOption para = new NaviParaOption()
.startPoint(MyDistanceUtil.entity2Baidu(loc_now))
.endPoint(MyDistanceUtil.entity2Baidu(loc_end));
BaiduMapNavigation.openWebBaiduMapNavi(para, context);
//###########################################
//第二种方式:这种方式不需要导入百度sdk,可以直接使用
//不推建使用这种方式,浏览器兼容问题比较严重,比如qq浏览器会封杀百度的此功能。
//注释掉这里段代码,取消上面第一种方式的注释可以启用第一种方式
//###########################################
//String url = APPUtil.getWebUrl_Baidu(loc_now, loc_end);
//Intent intent = new Intent(Intent.ACTION_VIEW);
//intent.setData(Uri.parse(url));
//context.startActivity(intent);
//###########################################
上面调用的一个静态方法,用于构建启调Intent的url:
//######################################
//通启调web地图
//######################################
public static String getWebUrl_Baidu(Location loc1,Location loc2){
if (loc1==null || loc2==null) {
return null;
}
if (loc1.getAddress()==null || "".equals(loc1.getAddress())) {
loc1.setAddress("我的位置");
}
if (loc2.getAddress()==null || "".equals(loc2.getAddress())) {
loc2.setAddress("目的地");
}
return "http://api.map.baidu.com/direction?origin=latlng:"+loc1.getStringLatLng()+"|name:"+loc1.getAddress()+"&destination=latlng:"+loc2.getStringLatLng()+"|name:"+loc2.getAddress()+"&mode=driving&src=重庆快易科技|CC房车-车主";
}