现在你已经学会了非常多的Android技能,并且通过这些技能你完全可以编写出相当不错的应用程序了。不过本章中,我们将要学习一些全新的Android技术,这些技术有别于传统的PC或Web领域的应用技术,是只有在移动设备上才能实现的。
说到只有在移动设备上才能实现的技术,很容易就让人联想到基于位置的服务(Location Based Service )o由于移动设备相比于电脑可以随身携带,我们通过地理定位的技术就可以随时得知自己所在的位置,从而围绕这一点开发出很多有意思的应用。本章中我们就将针对这一点进行讨论,学习一下基于位置的服务究竟是如何实现的。
11.1基于位置的服务简介
基于位置的服务简称LBS,随着移动互联网的兴起,这个技术在最近的几年里十分火爆。其实它本身并不是什么时髦的技术,主要的工作原理就是利用无线电通讯网络或GPS等定位方式来确定出移动设备所在的位置,而这种定位技术早在很多年前就已经出现了。
那为什么LBS技术直到最近几年才开始流行呢?这主要是因为,在过去移动设备的功能极其有限,即使定位到了设备所在的位置,也就仅仅只是定位到了而已,我们并不能在位置的基础上进行一些其他的操作。而现在就大大不同了,有了 Android系统作为载体,我们可以利用定位出的位置进行许多丰富多彩的操作。比如说天气预报程序可以根据用户所在的位置自动选择城市,发微博的时候我们可以向朋友们晒一下自己在哪里,不认识路的时候随时打开地图就可以查询路线,等等。
介绍了这么多,相信你已经按捺不住了吧?我们马上就要开始本章的学习之旅,但在开始之前,还有一些事情是你必须要知道的。
首先你要清楚,基于位置的服务所围绕的核心就是要先确定出用户所在的位置。通常有两种技术方式可以实现:一种是通过GPS定位,一种是通过网络定位。GPS定位的工作原理是基于手机内置的GPS硬件直接和卫星交互来获取当前的经纬度信息,这种定位方式精确度非常高,
但缺点是只能在室外使用,室内基本无法接收到卫星的信号。网络定位的工作原理是根据手机当 前网络附近的三个基站进行测速,以此计算出手机和每个基站之间的距离,再通过三角定位确定 出一个大概的位置,这种定位方式精确度一般,但优点是在室内室外都可以使用。
Android对这两种定位方式都提供了相应的API支持,但是由于一些特殊原因,Google的网络服务在中国不可访问,从而导致网络定位方式的API失效。而GPS定位虽然不需要网络,但是必须要在室外才可以使用,因此你在室内开发的时候很有可能会遇到不管使用哪种定位方式都无法成功定位的情况。
基于以上原因,我决定就不在本书中讲解Android原生定位API的用法了,而是使用一些国内第三方公司的SDKO目前国内在这一领域做得比较好的一个是百度,一个是高德,本章我们就来学习一下百度在LBS方面提供的丰富多彩的功能。
11.2 申请 API Key
要想在自己的应用程序里使用百度的LBS功能,首先必须申请一个API Key。你得拥有一个百度账号才能进行申请,我相信大多数人早就已经拥有了吧?如果你还没有的话,赶快去注册一个吧。
有了百度账号之后,我们就可以申请成为一名百度开发者了,登录你的百度账号,并打开http://developer.baidu.com/user/reg这个网址,在这里填写一些注册信息即可,如图11.1所示。
http://developer.baidu.com/user/reg这个网址,在这里填写一些注册信息即可,如图11.1所示。
只需填写带有号的那部分内容就足够了,接下来点击提交,会显示如图11.2所示的 界面。
接着点击“去我的邮箱”,将会进入到我们刚才填写的邮箱当中,这时收件箱中应该会有一 封刚刚收到的邮件,这就是百度发送给我们的验证邮件,点击邮件当中的链接就可以完成注册了 , 如图11.3所示。
到此一切顺利!这样你就已经成为一名百度开发者了。接着访问百度地图开放平台 | 百度地图API SDK | 地图开发百度地图开放平台 | 百度地图API SDK | 地图开发百度地图开放平台 | 百度地图API SDK | 地图开发 apiconsole/key这个地址,然后同意百度开发者协议,会看到如图11.4所示的界面。
由于这是一个刚刚注册的账号,所以目前的应用列表是空的。接下来点击创建应用就可以去 申请API Key T,应用名称可以随便填,应用类型选择Android SDK,启用服务保持默认即可, 如图11.5所示。
那么,这个发布版SHA1和开发版SHA1又是个什么东西呢?这是我们申请API Key所必须 填写的一个字段,它指的是打包程序时所用签名文件的SHA1指纹,可以通过Android Studio查看 到。打开Android Studio中的任意一个项目,点击右侧工具栏的Gradle—项目名fapp—Tasksi android,如图 11.6所示。
这里展示了一个Android Studio项目中所有内置的Gradle Tasks,其中signingReport这个Task 就可以用来查看签名文件信息。双击signingReport,结果如图H.7所示。
其中,91:16:04:30:C0:8B:6E:53:92:47:57:E6:FB:10:EF:08:lB:73:E6:3E 就是我们所需的 SHA1 指纹了,当然你的Android Studi。中显示的指纹和我的肯定是不一样的。另外需要注意,目前我 们使用的是debug.keystore文件所生成的指纹,这是Android自动生成的一个用于测试的签名文 件。而当你的应用程序发布时还需要创建一个正式的签名文件,如果要得到它的指纹,可以在 cmd中输入如下命令:
keytool -list -v -keystore v签名文件路径>
然后输入正确的密码就可以了。创建签名文件的方法我们将在第15章中学习。
那么也就是说,现在得到的这个SHA1指纹实际上是一个开发版的SHA1指纹,不过因为暂 时我们还没有一个发布版的SHA1指纹,因此这两个值都填成一样的就可以了。最后还剩下一个 包名选项,虽然目前我们的应用程序还不存在,但可以先将包名预定下来,比如就叫com.example. Ibstest,这样所有的内容就都填写完整了,如图11.8所示。
接下来点击提交,应用就应该创建成功了,如图11.9所示。
其中,i6VD2fHKM3msMfZtIOXAhFSzDiYGFIwL就是申请到的API Key,有了它就可以进 行后续的LBS开发工作了,那么我们马上开始吧。
11.3使用百度定位
现在正是趁热打铁的好时机,新建一个LBSTest项目,包名应该就会自动被命名为 com.example.lbstest0另外需要注意,本章中所写的代码建议你都在手机上运行,虽然模拟器中也 提供了模拟地理位置的功能,但在手机上可以得到真实的位置数据,你的感受会更加深刻。
11.3.1 准备 LBSSDK
在开始编码之前,我们还需要先将百度LBS开放平台的SDK准备好,下载地址是: http://lbsyun.baidu.com/sdk/downloado本章中我们会用到基础地图和定位功能这两个SDK,将它 们勾选上,然后点击“开发包”下载按钮即可,如图11.10所示。
下载完成后对该压缩包解压,其中会有一个libs目录,这里面的内容就是我们所需要的一切 了,如图11.11所示。
libs目录下的内容又分为两部分,BaiduLBS Android.jar这个文件是Java层要使用到的,其 他子目录下的so文件是Native层要用到的。so文件是用C/C++语言进行编写,然后再用NDK 编译出来的。当然这里我们并不需要去编写C/C++的代码,因为百度都已经做好了封装,但是我 们需要将libs目录下的每一个文件都放置到正确的位置。
首先观察一下当前的项目结构,你会发现app模块下面有一个libs目录,这里就是用来存放 所有的Jar包的,我们将BaiduLBS_Android.jar复制到这里,如图11.12所示。
接下来展开src/main目录,右击该目^^New->Directory,再仓!J建一个名为jniLibs的目录, 这里就是专门用来存放so文件的,然后把压缩包里的其他所有目录直接复制到这里,如图11.13 所示。
另外,虽然所有新创建的项目中,app/build.gradle文件都会默认配置以下这段声明:
dependencies { compile fileTree(dir: 1 libs ' , include: [' *.jar'])
)…
这表示会将libs目录下所有以.jar结尾的文件添加到当前项目的引用中。但是由于我们是直 接将Jar包复制到libs目录下的,并没有修改gradle文件,因此不会弹出我们平时熟悉的Sync Now 提示。这个时候必须手动点击一下Android Studio顶部工具栏中的Sync按钮(图11.14中最左边 的按钮),不然项目将无法引用到Jar包中提供的任何接口。
点击Sync按钮之后,libs目录下的jar文件就会多出一个向右的箭头,这就表示项目已经能 引用到这些Jar包了,如图11.15所示。
好了,这样我们就把LBS的SDK都准备好了,接下来开始编码吧。
11.3.2确定自己位置的经纬度
首先修改activity main.xml中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layoutwidth="matchparent"
android:layoutheight="matchparent" >
<TextView
android:id="@+id/position_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
布局文件中的内容非常简单,只有一个TextView控件,用于稍后显示当前位置的经纬度 信息。
然后修改AndroidManifest.xml文件中的代码,如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.\bstest">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
android:name="android.permission.ACCESS_FINE_L0CATI0N'7> android: name="android. permission. ACCESS__WIFI_STATE"/> android:name="android.permission.ACCESS_NETWORK_STATE"/> android:name=Handroid.permission.CHANGE_WIFI_STATE"/> android: name=llandroid ・ permission. READ_PHONE_ST ATE " /> android:name=Handroid.permission.WRITE_EXTERNAL_ST0RAGE'7> android:name="android. permission.INTERNET"/>
<application
android:aflowBackup="true"
android: icon="(amipmap/ic_launcher"
android:label="@string/app name"
android:supportsRtl="true" android: theme="(astyle/AppTheme">
<meta-data
android:name="com.baidu.Lbsapi.API_KEY" android:value="i6VD2fHKM3msMfZtI0xMhFSzDiYGF:IwL“ />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category* LAUNCHER" /> </intent-filter>
</activity>
<service android:name="com.baidu.location.f" android:enabled=,,true" android:process=n:remote">
</service>
</application>
</manifest>
AndroidManifest.xml文件改动比较多,我们来仔细阅读一下。可以看到,这里首先添加了很 多行权限声明,每一个权限都是百度LBS SDK内部要用到的。然后在<application>标签的内 部添加了一个<meta-data>标签,这个标签的android:name部分是固定的,必须填com.baidu. lbsapi.API_KEY, android: value部分则应该填入我们在11.2节申请到的API Key。最后,还 需要再注册一个LBS SDK中的服务,不用对这个服务的名字感到疑惑,因为百度LBSSDK中的 代码都是混淆过的。
接下来修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
public LocationClient mLocationClient;
private Textview positionText;
^Override
protected void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
mLocationClient = new Locationclient(getApplicationContext()); mLocationClient.registerLocationListener(new MyLocationListener()); setContentView(R.layout.activity_main);
positionText = (Textview) findViewByld(R.id.positiontextview); List<String> permissionList = new ArrayList<>();
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest. permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { permissionList.add(Manifest.permission.ACCESSFINELOCATION);
} ~ ~
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest. permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { permissionList.add(Manifest.permission.READPHONESTATE);
} ~ ~
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.
pemission.WRITE__EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { permissionList.add(Manifest.pe rmis s ion.WRITEEXTERNALSTORAGE);
} " -
if (!permissionList.isEmpty()) {
String [ ] permissions = permissionList .toArray(new St ring [permissionList. size()]);
ActivityCompat.requestPermissions(MainActivity.this, permissions, 1);
} else { requestLocation();
}
}
private void requestLocation() ( mLocationClient.start();
}
^Override
public void onRequestPermissionsResult(int requestcode, String!] permissions, int[] grantResults) ( switch (requestcode) {
case 1:
if (grantResults.length > 0) { for (int result : grantResults) ( if (result != PackageManager.PERMISSION GRANTED) {
Toast.makeText(this, ”必须同意所有权F赫能使用本程序",
Toast.LENGTH_SHORT).show();
finish();
return;
}
} requestLocation();
} else {
Toast.makeText(this, ”发生未知错误”,Toast.LENGTH_SHORT).show(); finish();
}
break;
default:
public class MyLocationListener implements BDLocationListener {
^Override
public void onReceiveLocation(BDLocation location) { StringBuilder currentposition = new StringBuilder(); currentPosition.append(" ^>1: ").append(location.getLatitude()).
append("Xn");
currentposition.append("经线:").append(location.getLongitude()). append("\nu);
currentPosition.append("定位方式:“);
if (location.getLocType() == BDLocation.TypeGpsLocation) { currentposition.append("GPS");
} else if (location,getLocType() == BDLocation.TypeNetWorkLocation) { currentposition.append(;
} positionText.setText(currentposition);
}
}
}
可以看到,在onCreate()方法中,我们首先创建了一个Locationclient的实例, Locationclient 的构建函数接收一个 Context 参数,这里调用 getApplicationContext ()方 法来获取一个全局的Context参数并传入。然后调用Locationclient的registerLocation- ListenerO方法来注册一个定位监听器,当获取到位置信息的时候,就会回调这个定位监听器。
接下来看一下这里运行时权限的用法,由于我们在AndroidManifest.xml中声明了很多权限, 参考一下7.2.1小节中的危险权限表格可以发现,其中ACCESS_COARSE_LOCATION. ACCESS, FINE_L0CATI0N. READ_PHONE_STATE、WRITE_EXTERNAL_STORAGE 这 4 个权限是需要进行运行 时权限处理的,不过由于ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION都属于同一个 权限组,因此两者只要申请其一就可以了。那么怎样才能在运行时一次性申请3个权限呢?这里 我们使用了一种新的用法,首先创建一个空的List集合,然后依次判断这3个权限有没有被授权, 如果没被授权就添加到List集合中,最后将List转换成数组,再调用ActivityCompat. requestpermissions ()方法一次性申请。
除此之外,onRequestPermissionsResult()方法中对权限申请结果的逻辑处理也和之前 有所不同,这次我们通过一个循环将申请的每个权限都进行了判断,如果有任何一个权限被拒绝, 那么就直接调用finishO方法关闭当前程序,只有当所有权限都被用户同意了,才会调用 requestLocation()方法开始地理位置定位。
requestLocation()方法中的代码比较简单,只是调用了一下Locationclient的start() 方法就能开始定位了。定位的结果会回调到我们前面注册的监听器当中,也就是 MyLocationListenero 观察一下 MyLocationListener 的。成配£加£1_。8以&()方法中,在这里我 们通过BDLocation的getLatitude()方法获取当前位置的纬度,通过getLongitude()方法获取当前位置的经度,通过getLocTypeO方法获取当前的定位方式,最终将结果组装成一个字符 串,显示到TextView上面。
现在我们可以来运行一下程序了,如图11.16所示。毫无疑问,打开程序首先就会弹出运行 时权限的申请对话框,注意看对话框的底部,提示我们一共有3项权限申请,当前是第1项,授 权了第1项后就会显示第2项,这里我们全部点击允许,然后就会立刻开始定位了,结果如图11.17 所示。
可以看到,设备当前的经纬度信息已经成功定位岀来了。
不过,在默认情况下,调用Locationclient的start()方法只会定位一次,如果我们正在快 速移动中,怎样才能实时更新当前的位置呢?为此,百度LBS SDK提供了一系列的设置方法, 来允许我们更改默认的行为,修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
private void requestLocation() {
initLocation(); mLocationClient.start();
}
private void initLocation()(
LocationClientOption option = new LocationClientOption(); option.setScanSpan(50G0);
mLocationClient.setLocOption(option);
}
@0verride
protected void onDestroyO { super.onDestroy(); mLocationClient ・ stopO;
}
这里增加了一个initLocation()方法,在initLocation()方法中我们创建了一个 LocationClientOption对象,然后调用它的setScanSpan()方法来设置更新的间隔。这里传 入了 5000,表示每5秒钟会更新一下当前的位置。
最后要记得,在活动被销毁的时候一定要调用Locationclient的stopO方法来停止定位, 不然程序会持续在后台不停地进行定位,从而严重消耗手机的电量。
现在重新运行一下程序,然后拿着手机随处移动,你会发现界面上的经纬度信息也会跟着一 起变化的。
11.3.3选择定位模式
还记得在本章刚开始的时候说过,Android中主要有两种定位方式吗? 一种是通过GPS定位, 一种是通过网络定位。而从上一小节中的例子中应该可以看出,我们一直是使用的网络定位。那 么如何才能切换到精确度更高的GPS定位呢?本小节我们就来学习一下。
首先,GPS定位功能必须要由用户主动去启用才行,不然任何应用程序都无法使用GPS获 取到手机当前的位置信息。进入手机的设置_>位置信息,如图11.18所示。
我们可以通过顶部的开关来控制定位功能是开启还是关闭,另外,点击“模式”可以选择具 体的定位模式,如图11.19所示。
其中,高精确度模式表示允许使用GPS、无线网络、蓝牙或移动网络来进行定位,节电模式 表示仅允许使用无线网络、蓝牙或移动网络来进行定位,而仅限设备模式表示仅允许使用GPS 来进行定位。也就是说,如果我们想要使用GPS定位功能,这里必须要选择高精确度模式,或者仅限设备模式。
当然,你并不需要担心一旦启用GPS定位功能后,手机的电量就会直线下滑,这只是表明你 已经同意让应用程序来对你的手机进行GPS定位了,但只有当定位操作真正开始的时候,才会影响到手机的电量。
开启了 GPS定位功能之后,再回来看一下代码。我们可以在initLocationO方法中对百度 LBS SDK的定位模式进行指定,一共有3种模式可选:Hight Accuracy. Battery Saving和 Device_Sensors0 Hight Accuracy表示高精确度模式,会在GPS信号正常的情况下优先使用GPS 定位,在无法接收GPS信号的时候使用网络定位。Battery_Saving表示节电模式,只会使用网络 进行定位。Device Sensors表示传感器模式,只会使用GPS进行定位。其中,Hight Accuracy是默认的模式,也就是说,我们即使不修改任何代码,只要拿着手机走到室外去,让手机可以接收到GPS信号,就会自动切换到GPS定位模式了。
当然我们也可以强制指定只使用GPS进行定位,修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
private void initLocation(){ LocationClientOption option = new LocationClientOption();
option・ setLocationMode(LocationClientOption.LocationMode・ Device__Sensors);
mLocationClient.setLocOption(option);
}
这里调用了 setLocationModeO方法来将定位模式指定成传感器模式,也就是说只能使用 GPS进行定位。重新运行一下程序,然后拿着你的手机走到室外去,结果如图11.20所示。
11-3.4看得懂的位置信息
话说回来,刚才我们虽然成功获取到了设备当前位置的经纬度信息,但遗憾的是,这种经纬 度的值一般人是根本看不懂的,相信谁也无法立刻答岀南纬25度、东经148度是什么地方吧? 为了能够更加直观地阅读,我们还需要学习一下如何获取看得懂的位置信息。
幸运的是,百度LBS SDK在这方面提供了非常好的支持,我们只需要进行一些简单的接口调用就能得到当前位置各种丰富的地址信息,下面就来一起看一下吧。
修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
private void initLocation(){
LocationClientOption option = new LocationClientOption();
option.setScanSpan(5000);
option.setlsNeedAddress(true); mLocationClient.setLocOption(option);
)
public class MyLocationListener implements BDLocationListener (
^Override
public void onReceiveLocation(BDLocation location) { StringBuilder currentposition = new StringBuilder(); currentposition. append ("纬度:").append (location. getLatitude()). appendCXn");
currentPosition.append(.append(location.getLongitude()). a叩end("\n");
currentposition.append(”国家:").append(location.getCountry()). append(H\n");
currentposition.append(: ").append(location.getProvince()). append("\n");
currentposition.append(11 市:").append(location.getCity()).append("Xn"); currentposition.append("K: ").append(location.getDistrict())・ append("Xn");
currentposition.append("«: ").append(location.getStreet()). append("\nu);
currentposition.append(;
if (location.getLocType() == BDLocation.TypeGpsLocation) { currentposition.append("GPS");
} else if (location.getLocType() == BDLocation.TypeNetWorkLocation) { currentposition.append(;
} positionText.setText(currentPosition);
}
}
}
首先在 initLocation()方法中,我们调用了 LocationClientOption 的 setlsNeedAddress() 方法,并传入true,这就表示我们需要获取当前位置详细的地址信息。
接下来在MyLocationListener的onReceiveLocation ()方法就可以获取到各种丰富的地址 信息了,调用getCountryO方法可以得到当前所在国家,调用getProvince()方法可以得到当 前所在省份,以此类推。另外还有一点需要注意,由于获取地址信息一定需要用到网络,因此即 使我们将定位模式指定成了 Device Sensors,也会自动开启网络定位功能。
现在重新运行一下程序,结果如图11.21所示。
可以看到,手机当前位置的地址信息已经成功显示岀来了。如果你带着手机移动了较远的距 离,界面上显示的位置也会跟着一起变化的。
11.4使用百度地图
现在手机地图的应用真的可以算得上是非常广泛了,和pc上的地图相比,手机地图能够随时随地进行查看,并且轻松构建出行路线,使用起来明显更加地方便。但是你有没有想过,其实我们在自己的应用程序里也是可以加入地图功能的,比如优步中使用的就是百度地图。本节我们就来学习一下这方面的知识。
11.4.1让地图显示出来
由于在上一节中我们已经将LBS SDK全部准备好了,其中就包括了地图功能,因此这里就不用再去下载百度地图的SDK 了。
那么我们直接在LBSTest项目的基础上进行开发,修改activity_main.xm 1中的代码,如下所示:
<LinearLayout xmlns:android=Hhttp://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/position_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone,' />
<com.baidu.mapapi.map・ MapView
and roid: id=,,@+id/bmapView*'
android :layout_width="match__parent"
android:layout_height="match_parent"
android:clickable="true" />
</LinearLayout>
这里在布局文件中新放置了一个MapView控件,并让它填充满整个屏幕。这个MapView是由百度提供的自定义控件,所以在使用它的时候需要将完整的包名加上。另外,之前用于显示定位信息的TextView现在暂时用不到了,我们将它的visibility属性指定成gone,让它在界面上隐藏起来。
接下来修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
private MapView mapView;
@Override
protected void onCreate(Bundle savedlnstanceState) (
super.onCreate(savedlnstanceState); mLocationClient = new Locationclient(getApplicationContext()); mLocationClient.registerLocationListener(new MyLocationListener()); SDKInitializer.initialize(getApplicationContext()); setContentView(R.layout.activitymain);
mapView = (MapView) findViewByld(R.id.bmapView);
}
^Override protected void onResume() { super.onResumeO; mapView.onResume();
}
^Override protected void onPause() { super.onPause(); mapView.onPauseO;
}
(^Override protected void onDestroy() { super.onDestroy(); mLocationClient.stopO;
mapView.onDestroy();
可以看到,这里的代码也非常简单。首先需要调用SDKInitializer的initialize()方法来进 行初始化操作,initialize()方法接收一个Context参数,这里我们调用getApplication- Context()方法来获取一个全局的Context参数并传入。注意初始化操作一定要在setContent- View()方法前调用,不然的话就会出错。接下来我们调用findViewByldO方法获取到了 MapView的实例,这个实例在后面的功能当中还会用到。
另外还需要重写onResume(). onPause()和onDestroy ()这3个方法,在这里对MapView 进行管理,以保证资源能够及时地得到释放。
好了,就是这么简单。现在重新运行一下程序,百度地图就应该成功显示出来了,如图11.22 所示。
11.4.2移动到我的位置
地图是成功显示出来了,但也许这并不是你想要的。因为这是一张默认的地图,显示的是北 京市中心的位置,而你可能希望看到更加精细的地图信息,比如说自己所在位置的周边环境。显 然,通过缩放和移动的方式来慢慢找到自己的位置是一种很愚蠢的做法。那么本小节我们就来学 习一下,如何才能在地图中快速移动到自己的位置。
百度LBS SDK的API中提供了一个BaiduMap类,它是地图的总控制器,调用Map View的 getMapO方法就能获取到BaiduMap的实例,如下所示:
BaiduMap baiduMap = mapView.getMap();
有了 BaiduMap后,我们就能对地图进行各种各样的操作了,比如设置地图的缩放级别以及 将地图移动到某一个经纬度上。
百度地图将缩放级别的取值范围限定在3到19之间,其中小数点位的值也是可以取的,值越大,地图显示的信息就越精细。比如我们想要将缩放级别设置成12.5,就可以这样写:
MapStatuslIpdate update = MapStatusUpdateFactory. zoomTo (12.5f);
baiduMap.animateMapStatus(update);
其中MapStatusUpdateFactory的zoomTo ()方法接收一个float型的参数,就是用于设置缩放级别的,这里我们传入12.5fo zoomTo()方法返回一个MapStatusUpdate对象,我们把这个对象传入BaiduMap的animateMapStatus ()方法当中即可完成缩放功能。
那么怎样才能让地图移动到某一个经纬度上呢?这就需要借助LatLng类了。其实LatLng 并没有什么太多的用法,主要就是用于存放经纬度值的,它的构造方法接收两个参数,第一个参数是纬度值,第二个参数是经度值。之后调用MapStatusUpdateFactory的newLatLng()方法将 LatLng对象传入,newLatLng()方法返回的也是一个MapStatusUpdate对象,我们再把这个对象传入BaiduMap的animateMapStatus ()方法当中,就可以将地图移动到指定的经纬度上了, 写法如下:
LatLng II = new LatLng(39.915, 116.404);
MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(It);
baiduMap.animateMapStatus(update);
上述代码就实现了将地图移动到北纬39.915度、东经116.404度这个位置的功能。
了解了这些知识之后,接下来再去实现将地图快速移动到自己位置的功能就变得非常简单 了。首先我们可以利用在11.3节中所学的定位技术来获得自己当前位置的经纬度,之后再按照上 述的方法来将地图移动到指定的位置就可以了。
那么下面我们就来继续完善LBSTest这个项目,加入“移动到我的位置”这个功能。修改 MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
private BaiduMap baiduMap;
private boolean isFirstLocate = true;
^Override
protected void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState); mLocationClient = new LocationClient(getApplicationContext()); mLocationClient.registerLocationListener(new MyLocationListener()); SDKInitializer.initialize(getApplicationContext()); setContentView(R.layout.activitymain);
mapView = (MapView) findViewByld(R.id.bmapView); baiduMap = mapView.getMap();
}
private void navigateTo(BDLocation location) { if (isFirstLocate) {
LatLng ll = new LatLngClocation.getLatitude(), location.getLongitude()); MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(ll); baiduMap.animateMapStatus(update);
update = MapStatusUpdateFactory.zoomTo(16f); baiduMap・ animateMapStatus(update); isFirstLocate = false;
} }
public class MyLocationListener implements BDLocationListener {
(QOverride public void onReceiveLocation(BDLocation location) { if (location.getLocType() == BDLocation.TypeGpsLocation
I I location.getLocType() == BDLocation.TypeNetWorkLocation) { navigateTo(location);
}
}
}
}
这里并没有新增多少代码,主要是加入了一个navigateToO方法。这个方法中的代码也很 好理解,先是将BDLocation对象中的地理位置信息取出并封装到LatLng对象中,然后调用 MapStatusUpdateFactory 的 newLatLng()方法并将 LatLng 对象传入,接着将返回的 MapStatusUpdate 对象作为参数传入到BaiduMap的animateMapStatus ()方法当中,和上面介绍的用法 是一模一样的。并且这里为了让地图信息可以显示得更加丰富一些,我们将缩放级别设置成了 16o另外还有一点需要注意,上述代码当中我们使用了一个isFirstLocate变量,这个变量的 作用是为了防止多次调用animateMapStatus()方法,因为将地图移动到我们当前的位置只需要 在程序第一次定位的时候调用一次就可以了。
写好了 navigateToO方法之后,剩下的事情就简单了,当定位到设备当前位置的时候,我 们在onReceiveLocation()方法中直接把BDLocation对象传给navigateToO方法,这样就能 够让地图移动到设备所在的位置了。
现在重新运行一下程序,结果如图11.23所示。
11.4.3让“我”显示在地图上
现在我们已经可以让地图显示我们周边的环境了,但是相信在你平时使用手机地图时应该会 注意到,通常情况下手机地图上应该都会有一个小光标,用于显示设备当前所在的位置,并且如 果设备正在移动的话,那么这个光标也会跟着一起移动。那么我们现在就继续对现有代码进行扩 展,让“我”能够显示在地图上。
百度LBS SDK当中提供了一个MyLocationData. Builder类,这个类是用来封装设备当前 所在位置的,我们只需将经纬度信息传入到这个类的相应方法当中就可以了,如下所示:
MyLocationData.Builder locationBuilder = new MyLocationData.Builder();
locationBuilder.latitude(39.915);
locationBuilder.longitude(116.404);
MyLocationData.Builder类还提供了一个build()方法,当我们把要封装的信息都设置完 成之后,只需要调用它的build ()方法,就会生成一个MyLocationData的实例,然后再将这个 实例传入到BaiduMap的setMyLocationData()方法当中,就可以让设备当前的位置显示在地 图上了,写法如下:
MyLocationData locationData = locationBuilder.build();
baiduMap.setMyLocationData(locationData);
大体思路就是这个样子,下面我们开始来实现一下,修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
(QOverride
protected void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
mLocationClient = new Locationclient(getApplicationContext()); mLocationClient.registerLocationListener(new MyLocationListener()); SDKInitializer.initialize(getApplicationContext());
setContentView(R.layout.activity main);
mapView = (MapView) findViewById?R.id.bmapView); baiduMap = mapView.getMap();
baiduMap.setMyLocationEnabled(true);
}
private void navigateTo(BDLocation location) {
if (isFirstLocate) {
LatLng ll = new LatLng(location.getLatitude(), location.getLongitude()); MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(ll); baiduMap.animateMapStatus(update);
update = MapStatusUpdateFactory.zoomTo(16f);
baiduMap.animateMapStatus(update); isFirstLocate = false;
}
MyLocationData.Builder locationBuilder = new MyLocationData.Builder(); LocationBuilder.latitude(location.getLatitude()); LocationBuilder.longitude(location.getLongitude());
MyLocationData LocationData = locationBuilder.build(); baiduMap.setMyLocationData(locationData);
}
^Override protected void onDestroy() { super.onDestroy(); mLocationClient.stop(); mapView.onDestroy();
baiduMap. setMyLocationEnabled(false);
}
}
可以看到,在navigateTo()方法中,我们添加了 MyLocationData的构建逻辑,将Location 中包含的经度和纬度分另0封装到了 MyLocationData.Builder当中,最后把MyLocationData设置到 了 BaiduMap的setMyLocationData()方法当中。注意这段逻辑必须写在isFirstLocate这个 if条件语句的外面,因为让地图移动到我们当前的位置只需要在第一次定位的时候执行,但是 设备在地图上显示的位置却应该是随着设备的移动而实时改变的。
另外,根据百度地图的限制,如果我们想要使用这一功能,一定要事先调用BaiduMap的 setMyLocationEnabled ()方法将此功能开启,否则设备的位置将无法在地图上显示。而在程序 退出的时候,也要记得将此功能给关闭掉。
就是这么简单,现在重新运行一下程序,结果如图11.24所示。
这样的话,用户就可以非常清晰地看出自己当前是在哪里了。
关于百度LBS SDK的用法我就准备介绍这么多,现在你已经算是成功入门了。如果想要更 加深入地研究百度LBS的各种用法,可以到官方网站上面参考开发指南,地址是: http://lbsyun.baidu.como另外,百度LBS SDK的版本未来随时都有可能更新,也许更新之后会导 致书上的例子无法正常运行,因此除了照着图书学习之外,根据官网的开发指南来进行学习也是 非常重要的,因为官方文档永远都是最新的。
好了,本章的主体内容到这里就结束了。下面我们将再次进入本书的特殊环节,学习一下关 于Git的高级用法。
11.5 Git时间——版本控制工具的高级用法
现在的你对于Git应该完全不会感到陌生了吧,通过了之前两节内容的学习,你已经掌握了 很多Git中常用的命令,像提交代码这种简单的操作相信肯定是难不倒你的。
那么打开Git Bash,并进入到LBSTest这个项目的根目录,然后执行提交操作:
git init
git add .
git commit -m "First Commit."
这样就将准备工作完成了,下面就让我们开始学习关于Git的高级用法。
11.5.1分支的用法
分支是版本控制工具中比较高级且比较重要的一个概念,它主要的作用就是在现有代码的基 础上开辟一个分叉口,使得代码可以在主干线和分支线上同时进行开发,且相互之间不会影响。 分支的工作原理示意图如图11.25所示。
你也许会有疑惑,为什么需要建立分支呢?只在主干线上进行开发不是挺好的吗?没错,通 常情况下,只在主干线上进行开发是完全没有问题的,不过一旦涉及出版本的情况,如果不建立 分支的话,你就会非常地头疼。举个简单的例子吧,比如说你们公司研发了一款不错的软件,最 近刚刚完成,并推出了 1.0版本。但是领导是不会让你们闲着的,马上提出了新的需求,让你们 投入到了 1.1版本的开发工作当中。过了几个星期,1.1版本的功能已完成了一半,但是这个时候 有用户反馈,之前上线的1.0版本发现了几个重大的bug,严重影响软件的正常使用。领导也相 当重视这个问题,要求你们立刻修复这些bug,并重新发布1.0版本,但这个时候你就非常为难 了,你会发现根本没法去修复这些bug。因为现在1.1版本已开发一半了,如果在现有代码的基 础上修复这些bug,那么更新的1.0版本将会带有一半1.1版本的功能!
进退两难了是不是?但是如果你使用了分支的话,就完全不会存在这个让人头疼的问题。你 只需要在发布1.0版本的时候建立一个分支,然后在主干线上继续开发1.1版本的功能。当1.0 版本上发现任何bug的时候,就在分支线上进行修改,然后发布新的1.0版本,并记得将修改后 的代码合并到主干线上。这样的话,不仅可以轻松解决掉1.0版本存在的bug,而且保证了主干 线上的代码也已经修复了这些bug,当1.1版本发布时就不会有同样的bug存在了。
说了这么多,相信你也已经意识到分支的重要性了,那么我们马上来学习一下如何在Git中 操作分支吧。
分支的英文名是branch,如果想要查看当前的版本库当中有哪些分支,可以使用git branch 这个命令,结果如图11.26所示。
图11.26查看所有分支
由于目前LBSTest项目中还没有创建过任何分支,因此只有一个master分支存在,这也就是 前面所说的主干线。接下来我们尝试去创建一个分支,命令如下:
git branch versionl.0
这样就创建了一个名为versionl.0的分支,我们再次输入git branch这个命令来检查一下, 结果如图11.27所示。
图11.27再次查看所有分支
可以看到,果然有一个叫作versionl.0的分支出现了。你会发现,master分支的前面有一个 号,说明目前我们的代码还是在master分支上的,那么怎样才能切换到versionl.O这个分支 上呢?其实也很简单,只需要使用checkout命令即可,如下所示:
git checkout versionl.0
再次输入git branch来进行检查,结果如图11.28所示。
图11.28查看切换分支后的结果
可以看到,我们已经把代码成功切换到versionl.0这个分支上了。
需要注意的是,在versionl.0分支上修改并提交的代码将不会影响到master分支。同样的道 理,在master•分支上修改并提交的代码也不会影响到versionl.0分支。因此,如果我们在versionl.0 分支上修复了一个bug,在master•分支上这个bug仍然是存在的。这时将修改的代码一行行复制 到master分支上显然不是一种聪明的做法,最好的办法就是使用merge命令来完成合并操作, 如下所示:
git checkout master
git merge versionl.0
仅仅这样简单的两行命令,就可以把在versionl.0分支上修改并提交的内容合并到master分 支上了。当然,在合并分支的时候还有可能出现代码冲突的情况,这个时候你就需要静下心来慢 慢地找出并解决这些冲突,Git在这里就无法帮助你了。
最后,当我们不再需要versionl.0这个分支的时候,可以使用如下命令将这个分支删除掉:
git branch -D versionl.0
11.5.2与远程版本库协作
可以这样说,如果你是一个人在开发,那么使用版本控制工具就远远无法发挥出它真正强大 的功能。没错,所有版本控制工具最重要的一个特点就是可以使用它来进行团队合作开发。每个 人的电脑上都会有一份代码,当团队的某个成员在自己的电脑上编写完成了某个功能后,就将代 码提交到服务器,其他的成员只需要将服务器上的代码同步到本地,就能保证整个团队所有人的 代码都相同。这样的话,每个团队成员就可以各司其职,大家共同来完成一个较为庞大的项目。
那么如何使用Git来进行团队合作开发呢?这就需要有一个远程的版本库,团队的每个成员都从这个版本库中获取到最原始的代码,然后各自进行开发,并且以后每次提交的代码都同步到远程版本库上就可以了。另外,团队中的每个成员最好都要养成经常从版本库中获取最新代码的习惯,不然的话,大家的代码就很有可能经常出现冲突。
比如说现在有一个远程版本库的Git地址是https://github.com/example/test.git,就可以使用如 下的命令将代码下载到本地:
git clone https://github.com/example/test.git
之后你在这份代码的基础上进行了一些修改和提交,那么怎样才能把本地修改的内容同步到 远程版本库上呢?这就需要借助push命令来完成了,用法如下所示:
git push origin master
其中origin部分指定的是远程版本库的Git地址,master部分指定的是同步到哪一个分支 上,上述命令就完成了将本地代码同步到https://github.com/example/test.git这个版本库的master 分支上的功能。
知道了将本地的修改同步到远程版本库上的方法,接下来我们看一下如何将远程版本库上的 修改同步到本地。Git提供了两种命令来完成此功能,分别是fetch和pull, fetch的语法规则 和push是差不多的,如下所示:
git fetch origin master
执行这个命令后,就会将远程版本库上的代码同步到本地,不过同步下来的代码并不会合并 到任何分支上去,而是会存放到一个0rigin/master分支上,这时我们可以通过diff命令来 查看远程版本库上到底修改了哪些东西:
git diff origin/master
之后再调用merge命令将origin/master分支上的修改合并到主分支上即可,如下所示:
git merge origin/master
而pull命令则是相当于将fetch和merge这两个命令放在一起执行了,它可以从远程版本 库上获取最新的代码并且合并到本地,用法如下所示:
git pull origin master
也许你现在对远程版本库的使用还是感觉比较抽象,没关系,因为暂时我们只是了解了一下 命令的用法,还没进行实践,在第14章当中,你将会对远程版本库的用法有更深一层的认识。
11.6小结与点评
不得不说,本章中学到的知识应该还算是蛮有趣的吧?在这次的Android特色开发环节中, 我们主要学习了基于位置服务的工作原理和用法,借助百度提供的LBS SDK,我们可以随时确定自己当前位置的经纬度,并且还能获取到具体的省、市、区、街道等地址。之后又学习了百度地图的用法,不仅成功地将地图信息显示了出来,还综合利用了前面所学到的定位技术实现了一 个较为完整的例子。
除了基于位置的服务之外,本章Git时间中继续对Git的用法进行了更深一步的探究,使得我们对分支和远程版本库的使用都有了一定层次的了解。
那么关于Android特色开发的内容就讲到这里,下一章中我们将会学习Android 5.0系统中新增的一套全新的知识点 Material Designo