在本章中,我们将涵盖以下主题:
位置感知为应用提供了许多好处,事实上如此之多,以至于现在即使是桌面应用也试图获得用户的位置。位置使用的范围从逐个转弯的方向,“查找最近的”应用,基于位置的警报,现在甚至有基于位置的游戏,让你用你的设备出去探索。
谷歌应用编程接口为创建位置感知应用和地图功能提供了许多丰富的功能。我们的第一个食谱如何获得最后一个位置将着眼于获得存储在设备上的最后一个已知位置。如果您的应用不是位置密集型的,这可能会提供一种理想的方法来获取用户的位置,而不会产生大量的资源开销。如果您需要持续更新,请转到如何接收位置更新食谱。虽然不断的位置更新需要更多的资源,但是用户很可能理解你何时给他们一个又一个的方向。如果您正在请求邻近位置的位置更新,请查看使用地理围栏选项,在创建和监控地理围栏配方中。
本章中的所有食谱都使用谷歌图书馆。如果您还没有下载软件开发工具包,请按照谷歌的说明进行操作。
从添加 SDK 包。
既然你已经知道了位置,很有可能你也想绘制地图。这是谷歌使用谷歌地图应用编程接口在安卓上非常容易做到的另一个领域。要开始使用谷歌地图,在安卓工作室创建新项目时,看一下谷歌地图活动选项。不像我们通常对这些食谱做的那样选择空白活动,而是选择谷歌地图活动,如下图所示:
最后一个位置怎么走我们将从一个通常需要的简单方法开始这一章:如何获得最后一个已知的位置。这是一种使用 API 的简单方法,几乎没有额外的资源消耗。(这意味着,你的应用不会负责杀死电池。)
这个食谱也提供了一个很好的设置谷歌定位应用编程接口的介绍。
在 Android Studio 中创建新的项目,并将其称为:GetLastLocation
。使用默认的电话&平板选项,当提示输入活动类型时,选择空活动。
首先,我们将向安卓清单添加必要的权限,然后我们将创建一个带有Button
和TextView
元素的布局。最后,我们将创建一个GoogleAPIClient
应用编程接口来访问最后一个位置。打开安卓清单,按照以下步骤操作:
添加以下权限:
java
Under the Gradle Scripts section, open the build.gradle (Module: app) file, as shown in this screenshot:
在dependencies
部分增加以下语句:
java
compile 'com.google.android.gms:play-services:8.4.0'
打开 activity_main.xml
,用以下 XML 替换现有的【T1:
java
android:layout_
android:layout_ />
打开MainActivity.java
并添加以下全局变量:
java
GoogleApiClient mGoogleApiClient;
TextView mTextView;
Button mButton;
为ConnectionCallbacks
添加类别:
java
GoogleApiClient.ConnectionCallbacks mCOnnectionCallbacks= new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle bundle) {
mButton.setEnabled(true);
}
@Override
public void onConnectionSuspended(int i) {}
};
添加类来处理OnConnectionFailedListener
回调:
java
GoogleApiClient.OnConnectionFailedListener mOnConnectionFailedListener= new GoogleApiClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Toast.makeText(MainActivity.this, connectionResult.toString(), Toast.LENGTH_LONG).show();
}
};
将以下代码添加到现有的onCreate()
方法中:
java
mTextView = (TextView) findViewById(R.id.textView);
mButton = (Button) findViewById(R.id.button);
mButton.setEnabled(false);
setupGoogleApiClient();
添加设置GoogleAPIClient
的方法:
java
protected synchronized void setupGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(mConnectionCallbacks)
.addOnConnectionFailedListener(mOnConnectionFailedListener)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
}
为按钮点击添加以下方法:
java
public void getLocation(View view) {
try {
Location lastLocation = LocationServices.FusedLocationApi.getLastLocation(
mGoogleApiClient);
if (lastLocation != null) {
mTextView.setText(
DateFormat.getTimeInstance().format(lastLocation.getTime()) + "\n" + "Latitude="+lastLocation.getLatitude() + "\n" + "LOngitude=" + lastLocation.getLongitude());
} else {
Toast.makeText(MainActivity.this, "null", Toast.LENGTH_LONG).show();
}
}
catch (SecurityException e) {e.printStackTrace();}
}
您已经准备好在设备或模拟器上运行应用。
在调用getLastLocation()
方法之前,我们需要设置GoogleApiClient
。我们在我们的setupGoogleApiClient()
方法中调用GoogleApiClient.Builder
方法,然后连接到库。当图书馆准备好了,它就调用我们的ConnectionCallbacks.onConnected()
方法。出于演示目的,这是我们启用按钮的地方。(我们将在后面的菜谱中使用这个回调来启动其他功能。)
我们使用了按钮来显示我们可以按需调用getLastLocation()
;这不是一次性的电话。系统负责更新位置,并可能在重复呼叫时返回相同的最后位置。(这可以在时间戳中看到——它是位置时间戳,而不是按下按钮时的时间戳。)
这种按需调用位置的方法在应用中发生某些事情(例如对对象进行地理编码)时只需要位置的情况下非常有用。由于系统负责位置更新,因此您的应用不会因位置更新而耗尽电池。
我们收到的位置对象的准确性基于我们的权限设置。我们使用了ACCESS_COARSE_LOCATION
,但是如果我们想要更高的精度,我们可以改为请求ACCESS_FINE_LOCATION
,并获得以下权限:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
最后,为了保持代码集中在GoogleApiClient
上,我们只需用SecurityException
包装getLastLocation()
。在生产应用中,您应该检查并请求权限,如前一章所示。(参见新的运行时权限模型。)
如果连接到GoogleApiClient
时出现问题,则调用OnConnectionFailedListener
。在这个例子中,我们显示了一个吐司。下一个方法是解决谷歌客户端 OnConnectionFailedListener 报告的问题,这将展示一种更稳健的方法来处理这种情况。
测试位置可能是一个挑战,因为在测试和调试时很难真正移动设备。幸运的是,我们有能力用模拟器模拟全球定位系统数据。(也可以在物理设备上创建模拟位置,但并不容易。)
有三种方法可以用模拟器模拟位置:
Geo
命令要在安卓工作室中设置模拟位置,请按照以下步骤操作:
以下是显示位置 控件的截图:
并不是说通过发送全球定位系统数据来模拟位置。因此,要让你的应用接收模拟位置,它需要接收全球定位系统数据。测试lastLocation()
可能不会发送模拟全球定位系统数据,因为它不仅仅依赖全球定位系统来确定设备位置。使用配方尝试模拟位置如何接收位置更新,在那里我们可以请求优先级。(我们不能强制系统使用任何特定的位置传感器,我们只能提出请求。系统将选择最佳解决方案来交付结果。)
随着谷歌应用接口性质的不断变化,你的用户可能会试图使用你的应用,但由于他们的文件已经过期,他们无法使用。在前面的例子中,我们只是展示了一个吐司,但是我们可以做得更好。我们可以使用GoogleApiAvailability
库显示一个对话框,帮助用户解决问题。
我们将继续前面的配方,并向onConnectionFailed()
回调添加代码。我们将使用错误结果向用户显示附加信息来解决他们的问题。
本食谱将从之前的食谱继续,如何获得最后的位置。如果是从下载的源文件加载项目,则称为HandleGoogleAPIError
。
由于我们是从前面的配方继续,我们将只介绍更新前面代码所需的步骤。打开ActivityMain.java
并按照以下步骤操作:
向全局类变量添加以下行:
java
private final int REQUEST_RESOLVE_GOOGLE_CLIENT_ERROR=1;
boolean mResolvingError;
添加以下方法显示谷歌应用编程接口错误对话框:
java
private void showGoogleAPIErrorDialog(int errorCode) {
GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
Dialog errorDialog = googleApiAvailability.getErrorDialog(this, errorCode, REQUEST_RESOLVE_GOOGLE_CLIENT_ERROR);
errorDialog.show();
}
添加以下代码以覆盖onActivityResult()
:
java
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_RESOLVE_GOOGLE_CLIENT_ERROR) {
mResolvingError = false;
if (resultCode == RESULT_OK && !mGoogleApiClient.isConnecting() && !mGoogleApiClient.isConnected()) {
mGoogleApiClient.connect();
}
}
}
在onConnectionFailed()
中,替换调用 Toast 的现有代码行,使用以下代码:
java
if (mResolvingError) {
return;
} else if (connectionResult.hasResolution()) {
mResolvingError = true;
try {
connectionResult.startResolutionForResult(MainActivity.this, REQUEST_RESOLVE_GOOGLE_CLIENT_ERROR);
} catch (IntentSender.SendIntentException e) {
mGoogleApiClient.connect();
}
} else {
showGoogleAPIErrorDialog(connectionResult.getErrorCode());
}
您已经准备好在设备或模拟器上运行应用。
我们现在检查connectionResult
看看我们能做什么,而不是像以前那样用 Toast 显示错误信息。GoogleAPIClient
用connectionResult
表示可能的行动路线。我们可以称之为hasResolution()
方法,如下:
connectionResult.hasResolution()
如果响应是 true
,那么就是用户可以解决的事情,比如启用定位服务。如果响应是false
,我们得到一个GoogleApiAvailability
的实例,并调用getErrorDialog()
方法。完成后,我们的onActivityResult()
回调被调用,在那里我们重置mResolvingError
,如果成功,尝试重新连接。
如果您没有使用旧版本谷歌应用编程接口进行测试的设备,您可以尝试在使用旧版本谷歌应用编程接口的仿真器上进行测试。
如果您的应用正在使用片段,您可以使用下面的代码获得一个对话框片段:
ErrorDialogFragment errorFragment = new ErrorDialogFragment();
Bundle args = new Bundle();
args.putInt("dialog_error", errorCode);
errorFragment.setArguments(args);
errorFragment.show(getSupportFragmentManager(), "errordialog");
如果您的应用需要频繁的位置更新,您的应用可以请求定期更新。该配方将使用GoogleApiClient
中的requestLocationUpdates()
方法进行演示。
在 Android Studio 中创建新项目,并将其称为:LocationUpdates
。使用默认的电话&平板电脑选项,当提示输入活动类型时,选择空活动。
由于我们正在从系统接收更新,我们不需要这个食谱的按钮。我们的布局将只包括TextView
来查看位置数据。打开安卓清单,按照以下步骤操作:
添加以下权限:
java
打开文件build.gradle (Module: app)
并在dependencies
部分添加以下语句:
java
compile 'com.google.android.gms:play-services:8.4.0'
打开activity_main.xml
并用以下 XML 替换现有的【T1:
java
android:layout_
android:layout_ />
打开MainActivity.java
并添加以下全局变量:
java
GoogleApiClient mGoogleApiClient;
LocationRequest mLocationRequest;
TextView mTextView;
创建以下LocationListener
类:
java
LocationListener mLocatiOnListener= new LocationListener() {
@Override
public void onLocationChanged(Location location) {
if (location != null) {
mTextView.setText(
DateFormat.getTimeInstance().format(location.getTime()) + "\n" + "Latitude="+location.getLatitude()+"\n" + "LOngitude="+location.getLongitude());
}
}
};
创建一个ConnectionCallbacks
类来接收位置更新:
java
GoogleApiClient.ConnectionCallbacks mCOnnectionCallbacks= new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle bundle) {
Log.i("onConnected()", "start");
try {
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, mLocationRequest, mLocationListener);
} catch (SecurityException e) {
Log.i("onConnected()","SecurityException: "+e.getMessage());
}
}
@Override
public void onConnectionSuspended(int i) {}
};
创建一个OnConnectionFailedListener
类:
java
GoogleApiClient.OnConnectionFailedListener mOnConnectionFailedListener= new GoogleApiClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Toast.makeText(MainActivity.this, connectionResult.toString(), Toast.LENGTH_LONG).show();
Log.i("onConnected()", "SecurityException: " +connectionResult.toString());
}
};
将以下代码添加到现有的onCreate()
回调中:
java
mTextView = (TextView) findViewById(R.id.textView);
setupLocationRequest();
创建setupLocationRequest()
方法:
java
protected synchronized void setupLocationRequest() {
mLocatiOnRequest= new LocationRequest();
mLocationRequest.setInterval(10000);
mLocationRequest.setFastestInterval(10000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(mConnectionCallbacks)
.addOnConnectionFailedListener(mOnConnectionFailedListener)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
}
您已经准备好在设备或模拟器上运行应用。
这个配方是类似于如何获取最后位置的配方,因为我们需要像之前一样设置GoogleApiClient
。但是,我们不是按需调用lastLocation()
方法,而是通过LocationListener
类调用requestLocationUpdates()
方法来接收定期位置更新。
requestLocationUpdates()
方法需要三个参数:
GoogleApiClient
LocationRequest
LocationListener
我们像以前一样创建GoogleApiClient
。这是创建我们的LocationRequest
的代码:
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(10000);
mLocationRequest.setFastestInterval(10000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
在调用setInterval()
时,通常最好使用最慢的延迟,因为它需要更少的设备资源。调用setPriority()
时也是同样的想法。第三个参数LocationListener
,是我们定义回调方法onLocationChanged()
的地方。这里我们只显示位置数据和位置时间戳。
与以前的安卓应用编程接口不同,GoogleApiClient
应用编程接口不允许选择特定的传感器进行位置更新。如的模拟地点部分所述,如何获得最后一个地点,使用LocationRequest.PRIORITY_HIGH_ACCURACY
以及ACCESS_FINE_LOCATION
的许可应该使用全球定位系统传感器。参考模拟位置部分,了解模拟位置的说明。
当您的应用不再需要位置更新时,调用removeLocationUpdates()
方法,如下所示:
LocationServices.FusedLocationApi.removeLocationUpdates(
mGoogleApiClient, mLocationListener);
通常,当应用不再处于前台时,您会希望禁用更新,但这取决于您的特定应用要求。如果您的应用需要不断更新,创建一个后台服务来处理回调可能会更好。
如果您的应用需要知道用户何时进入某个位置,那么除了必须持续检查用户位置之外,还有一种选择:地理围栏。地理围栏是一个位置(纬度和经度)以及一个半径。您可以创建地理围栏,并让系统在用户进入您指定的位置附近时通知您。(安卓目前允许每个用户最多 100 个地理围栏。)
地理围栏属性包括:
GEOFENCE_TRANSITION_ENTER
GEOFENCE_TRANSITION_EXIT
INITIAL_TRIGGER_DWELL
本食谱将向您展示如何创建地理围栏对象,并使用它创建GeofencingRequest
的实例。
在 Android Studio 中创建新项目,并将其称为:Geofence
。使用默认的电话&平板电脑选项,当提示输入活动类型时,选择空活动。
我们不需要这个食谱的布局,因为我们将使用祝酒词和通知进行用户交互。我们需要为IntentService
创建一个额外的 Java 类,它处理地理围栏警报。打开安卓清单,按照以下步骤操作:
添加以下权限:
java
打开文件build.gradle (Module: app)
并在dependencies
部分添加以下语句:
java
compile 'com.google.android.gms:play-services:8.4.0'
创建一个名为GeofenceIntentService
的新 Java 类,扩展IntentService
类。申报如下:
java
public class GeofenceIntentService extends IntentService {
添加以下构造函数:
java
public GeofenceIntentService() {
super("GeofenceIntentService");
}
添加onHandleIntent()
接收地理围栏警报:
java
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
Toast.makeText(getApplicationContext(), "Geofence error code= " + geofencingEvent.getErrorCode(), Toast.LENGTH_SHORT).show();
return;
}
int geofenceTransition = geofencingEvent.getGeofenceTransition();
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL) {
sendNotification();
}
}
添加sendNotification()
方法向用户显示消息:
java
private void sendNotification() {
Log.i("GeofenceIntentService", "sendNotification()");
Uri notificatiOnSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificatiOnBuilder= new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Geofence Alert")
.setContentText("GEOFENCE_TRANSITION_DWELL")
.setSound(notificationSoundUri)
.setLights(Color.BLUE, 500, 500);
NotificationManager notificatiOnManager= (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, notificationBuilder.build());
}
打开安卓清单,在
元素中添加以下内容,与
元素处于同一级别:
java
打开MainActivity.java
并添加以下全局变量:
java
private final int MINIMUM_RECOMENDED_RADIUS=100;
GoogleApiClient mGoogleApiClient;
PendingIntent mGeofencePendingIntent;
创建以下的类:
java
ResultCallback mResultCallback = new ResultCallback() {
@Override
public void onResult(Result result) {
Log.i("onResult()", "result: " + result.getStatus().toString());
}
};
创建ConnectionCallbacks
类:
java
GoogleApiClient.ConnectionCallbacks mCOnnectionCallbacks= new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle bundle) {
try {
LocationServices.GeofencingApi.addGeofences(
mGoogleApiClient,
createGeofencingRequest(),
getGeofencePendingIntent()
).setResultCallback(mResultCallback);
} catch (SecurityException e) {
Log.i("onConnected()", "SecurityException: " + e.getMessage());
}
}
@Override
public void onConnectionSuspended(int i) {}
};
创建一个OnConnectionFailedListener
类:
java
GoogleApiClient.OnConnectionFailedListener mOnConnectionFailedListener= new GoogleApiClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.i("onConnectionFailed()", "connectionResult: " +connectionResult.toString());
}
};
将以下代码添加到现有的onCreate()
回调中:
java
setupGoogleApiClient();
添加设置GoogleAPIClient
:
java
protected synchronized void setupGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(mConnectionCallbacks)
.addOnConnectionFailedListener(mOnConnectionFailedListener)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
}
的方法
14. 创建setupGoogleApiClient()
方法:
java
protected synchronized void setupGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(mConnectionCallbacks)
.addOnConnectionFailedListener(mOnConnectionFailedListener)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
}
使用以下方法创建待定意图:
java
private PendingIntent getGeofencePendingIntent() {
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(this, GeofenceIntentService.class);
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
创建geofence
对象,并将其添加到请求列表中:
java
private List createGeofenceList() {
List
geofenceList.add(new Geofence.Builder()
.setRequestId("GeofenceLocation")
.setCircularRegion(
37.422006, //Latitude
-122.084095, //Longitude
MINIMUM_RECOMENDED_RADIUS)
.setLoiteringDelay(30000)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_DWELL)
.build());
return geofenceList;
}
创建 createGeofencingRequest()
方法如下:
java
private GeofencingRequest createGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_DWELL);
builder.addGeofences(createGeofenceList());
return builder.build();
}
您已经准备好在设备或模拟器上运行应用。
首先,我们添加ACCESS_FINE_LOCATION
权限,因为这是地理围栏所必需的。我们设置GoogleApiClient
就像我们在之前的食谱中所做的一样,等到onConnected()
被调用来设置GeofencingApi
。
在调用GeofencingApi.addGeofences()
方法之前,我们必须准备三个对象:
GoogleApiClient
我们已经创造了GoogleApiClient
,我们保存在mGoogleApiClient
中。
要创建地理围栏请求,我们使用GeofencingRequest.Builder
。构建器需要地理围栏对象的列表,这些对象是用createGeofenceList()
方法创建的。(即使我们只创建了一个地理围栏对象,构建器也需要一个列表,所以我们只需将我们的单个地理围栏添加到一个ArrayList
中。)这里是我们设置地理围栏属性的地方:
.setRequestId("GeofenceLocation")
.setCircularRegion(
37.422006, //Latitude
-122.084095, //Longitude
MINIMUM_RECOMENDED_RADIUS)
.setLoiteringDelay(30000)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_DWELL)
只有游荡延迟是可选的,但是我们需要它,因为我们正在使用DWELL
转换。当调用setTransitionTypes()
时,我们可以使用OR
运算符组合多个转换类型,如管道所示。这里有一个用ENTER
和EXIT
代替的例子:
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT)
在本例中,我们使用了与模拟器相同的默认纬度和经度。根据需要更改这些值。
我们对Geofence.Builder()
的调用创建了地理围栏对象。地理围栏列表准备就绪后,我们调用GeofencingRequest.Builder
并将初始触发器设置为INITIAL_TRIGGER_DWELL
。(如果您更改了前面的转换类型,您可能还想更改初始触发器。)
我们需要的最后一个对象是待定意图,这是当地理围栏标准满足时,系统将如何通知我们的应用。我们创建了GeofenceIntentService
来通过向用户发送通知来处理地理围栏意图。(有关通知的更多信息,请参考第 7 章、警报和通知中的灯光、动作和声音还原使用通知食谱。)
创建了所有三个对象,我们只需调用LocationServices.GeofencingApi.addGeofences()
并等待通知到达。
要停止接收地理围栏通知,您可以使用RequestID
参数或PendingIntent
调用removeGeofences()
方法。以下示例使用了我们用于通知的相同PendingIntent
方法:
LocationServices.GeofencingApi.removeGeofences(
mGoogleApiClient,
getGeofencePendingIntent()
).setResultCallback(mResultCallback);
Geofence.Builder
类。Builder.htmlGeofencingRequest.Builder
类位于:https://developers . Google . com/Android/reference/com/Google/Android/GMS/location/GeofencingRequest。建筑商