Google is committed to advancing racial equity for Black communities. See how.

针对 Android (Java) 构建您自己的当前地点选择器

了解如何使用 Google Maps Platform 和 Places SDK for Android 向用户呈现用于确定他们当前位置的地点列表。

bd07a9ad2cb27a06.png

前提条件

  • Java 基本技能

要执行的操作

  • 向 Android 应用添加地图。
  • 使用位置权限对用户的位置进行地理定位。
  • 获取用户当前位置附近的地点。
  • 向用户呈现用于确定他们当前位置的可能地点。

要构建的内容

您要从头开始构建 Android 应用,但可以在调试时下载示例代码,以进行比较。从 GitHub 下载示例代码,如果您将 Git 设置为使用命令行,请输入以下代码:

git clone https://github.com/googlecodelabs/current-place-picker-android.git

如果在此 Codelab 的操作期间遇到任何问题(代码错误、语法错误、措辞含义不明或其他问题),请通过 Codelab 左下角的报告错误链接报告相应问题。

在开始此 Codelab 之前,您需要设置以下内容:

Android Studio

https://developer.android.com/studio 下载 Android Studio。

如果您已安装 Android Studio,请依次点击 Android Studio > Check for Updates...,以确保您安装的是最新版本。

1f36bae83b64e33.png

本实验是使用 Android Studio 3.4 编写的。

Android SDK

在 Android Studio 中,您可以使用 SDK 管理器配置所需的 SDK。本实验使用的是 Android Q SDK。

  1. 在 Android Studio 的欢迎屏幕上,依次点击 Configure > SDK Manager

d3fa03c269ec231c.png

  1. 选中所需 SDK 对应的复选框,然后点击 Apply

如果您尚未安装 SDK,执行此操作后,系统会开始将 SDK 下载到计算机上。

884e0aa1314f70d.png

Google Play 服务

您还需要通过 SDK 管理器安装 Google Play 服务。

  1. 点击 SDK Tools 标签页,然后选中 Google Play services 对应的复选框。

如果状态显示为 Update available,请更新。

ad6211fd78f3b629.png

若要运行应用,您可以连接自己的设备,也可以使用 Android 模拟器。

如果您使用自己的设备,请跳到本页末尾的真实设备说明:更新 Google Play 服务

添加模拟器

  1. 在 Android Studio 的欢迎屏幕上,依次点击 Configure > AVD Manager

5dd2d14c9c56d3f9.png

此时将打开 Android Virtual Device Manager 对话框。

  1. 点击 Create Virtual Device... 以打开您可以从中选择设备的列表。

2d44eada384f8b35.png

  1. 选择在 Play Store 列中带有 Play 图标 d5722488d80cd6be.png 的设备,然后点击 Next

e0248f1c6e85ab7c.png

您将看到一组可以安装的系统映像。如果目标平台为 Android 9.+ (Google Play)Q 版本旁显示 Download 一词,请点击 Download

316d0d1efabd9f24.png

  1. 点击 Next 为您的虚拟设备命名,然后点击 Finish

您会返回到 Your Virtual Devices 列表。

  1. 点击新设备旁边的“启动”图标 ba8adffe56d3b678.png

7605864ed27f77ea.png

片刻之后,模拟器便会打开。

模拟器说明 - 更新 Google Play 服务

  1. 模拟器启动后,在显示的导航栏中点击 ...**。**

2e1156e02643d018.png

此时将打开 Extended controls 对话框。

  1. 点击菜单中的 Google Play

如果有可用更新,请点击 Update

5afd2686c5cad0e5.png

  1. 使用 Google 帐号登录模拟器。

您可以使用自己的帐号,也可以免费创建一个新帐号,以确保您的测试信息与个人信息相互独立。

登录之后,Google Play 就会打开 Google Play 服务。

  1. 点击 Update 以获取最新版的 Google Play 服务。

f4bc067e80630b9c.png

如果系统要求您完成帐号设置和添加付款方式,请点击 Skip

