本指南将介绍地点兼容性库与新版独立 Places SDK for Android 之间的变化。如果您一直使用的是地点兼容性库,而未迁移到新的独立版 Places SDK for Android,本指南将向您展示如何更新项目以使用新版 Places SDK for Android。
届时,您只能使用 Places SDK for Android 来访问 Places SDK for Android 2.6.0 以上版本的功能和 bug 修复。 Google 建议您尽快从兼容性库更新到新的 Places SDK for Android 版本。
错误:9005 PLACES_API_RATE_LIMIT_EXCEEDED
发生了哪些变化?
主要变更如下:
- 新版 Places SDK for Android 以静态客户端库的形式分发。在 2019 年 1 月之前,Places SDK for Android 是通过 Google Play 服务提供的。此后,我们提供了地点兼容性库,以简化向全新 Places SDK for Android 的过渡。
- 我们还推出了全新方法。
- 现在,对于返回地点详情的方法,支持使用字段掩码。您可以使用字段掩码来指定要返回哪些类型的地点数据。
- 用于报告错误的状态代码已得到改进。
- 自动补全现在支持会话令牌。
- 地点选取器已不再提供。
地点兼容性库简介
2019 年 1 月,随着独立版 Places SDK for Android 1.0 版的发布,Google 提供了一个兼容性库,以帮助从已停用的 Google Play 服务版 Places SDK for Android (com.google.android.gms:play-services-places
) 进行迁移。
此兼容性库暂时用于将针对 Google Play 服务版本的 API 调用重定向并转换为新的独立版本,直到开发者能够迁移其代码以使用独立 SDK 中的新名称。对于从 1.0 版到 2.6.0 版的每个已发布的 Places SDK for Android 版本,我们都发布了相应版本的地点兼容性库,以提供等效的功能。
冻结并弃用地点兼容性库
自 2022 年 3 月 31 日起,适用于 Places SDK for Android 的所有版本的兼容性库都将被弃用。版本 2.6.0 是 Places 兼容性库的最后一个版本。届时,您只能使用 Places SDK for Android 来访问 Places SDK for Android 2.6.0 以上版本的功能和 bug 修复。
Google 建议您迁移到 Places SDK for Android,以便访问 2.6.0 以上版本的新功能和关键 bug 修复。 如果您目前使用的是兼容性库,请按照安装 Places SDK for Android 部分中的步骤迁移到 Places SDK for Android。
安装客户端库
新版 Places SDK for Android 以静态客户端库的形式分发。
使用 Maven 将 Places SDK for Android 添加到您的 Android Studio 项目中:
如果您目前使用的是地点兼容性库:
替换
dependencies
部分中的以下行:implementation 'com.google.android.libraries.places:places-compat:X.Y.Z'
使用此行代码切换到 Places SDK for Android:
implementation("com.google.android.libraries.places:places:4.3.1")
如果您目前使用的是 Play 服务版本的 Places SDK for Android:
替换
dependencies
部分中的以下行:implementation 'com.google.android.gms:play-services-places:X.Y.Z'
使用此行代码切换到 Places SDK for Android:
implementation("com.google.android.libraries.places:places:4.3.1")
同步您的 Gradle 项目。
将应用项目的
minSdkVersion
设置为 23 或更高。更新“Powered by Google”素材资源:
@drawable/powered_by_google_light // OLD @drawable/places_powered_by_google_light // NEW @drawable/powered_by_google_dark // OLD @drawable/places_powered_by_google_dark // NEW
构建应用。如果您因转换为 Places SDK for Android 而看到任何构建错误,请参阅以下部分,了解如何解决这些错误。
初始化新的 Places SDK 客户端
初始化新的 Places SDK 客户端,如以下示例所示:
// Add an import statement for the client library.
import com.google.android.libraries.places.api.Places;
...
// Initialize Places.
Places.initialize(getApplicationContext(), apiKey);
// Create a new Places client instance.
PlacesClient placesClient = Places.createClient(this);
状态代码
QPS 限制错误的相应状态代码已更改。现在,QPS 限制错误通过 PlaceStatusCodes.OVER_QUERY_LIMIT
返回。不再有 QPD 限制。
添加了以下状态代码:
REQUEST_DENIED
- 表示请求被拒绝。可能的原因包括:- 未提供 API 密钥。
- 提供的 API 密钥无效。
- 您尚未在 Cloud 控制台中启用 Places API。
- 提供的 API 密钥具有错误的密钥限制。
INVALID_REQUEST
- 请求无效,原因是缺少或包含无效实参。NOT_FOUND
- 未找到与给定请求相符的结果。
新方法
新版 Places SDK for Android 引入了全新方法,这些方法旨在实现一致性。所有新方法均遵循以下规则:
- 端点不再使用
get
动词。 - 请求和响应对象与相应的客户端方法同名。
- 请求对象现在具有构建器;必需参数作为请求构建器参数传递。
- 不再使用缓冲区。
本部分将介绍这些新方法,并说明它们的工作原理。
按 ID 提取地点
使用 fetchPlace()
获取有关特定地点的详细信息。fetchPlace()
的功能与 getPlaceById()
类似。
请按照以下步骤获取地点:
调用
fetchPlace()
,并传递一个FetchPlaceRequest
对象,该对象指定了地点 ID 和一个字段列表,用于指定要返回的地点数据。// Define a Place ID. String placeId = "INSERT_PLACE_ID_HERE"; // Specify the fields to return. List<Place.Field> placeFields = Arrays.asList(Place.Field.ID, Place.Field.DISPLAY_NAME); // Construct a request object, passing the place ID and fields array. FetchPlaceRequest request = FetchPlaceRequest.builder(placeId, placeFields) .build();
调用
addOnSuccessListener()
以处理FetchPlaceResponse
。系统会返回单个Place
结果。// Add a listener to handle the response. placesClient.fetchPlace(request).addOnSuccessListener((response) -> { Place place = response.getPlace(); Log.i(TAG, "Place found: " + place.getName()); }).addOnFailureListener((exception) -> { if (exception instanceof ApiException) { ApiException apiException = (ApiException) exception; int statusCode = apiException.getStatusCode(); // Handle error with given status code. Log.e(TAG, "Place not found: " + exception.getMessage()); } });
获取地点照片
使用 fetchPhoto()
获取地点照片。fetchPhoto()
会返回某个地点的照片。简化了请求照片的模式。您现在可以直接从 Place
对象请求 PhotoMetadata
;不再需要单独的请求。
照片的最大宽度或高度为 1600 像素。fetchPhoto()
函数类似。getPhoto()
如需提取地点照片,请按以下步骤操作:
设置对
fetchPlace()
的调用。请务必在请求中添加PHOTO_METADATAS
字段:List<Place.Field> fields = Arrays.asList(Place.Field.PHOTO_METADATAS);
获取 Place 对象(此示例使用
fetchPlace()
,但您也可以使用findCurrentPlace()
):FetchPlaceRequest placeRequest = FetchPlaceRequest.builder(placeId, fields).build();
添加
OnSuccessListener
以从FetchPlaceResponse
中的结果Place
获取照片元数据,然后使用生成的照片元数据获取位图和提供方信息文本:placesClient.fetchPlace(placeRequest).addOnSuccessListener((response) -> { Place place = response.getPlace(); // Get the photo metadata. PhotoMetadata photoMetadata = place.getPhotoMetadatas().get(0); // Get the attribution text. String attributions = photoMetadata.getAttributions(); // Create a FetchPhotoRequest. FetchPhotoRequest photoRequest = FetchPhotoRequest.builder(photoMetadata) .setMaxWidth(500) // Optional. .setMaxHeight(300) // Optional. .build(); placesClient.fetchPhoto(photoRequest).addOnSuccessListener((fetchPhotoResponse) -> { Bitmap bitmap = fetchPhotoResponse.getBitmap(); imageView.setImageBitmap(bitmap); }).addOnFailureListener((exception) -> { if (exception instanceof ApiException) { ApiException apiException = (ApiException) exception; int statusCode = apiException.getStatusCode(); // Handle error with given status code. Log.e(TAG, "Place not found: " + exception.getMessage()); } }); });
根据用户的位置信息查找地点
使用 findCurrentPlace()
查找用户设备的当前位置。findCurrentPlace()
返回一个 PlaceLikelihood
列表,指示用户设备最有可能位于的地点。findCurrentPlace()
的功能与 getCurrentPlace()
类似。
请按以下步骤获取用户设备的当前位置:
确保您的应用请求
ACCESS_FINE_LOCATION
和ACCESS_WIFI_STATE
权限。用户必须授予访问其当前设备位置信息的权限。如需了解详情,请参阅请求应用权限。创建
FindCurrentPlaceRequest
,包括要返回的地点数据类型列表。// Use fields to define the data types to return. List<Place.Field> placeFields = Arrays.asList(Place.Field.DISPLAY_NAME); // Use the builder to create a FindCurrentPlaceRequest. FindCurrentPlaceRequest request = FindCurrentPlaceRequest.builder(placeFields).build();
调用 findCurrentPlace 并处理响应,首先检查以验证用户是否已授予使用其设备位置信息的权限。
// Call findCurrentPlace and handle the response (first check that the user has granted permission). if (ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { placesClient.findCurrentPlace(request).addOnSuccessListener(((response) -> { for (PlaceLikelihood placeLikelihood : response.getPlaceLikelihoods()) { Log.i(TAG, String.format("Place '%s' has likelihood: %f", placeLikelihood.getPlace().getName(), placeLikelihood.getLikelihood())); textView.append(String.format("Place '%s' has likelihood: %f\n", placeLikelihood.getPlace().getName(), placeLikelihood.getLikelihood())); } })).addOnFailureListener((exception) -> { if (exception instanceof ApiException) { ApiException apiException = (ApiException) exception; Log.e(TAG, "Place not found: " + apiException.getStatusCode()); } }); } else { // A local method to request required permissions; // See https://developer.android.com/training/permissions/requesting getLocationPermission(); }
查找自动补全联想查询
使用 findAutocompletePredictions()
针对用户搜索查询返回地点预测结果。findAutocompletePredictions()
的功能与 getAutocompletePredictions()
类似。
以下示例展示了如何调用 findAutocompletePredictions()
:
// Create a new token for the autocomplete session. Pass this to FindAutocompletePredictionsRequest,
// and once again when the user makes a selection (for example when calling fetchPlace()).
AutocompleteSessionToken token = AutocompleteSessionToken.newInstance();
// Create a RectangularBounds object.
RectangularBounds bounds = RectangularBounds.newInstance(
new LatLng(-33.880490, 151.184363),
new LatLng(-33.858754, 151.229596));
// Use the builder to create a FindAutocompletePredictionsRequest.
FindAutocompletePredictionsRequest request = FindAutocompletePredictionsRequest.builder()
// Call either setLocationBias() OR setLocationRestriction().
.setLocationBias(bounds)
//.setLocationRestriction(bounds)
.setCountry("au")
.setTypesFilter(Arrays.asList(PlaceTypes.ADDRESS))
.setSessionToken(token)
.setQuery(query)
.build();
placesClient.findAutocompletePredictions(request).addOnSuccessListener((response) -> {
for (AutocompletePrediction prediction : response.getAutocompletePredictions()) {
Log.i(TAG, prediction.getPlaceId());
Log.i(TAG, prediction.getPrimaryText(null).toString());
}
}).addOnFailureListener((exception) -> {
if (exception instanceof ApiException) {
ApiException apiException = (ApiException) exception;
Log.e(TAG, "Place not found: " + apiException.getStatusCode());
}
});
会话令牌
会话令牌将用户搜索的查询和选择阶段归入不同的会话,以便进行结算。我们建议您针对所有自动补全会话使用会话令牌。会话在用户开始输入查询内容时开始,并在用户选择地点时结束。在每个会话中,用户可以输入多项查询内容,并最终选择一个地点。会话结束后,令牌将失效;您的应用必须为每个会话生成一个新的令牌。
字段掩码
在返回地点详情的方法中,您必须指定每次请求要返回哪些类型的地点数据。这有助于确保您仅请求(并支付)实际会使用的数据。
如需指定要返回的数据类型,请在 FetchPlaceRequest
中传递 Place.Field
的数组,如下例所示:
// Include address, ID, and phone number.
List<Place.Field> placeFields = Arrays.asList(Place.Field.FORMATTED_ADDRESS,
Place.Field.ID,
Place.Field.INTERNATIONAL_PHONE_NUMBER);
如需查看可在字段掩码中使用的字段列表,请参阅地点数据字段(新) 。
详细了解地点数据 SKU。
地点选择器和自动补全更新
本部分介绍了对 Places widget(地点选择器和自动补全)的更改。
程序化自动补全
对自动补全进行了以下更改:
- 已将
PlaceAutocomplete
重命名为Autocomplete
。- 已将
PlaceAutocomplete.getPlace
重命名为Autocomplete.getPlaceFromIntent
。 - 已将
PlaceAutocomplete.getStatus
重命名为Autocomplete.getStatusFromIntent
。
- 已将
PlaceAutocomplete.RESULT_ERROR
已重命名为AutocompleteActivity.RESULT_ERROR
(自动补全 fragment 的错误处理未发生变化)。
地点选取器
地点选取器已于 2019 年 1 月 29 日弃用。该服务已于 2019 年 7 月 29 日关闭,不再提供。继续使用会导致系统显示错误消息。新 SDK 不支持地点选择器。
自动补全 widget
自动补全 widget 已更新:
- 已从所有类中移除
Place
前缀。 - 添加了对会话令牌的支持。该 widget 会在后台自动为您管理令牌。
- 添加了对字段掩码的支持,可让您选择在用户做出选择后返回哪些类型的地点数据。
以下部分介绍了如何向项目添加 Autocomplete widget。
嵌入 AutocompleteFragment
如需添加自动补全 fragment,请执行以下步骤:
向 activity 的 XML 布局添加一个 fragment,如以下示例所示。
<fragment android:id="@+id/autocomplete_fragment" android:layout_width="match_parent" android:layout_height="wrap_content" android:name= "com.google.android.libraries.places.widget.AutocompleteSupportFragment" />
如需将自动补全 widget 添加到 activity,请按以下步骤操作:
- 初始化
Places
,并传递应用上下文和您的 API 密钥。 - 初始化
AutocompleteSupportFragment
。 - 调用
setPlaceFields()
以指明您要获取的地点数据类型。 - 添加
PlaceSelectionListener
以处理结果,并处理可能发生的任何错误。
以下示例展示了如何向 activity 添加自动补全 widget:
/** * Initialize Places. For simplicity, the API key is hard-coded. In a production * environment we recommend using a secure mechanism to manage API keys. */ if (!Places.isInitialized()) { Places.initialize(getApplicationContext(), "YOUR_API_KEY"); } // Initialize the AutocompleteSupportFragment. AutocompleteSupportFragment autocompleteFragment = (AutocompleteSupportFragment) getSupportFragmentManager().findFragmentById(R.id.autocomplete_fragment); autocompleteFragment.setPlaceFields(Arrays.asList(Place.Field.ID, Place.Field.DISPLAY_NAME)); autocompleteFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() { @Override public void onPlaceSelected(Place place) { // TODO: Get info about the selected place. Log.i(TAG, "Place: " + place.getName() + ", " + place.getId()); } @Override public void onError(Status status) { // TODO: Handle the error. Log.i(TAG, "An error occurred: " + status); } });
- 初始化
使用 intent 启动自动补全 activity
- 初始化
Places
,并传递应用上下文和您的 API 密钥 - 使用
Autocomplete.IntentBuilder
创建 intent,并传递所需的PlaceAutocomplete
模式(全屏或叠加)。该 intent 必须调用startActivityForResult
,并传入用于标识 intent 的请求代码。 - 替换
onActivityResult
回调以接收所选地点。
以下示例展示了如何使用 intent 启动自动补全功能,然后处理结果:
/**
* Initialize Places. For simplicity, the API key is hard-coded. In a production
* environment we recommend using a secure mechanism to manage API keys.
*/
if (!Places.isInitialized()) {
Places.initialize(getApplicationContext(), "YOUR_API_KEY");
}
...
// Set the fields to specify which types of place data to return.
List<Place.Field> fields = Arrays.asList(Place.Field.ID, Place.Field.DISPLAY_NAME);
// Start the autocomplete intent.
Intent intent = new Autocomplete.IntentBuilder(
AutocompleteActivityMode.FULLSCREEN, fields)
.build(this);
startActivityForResult(intent, AUTOCOMPLETE_REQUEST_CODE);
...
/**
* Override the activity's onActivityResult(), check the request code, and
* do something with the returned place data (in this example its place name and place ID).
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == AUTOCOMPLETE_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
Place place = Autocomplete.getPlaceFromIntent(data);
Log.i(TAG, "Place: " + place.getName() + ", " + place.getId());
} else if (resultCode == AutocompleteActivity.RESULT_ERROR) {
// TODO: Handle the error.
Status status = Autocomplete.getStatusFromIntent(data);
Log.i(TAG, status.getStatusMessage());
} else if (resultCode == RESULT_CANCELED) {
// The user canceled the operation.
}
}
}
地点选取器已不再提供
地点选取器已于 2019 年 1 月 29 日弃用。该服务已于 2019 年 7 月 29 日关闭,不再提供。继续使用会导致系统显示错误消息。新 SDK 不支持地点选择器。