Android学习记录——11.基于位置的服务(LBS)

1.基于位置的服务简介

基于位置的服务简称LBS,随着移动互联网的兴起,这个技术在最近的几年里十分火爆。其 实它本身并不是什么时髦的技术,主要的工作原理就是利用无线电通讯网络或GPS等定位方式 确定出移动设备所在的位置,而这种定位技术早在很多年前就已经出现了。
那为什么LBS技术直到最近几年才开始流行呢?这主要是因为,在过去移动设备的功能极其有限,即使定位到了设备所在的位置,也就仅仅只是定位到了而已,我们并不能在位置的基础 上进行一些其他的操作。而现在就大大不同了,有了Android系统作为载体,我们可以利用定位出的位置进行许多丰富多彩的操作。比如说天气预报程序可以根据用户所在的位置自动选择城市,发微博的时候我们可以向朋友们晒一下自己在哪里,不认识路的时候随时打开地图就可以查询路线,等等。
介绍了这么多,相信你已经按捺不住了吧?我们马上就要开始本篇博客的学习之旅,但在开始之前,还有一些事情是你必须要知道的。
首先你要清楚,基于位置的服务所围绕的核心就是要先确定出用户所在的位置。通常有两种技术方式可以实现:一种是通过GPS定位,一种是通过网络定位。GPS定位的工作原理是基于手机内置的GPS硬件直接和卫星交互来获取当前的经纬度信息,这种定位方式精确度非常高,但缺点是只能在室外使用,室内基本无法接收到卫星的信号。网络定位的工作原理是根据手机当前网络附近的三个基站进行测速,以此计算出手机和每个基站之间的距离,再通过三角定位确定出一个大概的位置,这种定位方式精确度一般,但优点是在室内室外都可以使用。
Android对这两种定位方式都提供了相应的API支持,但是由于一些特殊原因,Google的网络服务在中国不可访问,从而导致网络定位方式的API失效。而GPS定位虽然不需要网络,但是必须要在室外才可以使用,因此你在室内开发的时候很有可能会遇到不管使用哪种定位方式都 无法成功定位的情况。
基于以上原因,我决定就不在本篇博客中讲解Android原生定位API的用法了,而是使用一些国内第三方公司的SDK。目前国内在这一领域做得比较好的一个是百度,一个是高德,本章我们就来学习一下百度在LBS方面提供的丰富多彩的功能。

2.申请API Key

要想在自己的应用程序里使用百度的LBS功能,首先必须申请一个API Key。你得拥有一个百度账号才能进行申请,我相信大多数人早就已经拥有了吧?如果你还没有的话,赶快去注册一个吧。
有了百度账号之后,我们就可以申请成为一名百度开发者了,登录你的百度账号,并打开 http://developer.baidu.com/user/reg这个网址,在这里填写一些注册信息即可。
注册完成后,进入 http://developer.baidu.com/user/key,创建一个应用,应用类型选择Android SDK,启用服务保持默认即可,如图所示:
在这里插入图片描述
那么,这个发布版SHA1和开发版SHA1又是个什么东西呢?这是我们申请API Key所必须填写的一个字段,它指的是打包程序时所用签名文件的SHA1指纹,可以通过Android Studio查看到。打开Android Studio中的任意一个项目,点击右侧工具栏的Gradle——>项目名——>app——>Tasks——>android,如图所示:
在这里插入图片描述

这里展示了一个Android Studio项目中所有内置的Gradle Tasks,其中signingReport这个Task就可以用来查看签名文件信息。双击signingReport,结果如图所示:
在这里插入图片描述
其中,图中所示的就是我们所需的 SHA1 指纹了,当然你的Android Studio中显示的指纹和我的肯定是不一样的。另外需要注意,目前我们使用的是debug.keystore文件所生成的指纹,这是Android自动生成的一个用于测试的签名文件。而当你的应用程序发布时还需要创建一个正式的签名文件,如果要得到它的指纹,可以在 cmd中输入如下命令:

keytool -list -v -keystore <签名文件路径>