在模拟器中设置位置

  1. 模拟器启动后,在主屏幕的搜索栏中输入“maps”,屏幕上就会弹出 Google 地图应用图标。

2d996aadd53685a6.png

  1. 点击该图标即可启动 Google 地图应用。

您将看到默认地图。

  1. 在地图的右下角,点击 Your Location c5b4e2fda57a7e71.png

系统会要求您授予该手机使用位置信息的权限。

f2b68044eabca151.png

  1. 点击 ...,打开 Extended controls 菜单。
  2. 点击 Location 标签页。
  3. 输入纬度和经度。

在此处随意输入纬度和经度,但要确保覆盖的区域内有很多地点。

(夏威夷州毛伊县基希地区某个城镇的纬度为 20.7818,经度为 -156.4624,使用这两个经纬度值重现此 Codelab 中的结果。)

  1. 点击 Send,然后地图就会更新以包含此位置。

f9576b35218f4187.png

您现在可以开始运行应用,并使用位置进行测试。

真实设备说明 - 更新 Google Play 服务

如果您使用的是真实 Android 设备,请执行以下操作:

  1. 使用主屏幕上的搜索栏搜索 Google Play 服务,然后打开它。
  2. 点击更多详情

点击更新(如果有)。

ad16cdb975b5c3f7.png baf0379ef8a9c88c.png

  1. 在 Android Studio 的欢迎屏幕上,选择 Start a new Android Studio project
  2. Phone and Tablet 标签页上,选择 Google Maps Activity

c9c80aa8211a8761.png

此时将打开 Configure your project 对话框。您可以在这里为应用命名,并根据网域创建软件包。

下面是 Current Place 应用的设置,该应用与 com.google.codelab.currentplace 软件包对应。

37f5b93b94ee118c.png

  1. 选择 Java 作为语言,并选中“Use androidx. artifacts”对应的复选框。*

对于其余设置,请保留默认设置。

  1. 点击 Finish

若要获取 Android 中的位置权限,您需要使用 Google Play 服务的 Google Location and Activity Recognition API。如需详细了解如何添加此 API 和其他 Google Play 服务 API,请参阅设置 Google Play 服务

Android Studio 项目通常包含两个 build.gradle 文件。一个对应整个项目,一个对应应用。如果您在 Android 视图中显示 Android Studio Project Explorer,将会在 Gradle Scripts 文件夹中同时看到这两个文件。您需要修改 build.gradle (Module: app) 文件才能添加 Google 服务。

f3043429cf719c47.png

  1. 将两行代码添加到 dependencies 部分,以添加用于获取位置信息的 Google 服务和 Places API(参阅上下文中的示例代码)。

build.gradle(Module:app)

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.google.codelab.currentplace"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'com.google.android.gms:play-services-maps:16.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'

    implementation 'com.google.android.gms:play-services-location:16.0.0'
    implementation 'com.google.android.libraries.places:places:1.1.0'
}

为了完成以下启用步骤,您需要启用 Maps SDK for AndroidPlaces API

设置 Google Maps Platform

如果您还没有已启用结算功能的 Google Cloud Platform 帐号和项目,请参阅 Google Maps Platform 使用入门指南,创建结算帐号和项目。

  1. Cloud Console 中,点击项目下拉菜单,选择要用于此 Codelab 的项目。

  1. Google Cloud Marketplace 中启用此 Codelab 所需的 Google Maps Platform API 和 SDK。为此,请按照此视频此文档中的步骤操作。
  2. 在 Cloud Console 的凭据页面中生成 API 密钥。您可以按照此视频此文档中的步骤操作。向 Google Maps Platform 发出的所有请求都需要 API 密钥。

复制刚刚创建的 API 密钥。切换回 Android Studio,在 Android > app > res > values 下找到文件 google_maps_api.xml

YOUR_KEY_HERE 替换为您复制的 API 密钥。

aa576e551a7a1009.png

您的应用现已配置完成。

  1. 在 Project Explorer 中,打开 Android > app > res > layout 中的 activity_maps.xml 文件。

