掌握Android位置API:从基础到实现位置追踪功能
Android定位API可以用来跟踪你的手机当前位置,并在应用中显示。在本教程中,我们将开发一个可以程序化地获取用户当前位置的应用。
安卓定位API在我们的应用程序中获取用户位置有两种方法:
- android.location.LocationListener : This is a part of the Android API.
- com.google.android.gms.location.LocationListener : This is present in the Google Play Services API. (We’ll look into this in the next tutorial)
安卓定位服务自安卓API 1版本起可用。谷歌官方推荐使用谷歌播放定位服务API。安卓定位服务API仍然用于开发不支持谷歌播放服务的基于位置的应用程序。
位置监听器在Android的位置API中,LocationListener接口用于从LocationManager接收位置变化的通知。LocationManager类提供了访问系统位置服务的功能。LocationListener类需要实现以下方法。
- onLocationChanged(Location location) : Called when the location has changed.
- onProviderDisabled(String provider) : Called when the provider is disabled by the user.
- onProviderEnabled(String provider) : Called when the provider is enabled by the user.
- onStatusChanged(String provider, int status, Bundle extras) : Called when the provider status changes.
android.location有两种获取位置数据的方式。
- LocationManager.GPS_PROVIDER: Determines location using satellites. Depending on the conditions, this provider may take a while to return a location fix
- LocationManager.NETWORK_PROVIDER: Determines location based on the availability of nearby cell towers and WiFi access points. This is faster than GPS_PROVIDER
在本教程中,我们将创建一个服务,该服务实现了LocationListener类,以通过GPS提供程序或网络提供程序接收定期位置更新。
Android 位置API 项目结构该项目包括一个MainActivity.java类,用于显示“获取位置”功能,以及一个LocationTrack.java服务类。
安卓定位API代码
这是文章《使用Android位置API追踪您的当前位置》的第2部分(共2部分)。
下面定义了 activity_main.xml 的布局文件。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.Olivia.gpslocationtracking.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:layout_centerInParent="true"
android:text="GET LOCATION" />
</RelativeLayout>
以下是MainActivity.java类的定义。
package com.Olivia.gpslocationtracking;
import android.annotation.TargetApi;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.util.ArrayList;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
public class MainActivity extends AppCompatActivity {
private ArrayList permissionsToRequest;
private ArrayList permissionsRejected = new ArrayList();
private ArrayList permissions = new ArrayList();
private final static int ALL_PERMISSIONS_RESULT = 101;
LocationTrack locationTrack;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
permissions.add(ACCESS_FINE_LOCATION);
permissions.add(ACCESS_COARSE_LOCATION);
permissionsToRequest = findUnAskedPermissions(permissions);
//获取我们之前请求过但未被授予的权限
//我们将其存储在全局列表中以备后用
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (permissionsToRequest.size() > 0)
requestPermissions(permissionsToRequest.toArray(new String[permissionsToRequest.size()]), ALL_PERMISSIONS_RESULT);
}
Button btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
locationTrack = new LocationTrack(MainActivity.this);
if (locationTrack.canGetLocation()) {
double longitude = locationTrack.getLongitude();
double latitude = locationTrack.getLatitude();
Toast.makeText(getApplicationContext(), "经度:" + Double.toString(longitude) + "\n纬度:" + Double.toString(latitude), Toast.LENGTH_SHORT).show();
} else {
locationTrack.showSettingsAlert();
}
}
});
}
private ArrayList findUnAskedPermissions(ArrayList wanted) {
ArrayList result = new ArrayList();
for (String perm : wanted) {
if (!hasPermission(perm)) {
result.add(perm);
}
}
return result;
}
private boolean hasPermission(String permission) {
if (canMakeSmores()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED);
}
}
return true;
}
private boolean canMakeSmores() {
return (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1);
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case ALL_PERMISSIONS_RESULT:
for (String perms : permissionsToRequest) {
if (!hasPermission(perms)) {
permissionsRejected.add(perms);
}
}
if (permissionsRejected.size() > 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (shouldShowRequestPermissionRationale(permissionsRejected.get(0))) {
showMessageOKCancel("这些权限是应用程序必需的。请允许访问。",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissionsRejected.toArray(new String[permissionsRejected.size()]), ALL_PERMISSIONS_RESULT);
}
}
});
return;
}
}
}
break;
}
}
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(MainActivity.this)
.setMessage(message)
.setPositiveButton("确定", okListener)
.setNegativeButton("取消", null)
.create()
.show();
}
@Override
protected void onDestroy() {
super.onDestroy();
locationTrack.stopListener();
}
}
在上述代码中,我们正在实现适用于Android 6.0+设备的运行时权限。我们在AndroidManifest.xml文件中添加了ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION权限。点击按钮会调用LocationTrack.java服务类。如果在GPS提供程序的情况下返回的位置为空,我们会从LocationTrack.java类中调用showSettingsAlert()方法,稍后我们会看到。当活动被销毁时,会调用stopLocationTrack()方法来关闭位置更新。下面定义了LocationTrack.java类。
public class LocationTrack extends Service implements LocationListener {
private final Context mContext;
boolean checkGPS = false;
boolean checkNetwork = false;
boolean canGetLocation = false;
Location loc;
double latitude;
double longitude;
private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10;
private static final long MIN_TIME_BW_UPDATES = 1000 * 60 * 1;
protected LocationManager locationManager;
public LocationTrack(Context mContext) {
this.mContext = mContext;
getLocation();
}
private Location getLocation() {
try {
locationManager = (LocationManager) mContext
.getSystemService(LOCATION_SERVICE);
// 获取GPS状态
checkGPS = locationManager
.isProviderEnabled(LocationManager.GPS_PROVIDER);
// 获取网络提供程序状态
checkNetwork = locationManager
.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
if (!checkGPS && !checkNetwork) {
Toast.makeText(mContext, "没有可用的服务提供程序", Toast.LENGTH_SHORT).show();
} else {
this.canGetLocation = true;
// 如果GPS已启用,使用GPS服务获取经纬度
if (checkGPS) {
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: 考虑调用
// ActivityCompat#requestPermissions
// 在这里请求缺失的权限,然后重写
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// 来处理用户授予权限的情况。有关详细信息,请参阅
// ActivityCompat#requestPermissions的文档。
}
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
MIN_TIME_BW_UPDATES,
MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
if (locationManager != null) {
loc = locationManager
.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (loc != null) {
latitude = loc.getLatitude();
longitude = loc.getLongitude();
}
}
}
/*if (checkNetwork) {
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: 考虑调用
// ActivityCompat#requestPermissions
// 在这里请求缺失的权限,然后重写
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// 来处理用户授予权限的情况。有关详细信息,请参阅
// ActivityCompat#requestPermissions的文档。
}
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
MIN_TIME_BW_UPDATES,
MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
if (locationManager != null) {
loc = locationManager
.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
if (loc != null) {
latitude = loc.getLatitude();
longitude = loc.getLongitude();
}
}*/
}
} catch (Exception e) {
e.printStackTrace();
}
return loc;
}
public double getLongitude() {
if (loc != null) {
longitude = loc.getLongitude();
}
return longitude;
}
public double getLatitude() {
if (loc != null) {
latitude = loc.getLatitude();
}
return latitude;
}
public boolean canGetLocation() {
return this.canGetLocation;
}
public void showSettingsAlert() {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(mContext);
alertDialog.setTitle("GPS未启用!");
alertDialog.setMessage("您想打开GPS吗?");
alertDialog.setPositiveButton("是", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
mContext.startActivity(intent);
}
});
alertDialog.setNegativeButton("否", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
alertDialog.show();
}
public void stopListener() {
if (locationManager != null) {
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: 考虑调用
// ActivityCompat#requestPermissions
// 在这里请求缺失的权限,然后重写
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// 来处理用户授予权限的情况。有关详细信息,请参阅
// ActivityCompat#requestPermissions的文档。
return;
}
locationManager.removeUpdates(LocationTrack.this);
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onLocationChanged(Location location) {
}
@Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
@Override
public void onProviderEnabled(String s) {
}
@Override
public void onProviderDisabled(String s) {
}
}
从上述代码中可以得出的几个推论为:
- 在上述代码中,isProviderEnabled(String provider)方法被调用在locationManager对象上,用于检查GPS/网络提供程序是否已启用。
- 如果提供程序未启用,我们调用showSettingsAlert()方法,该方法会显示一个提示以启用GPS。
- LocationManager类的requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener)方法用于注册当前活动,以便由指定的提供程序定期通知。
- onLocationChanged会根据minTime和minDistance定期被调用,以先到者为准。
- Location类包含纬度和经度。要获取当前位置,使用以下代码片段:
Location loc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
在上述Location对象上,调用getter方法来存储纬度和经度的double值。这些double值随后作为Toast消息显示在屏幕上。 - 要停止位置更新,在LocationManager实例上调用removeUpdates方法。
以上应用在模拟器上的输出是:我们的模拟器无法获取位置信息,因此返回经纬度的值为0.0。你可以连接你的智能手机并在调试模式下运行应用程序来检查你的当前位置。为了在模拟器中模拟GPS位置,我们可以从Android Studio传递固定的纬度和经度值。除了模拟器窗口,你还可以看到一个选项列表。位于底部的选项(有三个点)是我们的扩展控制选项。打开它并发送一个虚拟位置。你的应用程序应该看起来像这样:教程到此结束。我们将在以后的教程中使用谷歌Play服务来实现位置API。你可以从以下链接下载Android GPSLocationTracking项目。
下载Android位置API项目