然后输入正确的密码就可以了。创建签名文件的方法我们将在之后学习。
那么也就是说,现在得到的这个SHA1指纹实际上是一个开发版的SHA1指纹,不过因为暂时我们还没有一个发布版的SHA1指纹,因此这两个值都填成一样的就可以了。最后还剩下一个包名选项,虽然目前我们的应用程序还不存在,但可以先将包名预定下来,比如就叫com.example.Ibstest,这样所有的内容就都填写完整了,如图所示:
在这里插入图片描述
接下来点击提交,应用就应该创建成功了,如图所示:
在这里插入图片描述
其中,riQTxI92aXG8s86NxvBcwNPS7EMzcQ8H就是申请到的API Key,有了它就可以进行后续的LBS开发工作了,那么我们马上开始吧。

3.使用百度定位

现在正是趁热打铁的好时机,新建一个LBSTest项目,包名应该就会自动被命名为 com.example.lbstest。另外需要注意,本章中所写的代码建议你都在手机上运行,虽然模拟器中也提供了模拟地理位置的功能,但在手机上可以得到真实的位置数据,你的感受会更加深刻。

3.1 准备LBS SDK

在开始编码之前,我们还需要先将百度LBS开放平台的SDK准备好,下载地址为:http://lbsyun.baidu.com/index.php?title=sdk/download&action,本章中我们会用到基础地图和定位功能这两个SDK,将它们勾选上,然后点击“开发包”下载按钮即可,如图所示:
在这里插入图片描述
下载完成后对该压缩包解压,其中会有一个libs目录,这里面的内容就是我们所需要的一切了,如图所示:
在这里插入图片描述
libs目录下的内容又分为两部分,BaiduLBS_Android.jar这个文件是Java层要使用到的,其他子目录下的so文件是Native层要用到的。so文件是用C/C++语言进行编写,然后再用NDK编译出来的。当然这里我们并不需要去编写C/C++的代码,因为百度都已经做好了封装,但是我们需要将libs目录下的每一个文件都放置到正确的位置。
首先观察一下当前的项目结构,你会发现app模块下面有一个libs目录,这里就是用来存放所有的Jar包的,我们将BaiduLBS_Android.jar复制到这里,如图所示:
在这里插入图片描述
接下来展开src/main目录,右击该目录——>New——>Directory,再创建一个名为jniLibs的目录, 这里就是专门用来存放so文件的,然后把压缩包里的其他所有目录直接复制到这里,如图所示:
在这里插入图片描述
另外,虽然所有新创建的项目中,app/build.gradle文件都会默认配置以下这段声明:

implementation fileTree(dir: 'libs', include: ['*.jar'])

这表示会将libs目录下所有以.jar结尾的文件添加到当前项目的引用中。但是由于我们是直接将Jar包复制到libs目录下的,并没有修改gradle文件,因此不会弹出我们平时熟悉的Sync Now 提示。这个时候必须手动点击一下Android Studio顶部工具栏中的Sync按钮,不然项目将无法引用到Jar包中提供的任何接口。
点击Sync按钮之后,libs目录下的jar文件就会多出一个向右的箭头,这就表示项目已经能引用到这些Jar包了,如图所示:
在这里插入图片描述
好了,这样我们就把LBS的SDK都准备好了,接下来开始编码吧。

3.2 准备自己位置的经纬度

首先修改activity main.xml中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://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" />

</LinearLayout>

布局文件中的内容非常简单,只有一个TextView控件,用于稍后显示当前位置的经纬度信息。
然后修改AndroidManifest.xml文件中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.lbstest">

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
        tools:ignore="ProtectedPermissions" />
    <uses-permission android:name="android.permission.WAKE_LOCK"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <meta-data
            android:name="com.baidu.lbsapi.API_KEY"
            android:value="riQTxI92aXG8s86NxvBcwNPS7EMzcQ8H"/>
        <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=":remote">
        </service>
    </application>

</manifest>