4e0d986480c57efa.png

  1. 基本界面将会在屏幕右侧打开,底部显示两个标签页,您可以通过它们为布局选择“Design”或“Text”编辑器。选择 Text,并将布局文件的全部内容替换为以下代码:

activity_maps.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:minHeight="?attr/actionBarSize"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:titleTextColor="@android:color/white"
        android:background="@color/colorPrimary" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <fragment
            android:id="@+id/map"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            android:layout_width="match_parent"
            android:layout_height="349dp"
            tools:context=".MapsActivity" />

        <ListView
            android:id="@+id/listPlaces"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

</LinearLayout>

此时,界面如下所示:

1bf786808a4697ce.png

为了让用户在想要选择自己当前地点时有可以点击的按钮,请添加带图标的应用栏,用户可在其中查找自己的当前地点和显示附近可能的地点。应用栏如下所示:

3a17c92b613a26c5.png

在手机上,只显示图标。在屏幕空间较大的平板电脑上,也可以显示文字。

制作图标

  1. 在 Project Explorer 中,依次点击 Android > app,然后右键点击 res 文件夹,接着依次选择 New > Image Asset

此时将打开 Asset Studio

  1. Icon Type 菜单中,点击 Action Bar and Tab Icons
  2. 将资源命名为 ic_geolocate
  3. 选择 Clip Art 作为资源类型**。**
  4. 点击 Clip Art 旁边的图片。

此时将打开 Select Icon 窗口。

  1. 选择图标。

您可以使用搜索栏查找与意图相关的图标。

  1. 搜索 location 并选择与位置相关的图标。

当用户想要将相机对准其当前位置时,my location 图标与 Google 地图应用中使用的图标相同。

  1. 依次点击 OK > Next > Finish,然后确认有一个名为 drawable 的新文件夹,其中包含您的新图标文件。

b9e0196137ed18ae.png

添加字符串资源

  1. 在 Project Explorer 中,依次点击 Android > app > res > values,然后打开 strings.xml 文件。
  2. <string name="title_activity_maps">Map</string> 之后添加以下几行代码:

strings.xml

    <string name="action_geolocate">Pick Place</string>
    <string name="default_info_title">Default Location</string>
    <string name="default_info_snippet">No places found, because location permission is disabled.</string>

第一行代码在应用栏中使用,如果空间充足,则会在图标旁边显示文本标签。其他几行代码用于您添加到地图的标记。

现在,此文件中的代码如下所示:

<resources>
    <string name="app_name">Current Place</string>
    <string name="title_activity_maps">Map</string>
    <string name="action_geolocate">Pick Place</string>
    <string name="default_info_title">Default Location</string>
    <string name="default_info_snippet">No places found, because location permission is disabled.</string>
</resources>

添加应用栏

  1. 在 Project Explorer 中,依次点击 Android > app,然后右键点击 res 文件夹,接着依次选择 New > Directory,以在 app/src/main/res 下创建新的子目录。
  2. 将目录命名为 menu
  3. 右键点击 menu 文件夹,然后依次选择 New > File
  4. 将文件命名为 menu.xml
  5. 粘贴以下代码:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- "Locate me", should appear as action button if possible -->
    <item
        android:id="@+id/action_geolocate"
        android:icon="@drawable/ic_geolocate"
        android:title="@string/action_geolocate"
        app:showAsAction="always|withText" />

</menu>

更新应用栏样式

  1. 在 Project Explorer 中,依次展开 Android > app > res > values,然后打开其中的文件 styles.xml
  2. <style> 标记中,将 parent 属性修改为 "Theme.AppCompat.NoActionBar"
  3. 请注意 name 属性,您将在后续步骤中用到此属性。

styles.xml

<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">

在 AndroidManifest.xml 中更新应用主题

  1. 依次点击 Android > app > manifests,然后打开 AndroidManifest.xml 文件。
  2. 找到 android:theme 行,将值修改为或确认值为 @style/AppTheme

