阅读郭林《第一行代码》的笔记——第11章 Android特色开发,基于位置的服务

1、基于位置的服务简介

基于位置的服务(Location Based Service )简称LBS,这个技术随着移动互联网的兴起,在最近的几年里十分火爆。其实它本身并不是什么时髦的技术,主要的工作原理就是利用无线电通讯网络或GPS等定位方式来确定出移动设备所在的位置,而这种定位技术早在很多年前就已经出现了。
那为什么LBS技术直到最近几年才开始流行呢?这主要是因为,在过去移动设备的功能极其有限,即使定位到了设备所在的位置,也就仅仅只是定位到了而已,我们并不能在位置的基础上进行一些其他的操作。而现在就大大不同了,有了Android系统作为载体,我们可以利用定位出的位置进行许多丰富多彩的操作。

2、找到自己的位置

  • LocationManager的基本用法
  • 确定自己位置的经纬度

先看一个例子:

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/txt_location"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

启动类:

package com.test.location;

import android.app.Activity;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;

import com.test.R;

import java.util.List;

/**
* Created by Administrator on 2016/5/15 0015.
*/
public class LocationActivity extends Activity {
    private TextView txtLocation;
    private LocationManager locationManager;
    private String provider;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_location);
        txtLocation = (TextView) findViewById(R.id.txt_location);
        //调用Context的getSystemService()方法获取到LocationManager的实例。
        // getSystemService()方法接收一个字符串参数用于确定获取系统的哪个服务,这里传入Context.LOCATION_SERVICE即可。
        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        //获取所有可用的位置提供器来确定设备当前的位置
        //如果有些时候你想让定位的精度尽量高一些,但又不确定GPS定位的功能是否已经启用,
        //这个时候就可以先判断一下有哪些位置提供器可用
        //getProviders()方法接收一个布尔型参数,传入true就表示只有启用的位置提供器才会被返回。
        //之后再从providerList中判断是否包含GPS定位的功能就行了。
        List<String> providerList = locationManager.getProviders(true);
        if (providerList.contains(LocationManager.GPS_PROVIDER)) {
            provider = LocationManager.GPS_PROVIDER;
        } else if (providerList.contains(LocationManager.NETWORK_PROVIDER)) {
            provider = LocationManager.NETWORK_PROVIDER;
        } else {
            //当没有可用的位置提供器时,弹出Toast提示用户
            Toast.makeText(this, "No location provider to use.", Toast.LENGTH_LONG);
            return;
        }
        //将选择好的位置提供器传入到getLastKnownLocation()方法中,就可以得到一个Location对象
        //这个Location对象中包含了经度、纬度、海拔等一系列的位置信息,然后从中取出我们所关心的那部分数据即可。
        Location location = locationManager.getLastKnownLocation(provider);
        if (location != null) {
            showLocation(location);
        }
        //在设备位置发生改变的时候获取到最新的位置信息用这个方法requestLocationUpdates(),该方法接收四个参数,
        // 第一个参数是位置提供器的类型,
        // 第二个参数是监听位置变化的时间间隔,以毫秒为单位,
        // 第三个参数是监听位置变化的距离间隔,以米为单位,
        // 第四个参数则是LocationListener监听器。
        locationManager.requestLocationUpdates(provider, 5000, 1, locationListener);
    }

    LocationListener locationListener = new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            //更新当前设备位置
            showLocation(location);
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {

        }

        @Override
        public void onProviderEnabled(String provider) {

        }

        @Override
        public void onProviderDisabled(String provider) {

        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (locationManager != null) {
            //关闭程序时将监听器移除
            locationManager.removeUpdates(locationListener);
        }
    }

    private void showLocation(Location location) {
        String currentPosition = "latitude is " + location.getLatitude() + "\n" + "longitude is " + location.getLongitude();
        txtLocation.setText(currentPosition);
    }

}

声明权限:

<!--获得当前设备位置的权限-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Android中一般有三种位置提供器可供选择,GPS_PROVIDER、NETWORK_PROVIDER和PASSIVE_PROVIDER。其中前两种使用的比较多,分别表示使用GPS定位和使用网络定位。这两种定位方式各有特点,GPS定位的精准度比较高,但是非常耗电,而网络定位的精准度稍差,但耗电量比较少。我们应该根据自己的实际情况来选择使用哪一种位置提供器,当位置精度要求非常高的时候,最好使用GPS_PROVIDER,而一般情况下,使用NETWORK_PROVIDER会更加得划算。
需要注意的是,定位功能必须要由用户主动去启用才行,不然任何应用程序都无法获取到手机当前的位置信息。进入手机的设置→定位服务,其中第一个选项表示允许使用网络的方式来对手机进行定位,第二个选项表示允许使用GPS的方式来对手机进行定位。

3、反向地理编码,看得懂的位置信息

  • Geocoding API的用法