AndroidManifest.xml文件改动比较多,我们来仔细阅读一下。可以看到,这里首先添加了很 多行权限声明,每一个权限都是百度LBS SDK内部要用到的。然后在<application>标签的内 部添加了一个<meta-data>标签,这个标签的android:name部分是固定的,必须填com.baidu.lbsapi.API_KEY,android: value部分则应该填入我们在之前申请到的API Key。最后,还需要再注册一个LBS SDK中的服务,不用对这个服务的名字感到疑惑,因为百度LBS SDK中的 代码都是混淆过的。
接下来修改MainActivity中的代码,如下所示:

package com.example.administrator.lbstest;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
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.SDKInitializer;
import com.baidu.mapapi.map.BaiduMap;
import com.baidu.mapapi.map.MapStatusUpdate;
import com.baidu.mapapi.map.MapStatusUpdateFactory;
import com.baidu.mapapi.map.MapView;
import com.baidu.mapapi.map.MyLocationData;
import com.baidu.mapapi.model.LatLng;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    public LocationClient mLocationClient;

    private TextView positionText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mLocationClient = new LocationClient(getApplicationContext());
        mLocationClient.registerLocationListener(new MyLocationListener());
        setContentView(R.layout.activity_main);
        positionText = (TextView)findViewById(R.id.position_text_view);
        List<String> permissionList = new ArrayList<>();
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.READ_PHONE_STATE);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if (!permissionList.isEmpty()){
            String [] permissions = permissionList.toArray(new String[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,"必须同意所有权限才能使用本程序",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(final BDLocation location){
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    StringBuilder currentPosition = new StringBuilder();
                    currentPosition.append("纬度:").append(location.getLatitude()).append("\n");
                    currentPosition.append("经线:").append(location.getLongitude()).append("\n");
                    currentPosition.append("国家:").append(location.getCountry()).append("\n");
                    currentPosition.append("省:").append(location.getProvince()).append("\n");
                    currentPosition.append("市:").append(location.getCity()).append("\n");
                    currentPosition.append("区:").append(location.getDistrict()).append("\n");
                    currentPosition.append("街道:").append(location.getStreet()).append("\n");
                    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的registerLocationListenerO方法来注册一个定位监听器,当获取到位置信息的时候,就会回调这个定位监听器。
接下来看一下这里运行时权限的用法,由于我们在AndroidManifest.xml中声明了很多权限, 参考一下之前的危险权限表格可以发现,其中ACCESS_COARSE_LOCATION、ACCESS_FINE_LOCATION、READ_PHONE_STATE、WRITE_EXTERNAL_STORAGE 这 4 个权限是需要进行运行时权限处理的,不过由于ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION都属于同一个权限组,因此两者只要申请其一就可以了。那么怎样才能在运行时一次性申请3个权限呢?这里我们使用了一种新的用法,首先创建一个空的List集合,然后依次判断这3个权限有没有被授权, 如果没被授权就添加到List集合中,最后将List转换成数组,再调用ActivityCompat.requestpermissions()方法一次性申请。
除此之外,onRequestPermissionsResult()方法中对权限申请结果的逻辑处理也和之前有所不同,这次我们通过一个循环将申请的每个权限都进行了判断,如果有任何一个权限被拒绝,那么就直接调用finish()方法关闭当前程序,只有当所有权限都被用户同意了,才会调用 requestLocation()方法开始地理位置定位。
requestLocation()方法中的代码比较简单,只是调用了一下Locationclient的start() 方法就能开始定位了。定位的结果会回调到我们前面注册的监听器当中,也就是 MyLocationListener。观察一下 MyLocationListener的onReceiverLocation()方法中,在这里我 们通过BDLocation的getLatitude()方法获取当前位置的纬度,通过getLongitude()方法获取当前位置的经度,通过getLocType()方法获取当前的定位方式,最终将结果组装成一个字符串,显示到TextView上面。
现在我们可以来运行一下程序了,如图所示:
在这里插入图片描述
毫无疑问,打开程序首先就会弹出运行时权限的申请对话框,注意看对话框的底部,提示我们一共有3项权限申请,当前是第1项,授权了第1项后就会显示第2项,这里我们全部点击允许,然后就会立刻开始定位了,结果如图所示:
在这里插入图片描述
可以看到,设备当前的经纬度信息已经成功定位岀来了。
不过,在默认情况下,调用Locationclient的start()方法只会定位一次,如果我们正在快速移动中,怎样才能实时更新当前的位置呢?为此,百度LBS SDK提供了一系列的设置方法, 来允许我们更改默认的行为,修改MainActivity中的代码,如下所示:

package com.example.lbstest;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
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 java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    public LocationClient mLocationClient;

    private TextView positionText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mLocationClient = new LocationClient(getApplicationContext());
        mLocationClient.registerLocationListener(new MyLocationListener());
        setContentView(R.layout.activity_main);
        positionText = (TextView)findViewById(R.id.position_text_view);
        List<String> permissionList = new ArrayList<>();
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.READ_PHONE_STATE);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if (!permissionList.isEmpty()){
            String [] permissions = permissionList.toArray(new String[permissionList.size()]);
            ActivityCompat.requestPermissions(MainActivity.this,permissions,1);
        }else {
            requestLocation();
        }
    }
    private void requestLocation(){
        initLocation();
        mLocationClient.start();
    }

    private void initLocation(){
        LocationClientOption option = new LocationClientOption();
        option.setScanSpan(5000);
        option.setIsNeedAddress(true);
        mLocationClient.setLocOption(option);
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        mLocationClient.stop();
    }



    @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,"必须同意所有权限才能使用本程序",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(final BDLocation location){
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    StringBuilder currentPosition = new StringBuilder();
                    currentPosition.append("纬度:").append(location.getLatitude()).append("\n");
                    currentPosition.append("经线:").append(location.getLongitude()).append("\n");
                    currentPosition.append("国家:").append(location.getCountry()).append("\n");
                    currentPosition.append("省:").append(location.getProvince()).append("\n");
                    currentPosition.append("市:").append(location.getCity()).append("\n");
                    currentPosition.append("区:").append(location.getDistrict()).append("\n");
                    currentPosition.append("街道:").append(location.getStreet()).append("\n");
                    currentPosition.append("定位方式:");
                    if (location.getLocType() == BDLocation.TypeGpsLocation){
                        currentPosition.append("GPS");
                    }else if (location.getLocType() == BDLocation.TypeNetWorkLocation){
                        currentPosition.append("网络");
                    }
                    positionText.setText(currentPosition);
                }
            });
        }
    }
}