AndroidManifest.xml

   <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">

您现在可以开始编码了!

  1. 在 Project Explorer 中,找到 MapsActivity.java 文件。

此文件位于您在第 1 步为应用创建的软件包所对应的文件夹中。

8b0fa27d417f5f55.png

  1. 打开此文件,此时您使用的是 Java 代码编辑器。

导入 Places SDK 和其他依赖项

MapsActivity.java 顶部添加下列几行代码,以替换现有导入语句。

这些代码包括现有导入语句,并添加了更多可在此 Codelab 的代码中使用的导入语句。

MapsActivity.java

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.android.libraries.places.api.Places;
import com.google.android.libraries.places.api.model.Place;
import com.google.android.libraries.places.api.model.PlaceLikelihood;
import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest;
import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;
import com.google.android.libraries.places.api.net.PlacesClient;

import java.util.Arrays;
import java.util.List;

更新类签名

Places API 使用 AndroidX 组件实现向后兼容性支持,因此您需要定义该组件来扩展 AppCompatActivity。该组件会取代默认为地图活动定义的 FragmentActivity 扩展。

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback {

添加类变量

然后,声明在不同类方法中使用的各种类变量,其中包括界面元素和状态代码。这些变量应该位于 GoogleMap mMap 变量声明的正下方。

    // New variables for Current Place picker
    private static final String TAG = "MapsActivity";
    ListView lstPlaces;
    private PlacesClient mPlacesClient;
    private FusedLocationProviderClient mFusedLocationProviderClient;

    // The geographical location where the device is currently located. That is, the last-known
    // location retrieved by the Fused Location Provider.
    private Location mLastKnownLocation;

    // A default location (Sydney, Australia) and default zoom to use when location permission is
    // not granted.
    private final LatLng mDefaultLocation = new LatLng(-33.8523341, 151.2106085);
    private static final int DEFAULT_ZOOM = 15;
    private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
    private boolean mLocationPermissionGranted;

    // Used for selecting the Current Place.
    private static final int M_MAX_ENTRIES = 5;
    private String[] mLikelyPlaceNames;
    private String[] mLikelyPlaceAddresses;
    private String[] mLikelyPlaceAttributions;
    private LatLng[] mLikelyPlaceLatLngs;

更新 onCreate 方法

您需要更新 onCreate 方法来处理位置信息服务的运行时用户权限,设置界面元素和创建 Places API 客户端。

将下列几行关于操作工具栏、视图设置和 Places API 客户端的代码添加到现有 onCreate() 方法的末尾。

MapsActivity.java onCreate()

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

        //
        // PASTE THE LINES BELOW THIS COMMENT
        //

        // Set up the action toolbar
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Set up the views
        lstPlaces = (ListView) findViewById(R.id.listPlaces);

        // Initialize the Places client
        String apiKey = getString(R.string.google_maps_key);
        Places.initialize(getApplicationContext(), apiKey);
        mPlacesClient = Places.createClient(this);
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
    }

为应用栏菜单添加代码

以下两个方法可添加应用栏菜单(包含一项内容,即“选择地点”图标)并处理用户对该图标的点击。

复制以下两个方法,并将其粘贴在文件中 onCreate 方法之后的位置。

MapsActivity.java onCreateOptionsMenu() 和 onOptionsItemSelected()

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
           case R.id.action_geolocate:

                // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                // Present the current place picker
                // pickCurrentPlace();
                return true;

            default:
                // If we got here, the user's action was not recognized.
                // Invoke the superclass to handle it.
                return super.onOptionsItemSelected(item);

        }
    }

测试应用

  1. 在 Android Studio 中,点击 Run 或依次点击 Run menu > Run ‘app'

28bea91c68c36fb2.png

  1. 系统会要求您选择部署目标。正在运行的模拟器应该会显示在此列表中。选择模拟器,然后 Android Studio 会为您将应用部署到该模拟器中。

f44658ca91f6f41a.png