其实Android本身就提供了地理编码的API,主要是使用GeoCoder这个类来实现的。它可以非常简单地完成正向和反向的地理编码功能,从而轻松地将一个经纬值转换成看得懂的位置信息。不过,非常遗憾的是,GeoCoder长期存在着一些较为严重的bug,在反向地理编码的时候会有一定的概率不能解析出位置的信息,这样就无法保证位置解析的稳定性,因此我们不得不去寻找GeoCoder的替代方案。还算比较幸运,谷歌又提供了一套Geocoding API,使用它的话也可以完成反向地理编码的工作,只不过它的用法稍微复杂了一些,但稳定性要比GeoCoder强得多。
Geocoding API的工作原理并不神秘,其实就是利用了我们上一章中学习的HTTP协议。在手机端我们可以向谷歌的服务器发起一条HTTP请求,并将经纬度的值作为参数一同传递过去,然后服务器会帮我们将这个经纬值转换成看得懂的位置信息,再将这些信息返回给手机端,最后手机端去解析服务器返回的信息,并进行处理就可以了。
Geocoding API中规定了很多接口,其中反向地理编码的接口如下:
http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&sensor=true_or_false
我们来仔细看下这个接口的定义,其中http://maps.googleapis.com/maps/api/geocode/是固定的,表示接口的连接地址。json表示希望服务器能够返回JSON格式的数据,这里也可以指定成xml。latlng=40.714224,-73.96145表示传递给服务器去解码的经纬值是北纬40.714224度,西经73.96145度。sensor=true_or_false表示这条请求是否来自于某个设备的位置传感器,通常指定成false即可。
如果发送http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.96145&sensor=false这样一条请求给服务器,我们将会得到一段非常长的JSON格式的数据,其中会包括如下部分内容:”formatted_address” : “277 Bedford Avenue, 布鲁克林纽约州 11211美国”
从这段内容中我们就可以看出北纬40.714224度,西经73.96145度对应的地理位置是在哪里了。如果你想查看服务器返回的完整数据,在浏览器中访问上面的网址即可。

  • 对经纬度进行解析

4、使用百度地图

  • 申请API Key

先申请成为百度开发者,然后创建应用,
安全码是我们申请API Key所必须填写的一个字段,它的组成方式是数字签名+;+包名。
这里数字签名指的是我们打包程序时所用keystore的SHA1指纹。
这边要用输入SHA1,在eclipse中可以通过点击Eclipse导航栏的Window→Preferences→Android→Build获得,而android studio的童鞋可以通过下面方法获得SHA1
方法:
第1步:运行进入控制台
第2步:定位到.android文件夹下,输入cd .android
第3步:输入keytool -list -v -keystore debug.keystore,会得到三种指纹证书,选取SHA1类型的证书(密钥口令是android),
其中keytool为jdk自带工具;keystorefile为Android 签名证书文件

  • 让地图显示出来
  • 定位到我的位置
  • 使用覆盖物来增加更多功能

下面是一个例子:
布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context="com.test.MainActivity">

    <com.baidu.mapapi.map.MapView
        android:id="@+id/my_map_view"
        android:layout_width="match_parent"
        android:clickable="true"
        android:layout_height="match_parent" />

</RelativeLayout> 

启动类:

扫描二维码关注公众号,回复: 1347869 查看本文章
package com.test;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.location.LocationManager;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.widget.Toast;

import com.baidu.mapapi.BMapManager;
import com.baidu.mapapi.map.LocationData;
import com.baidu.mapapi.map.MapController;
import com.baidu.mapapi.map.MapView;
import com.baidu.mapapi.map.MyLocationOverlay;
import com.baidu.mapapi.map.PopupClickListener;
import com.baidu.mapapi.map.PopupOverlay;
import com.baidu.platform.comapi.basestruct.GeoPoint;

import java.util.List;

public class MainActivity extends ActionBarActivity {
    private BMapManager manager;
    private MapView mapView;