这里增加了一个initLocation()方法,在initLocation()方法中我们创建了一个 LocationClientOption对象,然后调用它的setScanSpan()方法来设置更新的间隔。这里传入了5000,表示每5秒钟会更新一下当前的位置。
最后要记得,在活动被销毁的时候一定要调用Locationclient的stop()方法来停止定位, 不然程序会持续在后台不停地进行定位,从而严重消耗手机的电量。
现在重新运行一下程序,然后拿着手机随处移动,你会发现界面上的经纬度信息也会跟着一 起变化的。

3.3 选择定位模式

还记得在本章刚开始的时候说过,Android中主要有两种定位方式吗? 一种是通过GPS定位,,一种是通过网络定位。而从上一小节中的例子中应该可以看出,我们一直是使用的网络定位。那么如何才能切换到精确度更高的GPS定位呢?本小节我们就来学习一下。
首先,GPS定位功能必须要由用户主动去启用才行,不然任何应用程序都无法使用GPS获 取到手机当前的位置信息。进入手机的设置——>位置信息,我们可以通过顶部的开关来控制定位功能是开启还是关闭,另外,点击“模式”可以选择具体的定位模式。
其中,高精确度模式表示允许使用GPS、无线网络、蓝牙或移动网络来进行定位,节电模式表示仅允许使用无线网络、蓝牙或移动网络来进行定位,而仅限设备模式表示仅允许使用GPS来进行定位。也就是说,如果我们想要使用GPS定位功能,这里必须要选择高精确度模式,或者仅限设备模式。
当然,你并不需要担心一旦启用GPS定位功能后,手机的电量就会直线下滑,这只是表明你已经同意让应用程序来对你的手机进行GPS定位了,但只有当定位操作真正开始的时候,才会影响到手机的电量。
开启了 GPS定位功能之后,再回来看一下代码。我们可以在initLocation()方法中对百度 LBS SDK的定位模式进行指定,一共有3种模式可选:Hight Accuracy,Battery Saving和 Device_Sensors 。Hight Accuracy表示高精确度模式,会在GPS信号正常的情况下优先使用GPS定位,在无法接收GPS信号的时候使用网络定位。Battery_Saving表示节电模式,只会使用网络进行定位。Device Sensors表示传感器模式,只会使用GPS进行定位。其中,Hight Accuracy是默认的模式,也就是说,我们即使不修改任何代码,只要拿着手机走到室外去,让手机可以接收到GPS信号,就会自动切换到GPS定位模式了。
当然我们也可以强制指定只使用GPS进行定位,修改MainActivity中的代码,如下所示:

private void initLocation(){
    LocationClientOption option = new LocationClientOption();
    option.setScanSpan(5000);
    option.setIsNeedAddress(true);
    option.setLocationMode(LocationClientOption.LocationMode.Device_Sensors);
    mLocationClient.setLocOption(option);
}

这里调用了 setLocationMode()方法来将定位模式指定成传感器模式,也就是说只能使用GPS进行定位。重新运行一下程序,然后拿着你的手机走到室外去,结果如图所示(用的模拟器测试,所以定位方式还是为网络,如果用真机测试定位方式会变为网络):
在这里插入图片描述

3.4 看得懂的位置信息

话说回来,刚才我们虽然成功获取到了设备当前位置的经纬度信息,但遗憾的是,这种经纬 度的值一般人是根本看不懂的,相信谁也无法立刻答岀南纬25度、东经148度是什么地方吧? 为了能够更加直观地阅读,我们还需要学习一下如何获取看得懂的位置信息。
幸运的是,百度LBS SDK在这方面提供了非常好的支持,我们只需要进行一些简单的接口 调用就能得到当前位置各种丰富的地址信息,下面就来一起看一下吧。
修改MainActivity中的代码(不需要修改,已经加上了详细的地址信息)。
首先在 initLocation()方法中,我们调用了 LocationClientOption 的 setlsNeedAddress() 方法,并传入true,这就表示我们需要获取当前位置详细的地址信息。
接下来在MyLocationListener的onReceiveLocation()方法就可以获取到各种丰富的地址信息了,调用getCountry()方法可以得到当前所在国家,调用getProvince()方法可以得到当 前所在省份,以此类推。另外还有一点需要注意,由于获取地址信息一定需要用到网络,因此即使我们将定位模式指定成了 Device Sensors,也会自动开启网络定位功能。
现在重新运行一下程序,结果如图所示:
在这里插入图片描述

4.使用百度地图

4.1 让地图显示出来

由于在上一节中我们已经将LBS SDK全部准备好了,其中就包括了地图功能,因此这里就不用再去下载百度地图的SDK 了。
那么我们直接在LBSTest项目的基础上进行开发,修改activity_main.xml中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://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
        android:id="@+id/bmapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true"/>
</LinearLayout>

这里在布局文件中新放置了一个MapView控件,并让它填充满整个屏幕。这个MapView是由百度提供的自定义控件,所以在使用它的时候需要将完整的包名加上。另外,之前用于显示定 位信息的TextView现在暂时用不到了,我们将它的visibility属性指定成gone,让它在界面上隐藏起来。
接下来修改MainActivity中的代码,如下所示:

package com.example.lbstest;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
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.SDKInitializer;
import com.baidu.mapapi.map.MapView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    public LocationClient mLocationClient;

    private TextView positionText;
    
    private MapView mapView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mLocationClient = new LocationClient(getApplicationContext());
        mLocationClient.registerLocationListener(new MyLocationListener());
        SDKInitializer.initialize(getApplicationContext());
        setContentView(R.layout.activity_main);
        mapView = (MapView)findViewById(R.id.bmapView);
        positionText = (TextView)findViewById(R.id.position_text_view);
        List<String> permissionList = new ArrayList<>();
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.READ_PHONE_STATE);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if (!permissionList.isEmpty()){
            String [] permissions = permissionList.toArray(new String[permissionList.size()]);
            ActivityCompat.requestPermissions(MainActivity.this,permissions,1);
        }else {
            requestLocation();
        }
    }
    private void requestLocation(){
        initLocation();
        mLocationClient.start();
    }

    private void initLocation(){
        LocationClientOption option = new LocationClientOption();
        option.setScanSpan(5000);
        option.setIsNeedAddress(true);
        mLocationClient.setLocOption(option);
    }

    @Override
    protected void onResume(){
        super.onResume();
        mapView.onResume();
    }

    @Override
    protected void onPause(){
        super.onPause();
        mapView.onPause();
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        mLocationClient.stop();
        mapView.onDestroy();
    }
    
    @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,"必须同意所有权限才能使用本程序",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(final BDLocation location){
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    StringBuilder currentPosition = new StringBuilder();
                    currentPosition.append("纬度:").append(location.getLatitude()).append("\n");
                    currentPosition.append("经线:").append(location.getLongitude()).append("\n");
                    currentPosition.append("国家:").append(location.getCountry()).append("\n");
                    currentPosition.append("省:").append(location.getProvince()).append("\n");
                    currentPosition.append("市:").append(location.getCity()).append("\n");
                    currentPosition.append("区:").append(location.getDistrict()).append("\n");
                    currentPosition.append("街道:").append(location.getStreet()).append("\n");
                    currentPosition.append("定位方式:");
                    if (location.getLocType() == BDLocation.TypeGpsLocation){
                        currentPosition.append("GPS");
                    }else if (location.getLocType() == BDLocation.TypeNetWorkLocation){
                        currentPosition.append("网络");
                    }
                    positionText.setText(currentPosition);
                }
            });
        }
    }
}

可以看到,这里的代码也非常简单。首先需要调用SDKInitializer的initialize()方法来进行初始化操作,initialize()方法接收一个Context参数,这里我们调用getApplication-Context()方法来获取一个全局的Context参数并传入。注意初始化操作一定要在setContentView()方法前调用,不然的话就会出错。接下来我们调用findViewByld()方法获取到了MapView的实例,这个实例在后面的功能当中还会用到。
另外还需要重写onResume()、onPause()和onDestroy()这3个方法,在这里对MapView进行管理,以保证资源能够及时地得到释放。
好了,就是这么简单。现在重新运行一下程序,百度地图就应该成功显示出来了,如图所示:
在这里插入图片描述

4.2 移动到我的位置

地图是成功显示出来了,但也许这并不是你想要的。因为这是一张默认的地图,显示的是北京市中心的位置,而你可能希望看到更加精细的地图信息,比如说自己所在位置的周边环境。显然,通过缩放和移动的方式来慢慢找到自己的位置是一种很愚蠢的做法。那么本小节我们就来学 习一下,如何才能在地图中快速移动到自己的位置。
百度LBS SDK的API中提供了一个BaiduMap类,它是地图的总控制器,调用MapView的 getMap()方法就能获取到BaiduMap的实例,如下所示:

BaiduMap baiduMap = mapView.getMap();

有了 BaiduMap后,我们就能对地图进行各种各样的操作了,比如设置地图的缩放级别以及将地图移动到某一个经纬度上。
百度地图将缩放级别的取值范围限定在3到19之间,其中小数点位的值也是可以取的,值越大,地图显示的信息就越精细。比如我们想要将缩放级别设置成12.5,就可以这样写:

MapStatuslIpdate update = MapStatusUpdateFactory.zoomTo(12.5f);baiduMap.animateMapStatus(update);