片刻之后,应用便会启动。您会看到一个以澳大利亚悉尼为中心的地图,其中包含一个按钮和未填充的地点列表。

68eb8c70f4748350.png

该地图的焦点不会移动到用户的位置,除非您请求获取设备位置信息的权限。

在地图可以使用后请求位置权限

  1. 定义一个名为 getLocationPermission 的方法,用于请求用户权限。

将此代码粘贴在您刚刚创建的 onOptionsSelected 方法下。

MapsActivity.java getLocationPermission()

    private void getLocationPermission() {
        /*
         * Request location permission, so that we can get the location of the
         * device. The result of the permission request is handled by a callback,
         * onRequestPermissionsResult.
         */
        mLocationPermissionGranted = false;
        if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
                android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
            mLocationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    }
  1. 将两行代码添加到现有 onMapReady 方法的末尾,以启用缩放控件并向用户请求位置权限。

MapsActivity.java onMapReady()

   @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

        // Add a marker in Sydney and move the camera
        LatLng sydney = new LatLng(-34, 151);
        mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
        mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));

        //
        // PASTE THE LINES BELOW THIS COMMENT
        //

        // Enable the zoom controls for the map
        mMap.getUiSettings().setZoomControlsEnabled(true);

        // Prompt the user for permission.
        getLocationPermission();

    }

处理所请求权限的结果

用户针对请求权限对话框做出响应后,Android 会调用此回调。

getLocationPermission() 方法之后粘贴下列代码:

MapsActivity.java onRequestPermissionsResult()

   /**
     * Handles the result of the request for location permissions
     */
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
    }

当用户点击应用栏中的 Pick Place 时,应用会调用 pickCurrentPlace() 方法,该方法会调用您之前定义的 getDeviceLocation() 方法。检索设备的最新位置信息之后,getDeviceLocation 方法会调用另一个方法,即 getCurrentPlaceLikelihoods,

调用 findCurrentPlace API 并处理响应

getCurrentPlaceLikelihoods 构建 findCurrentPlaceRequest,并调用 Places API findCurrentPlace 任务。如果任务成功,则返回 findCurrentPlaceResponse,其中包含 placeLikelihood 对象列表。这些对象都具有多个属性,包括地点的名称和地址,以及您在该地点的可能性概率(从 0 到 1 的双精度值)。此方法通过 placeLikelihoods 构建地点详情列表来处理响应。

此代码会遍历五个最有可能的地点,并将可能性概率超过 0 的地点添加到随后呈现的列表中。如果您希望显示的地点超过或少于五个,可以修改 M_MAX_ENTRIES 常量。

onMapReady 方法之后粘贴下列代码。