    private LocationManager locationManager;
    private String provider;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //创建一个BMapManager对象
        manager = new BMapManager(this);
        //初始化,init()方法接收两个参数,
        // 第一个参数就是在上一小节中我们申请到的API Key,
        // 第二个参数传入null即可。
        //注意初始化操作一定要在setContentView()方法前调用,不然的话就会出错。
        manager.init("MKTYkwNGFlmnzhn9jL7DGDp5KFYHGZYC", null);
        setContentView(R.layout.activity_main);
        mapView = (MapView) this.findViewById(R.id.my_map_view);
        //启用内置的缩放控制功能
        mapView.setBuiltInZoomControls(true);
        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        //获取所有可用的位置提供器
        List<String> providerList = locationManager.getProviders(true);
        if (providerList.contains(LocationManager.GPS_PROVIDER)) {
            provider = LocationManager.GPS_PROVIDER;
        } else if (providerList.contains(LocationManager.NETWORK_PROVIDER)) {
            provider = LocationManager.NETWORK_PROVIDER;
        } else {
            //当没有位置提供器时,弹出toast提示用户
            Toast.makeText(MainActivity.this, "No location provider to use.", Toast.LENGTH_SHORT).show();
            return;
        }
        Location location = locationManager.getLastKnownLocation(provider);
        if (location != null) {
            navigateTo(location);
        }
    }

    private void navigateTo(Location location) {
        //获取MapController对象(地图的总控制器),MapController类主要是对地图的放大缩小,
        //俯视旋转,手势键盘处理,指南针位置,动画等的管理
        MapController cotroller = mapView.getController();
        //设置缩放级别,取值范围是3到19,级别越高,地图显示的信息就越精细。
        cotroller.setZoom(16);
//GeoPoint主要就是用于存放经纬度值的,它的构造方法接收两个参数,第一个参数是纬度值,第二参数是经度值。
//但是需要注意,GeoPoint是以微度为单位的,因此我们还要把经纬度的值乘以10的6次方再传给GeoPoint。
        //通过纬度、经度创建一个地理坐标点
        GeoPoint point = new GeoPoint((int) (location.getLatitude() * 1E6), (int) (location.getLongitude() * 1E6));
        //设置地图中心
        cotroller.setCenter(point);
        //MyLocationOverlay类用于在地图上标注自己所处的位置
        MyLocationOverlay myLocationOverlay = new MyLocationOverlay(mapView);
        LocationData locationData = new LocationData();
        //定位我的位置
        locationData.latitude = location.getLatitude();
        locationData.longitude = location.getLongitude();
        myLocationOverlay.setData(locationData);
        mapView.getOverlays().add(myLocationOverlay);
        //刷新使新生覆盖物生效
        mapView.refresh();
        PopupOverlay popup = new PopupOverlay(mapView, new PopupClickListener() {
            @Override
            public void onClickedPopup(int i) {
                //相应图片的点击事件
                Toast.makeText(MainActivity.this, "You clicked button " + i, Toast.LENGTH_SHORT).show();
            }
        });
        //创建一个长度为3的Bitmap数组
        Bitmap[] bitmaps = new Bitmap[3];
        //将三张图片读取到内存中
        bitmaps[0] = BitmapFactory.decodeResource(getResources(), R.drawable.left);
        bitmaps[1] = BitmapFactory.decodeResource(getResources(), R.drawable.middle);
        bitmaps[2] = BitmapFactory.decodeResource(getResources(), R.drawable.right);
        popup.showPopup(bitmaps, point, 18);
    }

    //重写onResume()、onPause()和onDestroy()这三个方法对百度地图的API进行管理,以保证资源能够及时地得到释放。

    @Override
    protected void onResume() {
        mapView.onResume();
        if (manager != null) {
            manager.start();
        }
        super.onResume();
    }

    @Override
    protected void onPause() {
        mapView.onPause();
        if (manager != null) {
            manager.stop();
        }
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        mapView.destroy();
        if (manager != null) {
            manager.destroy();
        }
        super.onDestroy();
    }
}

声明权限:

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_GPS" />

除了普通的地图展示之外,百度地图还提供了一种叫作覆盖物的功能,所有叠加或覆盖到地图上的内容都被统称为地图覆盖物,如标注、矢量图形元素、定位图标等。覆盖物拥有自己的地理坐标,当我们拖动或缩放地图时,它们会自动进行相应地移动。百度地图提供了很多种类型的覆盖物,开发人员可以根据自己的实际需求来选择使用哪些覆盖物,在百度地图所有的覆盖物中,最常用的就是 了,它主要的作用是可以在地图中添加一个图层,以标注出设备当前的位置。
MyLocationOverlay:

MyLocationOverlay myLocationOverlay = new MyLocationOverlay(mapView);
        LocationData locationData = new LocationData();
         // 指定我的位置
        locationData.latitude = location.getLatitude();
        locationData.longitude = location.getLongitude();
        myLocationOverlay.setData(locationData);
        mapView.getOverlays().add(myLocationOverlay);
        mapView.refresh(); // 刷新使新增覆盖物生效

下面我们再学习一下PopupOverlay这种覆盖物的用法吧。相比于 MyLocationOverlay,PopupOverlay 允许我们自己指定覆盖物上显示的图片,并且还可以响应图片的点击事件,每个 PopupOverlay上最多可以显示三张图片。
PopupOverlay:

PopupOverlay pop = new PopupOverlay(mapView, new PopupClickListener() {
            @Override
            public void onClickedPopup(int index) {
                // 相应图片的点击事件
                Toast.makeText(MainActivity.this,
                        "You clicked button " + index, Toast.LENGTH_SHORT). show();
            }
        });

        // 创建一个长度为3的 Bitmap 数组
        Bitmap[] bitmaps = new Bitmap[3];
        try {
            // 将三张图片读取到内存中
            bitmaps[0] = BitmapFactory.decodeResource(getResources(), R.drawable.left);
            bitmaps[1] = BitmapFactory.decodeResource(getResources(), R.drawable.middle);
            bitmaps[2] = BitmapFactory.decodeResource(getResources(), R.drawable.right);
        } catch (Exception e) {
            e.printStackTrace();
        }
        pop.showPopup(bitmaps, point, 18);
    }

猜你喜欢

转载自blog.csdn.net/u010102829/article/details/52748180