其中MapStatusUpdateFactory的zoomTo()方法接收一个float型的参数,就是用于设置缩放级别的,这里我们传入12.5f。zoomTo()方法返回一个MapStatusUpdate对象,我们把这个对象传入BaiduMap的animateMapStatus ()方法当中即可完成缩放功能。
那么怎样才能让地图移动到某一个经纬度上呢?这就需要借助LatLng类了。其实LatLng并没有什么太多的用法,主要就是用于存放经纬度值的,它的构造方法接收两个参数,第一个参数是纬度值,第二个参数是经度值。之后调用MapStatusUpdateFactory的newLatLng()方法将 LatLng对象传入,newLatLng()方法返回的也是一个MapStatusUpdate对象,我们再把这个对 象传入BaiduMap的animateMapStatus ()方法当中,就可以将地图移动到指定的经纬度上了, 写法如下:

LatLng ll = new LatLng(39.915, 116.404);
MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(ll); 
baiduMap.animateMapStatus(update);

上述代码就实现了将地图移动到北纬39.915度、东经116.404度这个位置的功能。
了解了这些知识之后,接下来再去实现将地图快速移动到自己位置的功能就变得非常简单了。首先我们可以利用在之前所学的定位技术来获得自己当前位置的经纬度,之后再按照上述的方法来将地图移动到指定的位置就可以了。
那么下面我们就来继续完善LBSTest这个项目,加入“移动到我的位置”这个功能。修改 MainActivity中的代码,如下所示:

package com.example.lbstest;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
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.SDKInitializer;
import com.baidu.mapapi.map.BaiduMap;
import com.baidu.mapapi.map.MapStatusUpdate;
import com.baidu.mapapi.map.MapStatusUpdateFactory;
import com.baidu.mapapi.map.MapView;
import com.baidu.mapapi.model.LatLng;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    public LocationClient mLocationClient;

    private TextView positionText;

    private MapView mapView;

    private BaiduMap baiduMap;

    private boolean isFirstLocate = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        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();
        positionText = (TextView)findViewById(R.id.position_text_view);
        List<String> permissionList = new ArrayList<>();
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.READ_PHONE_STATE);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if (!permissionList.isEmpty()){
            String [] permissions = permissionList.toArray(new String[permissionList.size()]);
            ActivityCompat.requestPermissions(MainActivity.this,permissions,1);
        }else {
            requestLocation();
        }
    }
    private void requestLocation(){
        initLocation();
        mLocationClient.start();
    }

    private void initLocation(){
        LocationClientOption option = new LocationClientOption();
        option.setScanSpan(5000);
        option.setIsNeedAddress(true);
        mLocationClient.setLocOption(option);
    }

    @Override
    protected void onResume(){
        super.onResume();
        mapView.onResume();
    }

    @Override
    protected void onPause(){
        super.onPause();
        mapView.onPause();
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        mLocationClient.stop();
        mapView.onDestroy();
    }

    @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,"必须同意所有权限才能使用本程序",Toast.LENGTH_SHORT).show();
                            finish();
                            return;
                        }
                    }
                    requestLocation();
                }else {
                    Toast.makeText(this,"发生未知错误",Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
        }
    }

    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;
        }
    }

    public class MyLocationListener implements BDLocationListener{

        @Override
        public void onReceiveLocation(final BDLocation location){
            if (location.getLocType() == BDLocation.TypeGpsLocation || location.getLocType() == BDLocation.TypeNetWorkLocation){
                navigateto(location);
            }
        }
    }
}

这里并没有新增多少代码,主要是加入了一个navigateTo()方法。这个方法中的代码也很好理解,先是将BDLocation对象中的地理位置信息取出并封装到LatLng对象中,然后调用 MapStatusUpdateFactory 的 newLatLng()方法并将 LatLng对象传入,接着将返回的 MapStatusUpdate 对象作为参数传入到BaiduMap的animateMapStatus ()方法当中,和上面介绍的用法是一模一样的。并且这里为了让地图信息可以显示得更加丰富一些,我们将缩放级别设置成了 16。另外还有一点需要注意,上述代码当中我们使用了一个isFirstLocate变量,这个变量的作用是为了防止多次调用animateMapStatus()方法,因为将地图移动到我们当前的位置只需要在程序第一次定位的时候调用一次就可以了。
写好了 navigateTo()方法之后,剩下的事情就简单了,当定位到设备当前位置的时候,我们在onReceiveLocation()方法中直接把BDLocation对象传给navigateTo()方法,这样就能 够让地图移动到设备所在的位置了。
现在重新运行一下程序,结果如图所示:
在这里插入图片描述

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中的代码,如下所示:

package com.example.lbstest;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
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.SDKInitializer;
import com.baidu.mapapi.map.BaiduMap;
import com.baidu.mapapi.map.MapStatusUpdate;
import com.baidu.mapapi.map.MapStatusUpdateFactory;
import com.baidu.mapapi.map.MapView;
import com.baidu.mapapi.map.MyLocationData;
import com.baidu.mapapi.model.LatLng;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    public LocationClient mLocationClient;

    private TextView positionText;

    private MapView mapView;

    private BaiduMap baiduMap;

    private boolean isFirstLocate = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        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);
        positionText = (TextView)findViewById(R.id.position_text_view);
        List<String> permissionList = new ArrayList<>();
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.READ_PHONE_STATE);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if (!permissionList.isEmpty()){
            String [] permissions = permissionList.toArray(new String[permissionList.size()]);
            ActivityCompat.requestPermissions(MainActivity.this,permissions,1);
        }else {
            requestLocation();
        }
    }
    private void requestLocation(){
        initLocation();
        mLocationClient.start();
    }

    private void initLocation(){
        LocationClientOption option = new LocationClientOption();
        option.setScanSpan(5000);
        option.setIsNeedAddress(true);
        mLocationClient.setLocOption(option);
    }

    @Override
    protected void onResume(){
        super.onResume();
        mapView.onResume();
    }

    @Override
    protected void onPause(){
        super.onPause();
        mapView.onPause();
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        mLocationClient.stop();
        mapView.onDestroy();
        baiduMap.setMyLocationEnabled(false);
    }

    @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,"必须同意所有权限才能使用本程序",Toast.LENGTH_SHORT).show();
                            finish();
                            return;
                        }
                    }
                    requestLocation();
                }else {
                    Toast.makeText(this,"发生未知错误",Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
        }
    }

    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);
    }

    public class MyLocationListener implements BDLocationListener{

        @Override
        public void onReceiveLocation(final BDLocation location){
            if (location.getLocType() == BDLocation.TypeGpsLocation || location.getLocType() == BDLocation.TypeNetWorkLocation){
                navigateto(location);
            }
        }
    }
}

可以看到,在navigateTo()方法中,我们添加了MyLocationData的构建逻辑,将Location中包含的经度和纬度分别封装到了 MyLocationData.Builder当中,最后把MyLocationData设置到 了BaiduMap的setMyLocationData()方法当中。注意这段逻辑必须写在isFirstLocate这个if条件语句的外面,因为让地图移动到我们当前的位置只需要在第一次定位的时候执行,但是设备在地图上显示的位置却应该是随着设备的移动而实时改变的。
另外,根据百度地图的限制,如果我们想要使用这一功能,一定要事先调用BaiduMap的setMyLocationEnabled ()方法将此功能开启,否则设备的位置将无法在地图上显示。而在程序退出的时候,也要记得将此功能给关闭掉。
就是这么简单,现在重新运行一下程序,结果如图所示:
在这里插入图片描述
这样的话,用户就可以非常清晰地看出自己当前是在哪里了。
关于百度LBS SDK的用法我就准备介绍这么多,现在你已经算是成功入门了。如果想要更加深入地研究百度LBS的各种用法,可以到官方网站上面参考开发指南,地址是: http://lbsyun.baidu.com。另外,百度LBS SDK的版本未来随时都有可能更新,也许更新之后会导致书上的例子无法正常运行,因此除了照着图书学习之外,根据官网的开发指南来进行学习也是非常重要的,因为官方文档永远都是最新的。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赈川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值