MapsActivity.java getCurrentPlaceLikelihoods()

   private void getCurrentPlaceLikelihoods() {
        // Use fields to define the data types to return.
        List<Place.Field> placeFields = Arrays.asList(Place.Field.NAME, Place.Field.ADDRESS,
                Place.Field.LAT_LNG);

        // Get the likely places - that is, the businesses and other points of interest that
        // are the best match for the device's current location.
        @SuppressWarnings("MissingPermission") final FindCurrentPlaceRequest request =
                FindCurrentPlaceRequest.builder(placeFields).build();
        Task<FindCurrentPlaceResponse> placeResponse = mPlacesClient.findCurrentPlace(request);
        placeResponse.addOnCompleteListener(this,
                new OnCompleteListener<FindCurrentPlaceResponse>() {
                    @Override
                    public void onComplete(@NonNull Task<FindCurrentPlaceResponse> task) {
                        if (task.isSuccessful()) {
                            FindCurrentPlaceResponse response = task.getResult();
                            // Set the count, handling cases where less than 5 entries are returned.
                            int count;
                            if (response.getPlaceLikelihoods().size() < M_MAX_ENTRIES) {
                                count = response.getPlaceLikelihoods().size();
                            } else {
                                count = M_MAX_ENTRIES;
                            }

                            int i = 0;
                            mLikelyPlaceNames = new String[count];
                            mLikelyPlaceAddresses = new String[count];
                            mLikelyPlaceAttributions = new String[count];
                            mLikelyPlaceLatLngs = new LatLng[count];

                            for (PlaceLikelihood placeLikelihood : response.getPlaceLikelihoods()) {
                                Place currPlace = placeLikelihood.getPlace();
                                mLikelyPlaceNames[i] = currPlace.getName();
                                mLikelyPlaceAddresses[i] = currPlace.getAddress();
                                mLikelyPlaceAttributions[i] = (currPlace.getAttributions() == null) ?
                                        null : TextUtils.join(" ", currPlace.getAttributions());
                                mLikelyPlaceLatLngs[i] = currPlace.getLatLng();

                                String currLatLng = (mLikelyPlaceLatLngs[i] == null) ?
                                        "" : mLikelyPlaceLatLngs[i].toString();

                                Log.i(TAG, String.format("Place " + currPlace.getName()
                                        + " has likelihood: " + placeLikelihood.getLikelihood()
                                        + " at " + currLatLng));

                                i++;
                                if (i > (count - 1)) {
                                    break;
                                }
                            }

                            // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                            // Populate the ListView
                            // fillPlacesList();
                        } else {
                            Exception exception = task.getException();
                            if (exception instanceof ApiException) {
                                ApiException apiException = (ApiException) exception;
                                Log.e(TAG, "Place not found: " + apiException.getStatusCode());
                            }
                        }
                    }
                });
    }

将地图的相机移动到设备的当前位置

如果用户授予权限,应用会获取用户的最新位置,并将相机移动到该位置的中心。

如果用户拒绝授予权限,应用只会将相机移动到本页开头提及的常量所定义的默认位置(在示例代码中,该位置为澳大利亚悉尼)。

getPlaceLikelihoods() 方法之后粘贴下列代码:

MapsActivity.java getDeviceLocation()

    private void getDeviceLocation() {
        /*
         * Get the best and most recent location of the device, which may be null in rare
         * cases when a location is not available.
         */
        try {
            if (mLocationPermissionGranted) {
                Task<Location> locationResult = mFusedLocationProviderClient.getLastLocation();
                locationResult.addOnCompleteListener(this, new OnCompleteListener<Location>() {
                    @Override
                    public void onComplete(@NonNull Task<Location> task) {
                        if (task.isSuccessful()) {
                            // Set the map's camera position to the current location of the device.
                            mLastKnownLocation = task.getResult();
                            Log.d(TAG, "Latitude: " + mLastKnownLocation.getLatitude());
                            Log.d(TAG, "Longitude: " + mLastKnownLocation.getLongitude());
                            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                    new LatLng(mLastKnownLocation.getLatitude(),
                                            mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                        } else {
                            Log.d(TAG, "Current location is null. Using defaults.");
                            Log.e(TAG, "Exception: %s", task.getException());
                            mMap.moveCamera(CameraUpdateFactory
                                    .newLatLngZoom(mDefaultLocation, DEFAULT_ZOOM));
                        }

                       getCurrentPlaceLikelihoods();
                    }
                });
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }

当用户点击“Pick Place”时,检查位置权限

当用户点按 Pick Place 时,此方法会检查位置权限,如果用户未授予权限,则会提示用户。

如果用户已授予权限,此方法将调用 getDeviceLocation,以启动获取当前可能地点的流程。

  1. getDeviceLocation() 之后添加此方法:

MapsActivity.java pickCurrentPlace()

   private void pickCurrentPlace() {
        if (mMap == null) {
            return;
        }

        if (mLocationPermissionGranted) {
            getDeviceLocation();
        } else {
            // The user has not granted permission.
            Log.i(TAG, "The user did not grant location permission.");

            // Add a default marker, because the user hasn't selected a place.
            mMap.addMarker(new MarkerOptions()
                    .title(getString(R.string.default_info_title))
                    .position(mDefaultLocation)
                    .snippet(getString(R.string.default_info_snippet)));

            // Prompt the user for permission.
            getLocationPermission();
        }
    }
  1. 现在已经定义了 pickCurrentPlace,可以在 onOptionsItemSelected() 中找到用于调用 pickCurrentPlace 的代码行,取消对它的备注。

MapsActivity.java onOptionItemSelected()

           case R.id.action_geolocate:

                // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                // Present the Current Place picker
                pickCurrentPlace();
                return true;

测试应用

如果您现在运行应用并点按 Pick Place,它应该会提示您授予位置权限。

  • 如果您同意授予权限,系统就会保存该偏好设置,并且不会再显示提示信息。如果您拒绝授予权限,那么在下次点按该按钮时,系统还会显示提示信息。
  • 尽管 getPlaceLikelihoods 已获取当前的可能地点,但 ListView 尚未显示这些地点。在 Android Studio 中,您可以点击 ⌘6,在 Logcat 中检查标记为 MapsActivity 的语句的日志,以验证您的新方法是否正常运行。
  • 如果您授予了权限,日志中包含 Latitude:Longitude: 的语句,它们显示了检测到的设备位置。如果您之前使用 Google 地图和模拟器的“Extended controls”菜单指定模拟器的位置,上述语句就会显示该位置。
  • 如果成功调用了 findCurrentPlace,日志中包含五个语句,它们会显示五个最有可能的地点的名称和位置。

d9896a245b81bf3.png

为所选地点设置处理程序

让我们想一下,当用户点击 ListView 中的内容时,我们希望发生什么。为了确认用户选择的当前所在地点,您可以在地图中为该地点添加标记。如果用户点击该标记,将弹出信息窗口,其中显示该地点的名称和地址。

pickCurrentPlace 方法之后粘贴此点击处理程序。

MapsActivity.java listClickedHandler

    private AdapterView.OnItemClickListener listClickedHandler = new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView parent, View v, int position, long id) {
            // position will give us the index of which place was selected in the array
            LatLng markerLatLng = mLikelyPlaceLatLngs[position];
            String markerSnippet = mLikelyPlaceAddresses[position];
            if (mLikelyPlaceAttributions[position] != null) {
                markerSnippet = markerSnippet + "\n" + mLikelyPlaceAttributions[position];
            }

            // Add a marker for the selected place, with an info window
            // showing information about that place.
            mMap.addMarker(new MarkerOptions()
                    .title(mLikelyPlaceNames[position])
                    .position(markerLatLng)
                    .snippet(markerSnippet));

           // Position the map's camera at the location of the marker.
            mMap.moveCamera(CameraUpdateFactory.newLatLng(markerLatLng));
        }
    };

填充 ListView

现在,您已经获取了用户当前最有可能访问的地点的列表,可以在 ListView 中向用户呈现这些选项。您还可以设置 ListView 点击监听器,以使用您刚刚定义的点击处理程序。

在点击处理程序之后粘贴下列方法:

MapsActivity.java fillPlacesList()

    private void fillPlacesList() {
        // Set up an ArrayAdapter to convert likely places into TextViews to populate the ListView
        ArrayAdapter<String> placesAdapter =
                new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mLikelyPlaceNames);
        lstPlaces.setAdapter(placesAdapter);
        lstPlaces.setOnItemClickListener(listClickedHandler);
    }

现在已经定义了 fillPlacesList,可以在 findPlaceLikelihoods 的末尾找到用于调用 fillPlacesList 的代码行,取消对它的备注。

MapsActivity.java fillPlaceLikelihoods()

               // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                // Populate the ListView
                fillPlacesList();

以上就是当前地点选取器所需的所有代码!

测试选择地点

  1. 再次运行应用。

这次,当您点按 Pick Place 时,应用会在列表中填入此位置附近的已命名地点。毛伊岛上这个位置附近的地点有 Ululani's Hawaiian Shave Ice 和 Sugar Beach Bake Shop 等。由于多个地点都与该位置的坐标非常相近,因此这里列出了您可能位于的地点。

  1. 点击 ListView 中的地点名称。

您应该会看到一个添加到地图的标记。

  1. 点按该标记。

您可以查看地点详情。

e52303cc0de6a513.png 864c74342fb52a01.png

测试其他位置

如果您想更改位置,并且使用的是模拟器,当您在模拟器的“Extended controls”菜单中更新位置坐标时,设备位置信息不会自动更新。

为了解决这个问题,请按照以下步骤使用原生 Google 地图应用强制更新模拟器的位置:

  1. 打开 Google 地图。
  2. 点按 > 位置,以将纬度和经度更改为新坐标,然后点按发送
  3. 例如,您可以使用“纬度:49.2768 和经度:-123.1142”将位置设为加拿大温哥华市中心。
  4. 验证 Google 地图是否已将地图中心重新设为新坐标对应的位置。您可能需要在 Google 地图应用中点按我的位置按钮,才能请求重新设置地图中心。
  5. 返回到当前地点应用,并点按 Pick Place,以获取以新坐标为中心的地图,这时可以看到一个包含当前可能地点的新列表。

9adb99d1ce25c184.png

这样就大功告成了!您构建了一个简单的应用,它可以检查当前位置所在的地点,并提供您在每个地点的可能性概率。尽情试用!

接下来,您可以在完成上述修改后运行应用,以完成额外步骤!

为了防止您的 API 密钥被盗,您需要妥善保管该密钥,以便只有您的 Android 应用可以使用该密钥。如果不加限制,任何拥有您密钥的用户都可以使用它来调用 Google Maps Platform API,并导致您支付费用。

获取 SHA-1 证书

稍后,您在限制 API 密钥时需要使用此证书。以下是用于获取调试证书的一组指令。

对于 Linux 或 macOS,请打开终端窗口,然后输入以下内容:

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

对于 Windows Vista 和 Windows 7,请运行以下命令:

keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android

您会看到如下所示的输出:

Alias name: androiddebugkey
Creation date: Jan 01, 2013
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Android Debug, O=Android, C=US
Issuer: CN=Android Debug, O=Android, C=US
Serial number: 4aa9b300
Valid from: Mon Jan 01 08:04:04 UTC 2013 until: Mon Jan 01 18:04:04 PST 2033
Certificate fingerprints:
     MD5:  AE:9F:95:D0:A6:86:89:BC:A8:70:BA:34:FF:6A:AC:F9
     SHA1: BB:0D:AC:74:D3:21:E1:43:07:71:9B:62:90:AF:A1:66:6E:44:5D:75
     Signature algorithm name: SHA1withRSA
     Version: 3

以 SHA1 开头的行包含证书的 SHA-1 指纹。该指纹是以英文冒号分隔的 20 个两位十六进制数字的序列。

如果您准备好发布应用,请按照此文档中的说明检索您的发布证书。

向 API 密钥添加限制

  1. 在 Cloud Console 中,依次转到 API 和服务 > 凭据

您用于此应用的密钥应列在 API 密钥下方。

  1. 点击 6454a04865d551e6.png 即可修改密钥设置。

316b052c621ee91c.png

  1. 在 API 密钥页面上的密钥限制后面,按照以下步骤设置应用限制
  2. 选择 Android 应用并按照说明操作。
  3. 点击添加项
  4. 输入您的软件包名称和 SHA-1 证书指纹(可在上一部分中检索到)。

例如:

com.google.codelab.currentplace
BB:0D:AC:74:D3:21:E1:43:07:71:9B:62:90:AF:A1:66:6E:44:5D:75s
  1. 如需进一步的防护措施,请按照以下步骤设置 API 限制
  2. API 限制设置完成后,选择限制密钥
  3. 选择 Maps SDK for Android 和 Places API。
  4. 依次点击完成保存

您构建了一个简单的应用,它可以检查当前位置最有可能所在的地点,并为用户选择的地点向地图添加标记。

了解更多内容