Google Places for Android 入门指南

原文出处:http://itangqi.me/android/google-places-for-android-guide/ 

前言

最近由于项目的原因,接触到了 Google Places 的使用与开发,学习并在项目中实现了简单的地点定位与自动补全功能。
在整个实践过程中,发现除了官方文档外,网上可以参考的中文教程寥寥无几且质量都不高,遂决定自己写篇入门指南(其实也没多少干货啦),希望可以帮助到,有这方面需求的同学们。下面,我将以一个实际的例子,图文并茂的带着你快速了解并使用上 Google Places .


在 Google Developers Console 中创建 API 项目

如果你之前从未在应用中使用过 Google 提供的相关服务(API),那么请直接使用系统引导帮你完成整个流程并自动激活 Google Places API for Android ,请点击此链接,按下面截图所示进行操作,便可轻松完成创建。

  1. 选择「Create a new project」 

  2. 选择「Go to Credentials」

  3. 此处直接选择「Create」,既默认所有应用均可使用同样的「API key」 

  4. 好啦,此时主角「API key」便自动生成了 

  5. 如果你还需要新增(使用)其他服务(API),请在「APIs」里进行查看与添加 


配置你的应用

使用 Google Places API for Android 的所有应用均需执行以下配置步骤。

添加 GOOGLE PLAY 服务

要访问 Google Places API for Android,应用的开发项目必须包含 Google Play 服务。通过 SDK 管理器下载并安装 Google Play 服务组件,然后将库添加至您的项目。

-buidl.gradle (app)

dependencies {
    compile 'com.google.android.gms:play-services:7.5.0'
}

添加相应权限

-AndroidManifest.xml

<!-- PlacePicker requires the ACCESS_FINE_LOCATION permission and a geo API key.-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- PlacePicker also requires OpenGL ES version 2 -->
<uses-feature
	android:glEsVersion="0x00020000"
   	android:required="true" />

添加 API 密钥

请按照以下代码示例所示,将你的 API 密钥添加至 Manifest ,并将 API_KEY 替换为您自己的 API 密钥:

-AndroidManifest.xml

<application>
  ...
  <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
            
  <meta-data
      android:name="com.google.android.geo.API_KEY"
      android:value="API_KEY"/>
</application>

当前地点

获取当前位置

要查找本地商家或设备的最后已知所在地点,请调用 PlaceDetectionApi.getCurrentPlace()。

您可以选择指定一个 PlaceFilter,以将结果限制为一个或多个地点 ID(最多 10 个),或者仅选择当前打开的地点。如果未指定筛选器,则不会筛选结果。

API 会在 PendingResult 中返回 PlaceLikelihoodBuffer。PlaceLikelihoodBuffer 包含表示类似地点的 PlaceLikelihood 对象列表。对于每个地点,结果中都包含指示地点是正确地点的可能性信息。如果没有与筛选条件对应的已知地点,缓冲区可能为空。

您可以调用 PlaceLikelihood.getPlace() 来检索 Place 对象,调用PlaceLikelihood.getLikelihood() 来获取地点的可能性评分。值越高,表示该地点是最佳匹配项的可能性越大。

地点自动完成

要获取预测地点名称和/或地址的列表,请调用 GeoDataApi.getAutocompletePredictions(),传递以下参数:

  • 必填:query 字符串包含用户键入的文本。

  • 必填:LatLngBounds 对象,将结果限制为通过纬度和经度边界指定的特定区域。

  • 可选:AutocompleteFilter,包含一组地点类型,您可以使用它们将结果限制为一种或多种地点类型,如商店、学校、邮局等。有关可用地点类型的列表,请参见支持的类型列表。

API 会在 PendingResult 中返回AutocompletePredictionBuffer。AutocompletePredictionBuffer 包含表示预测地点的AutocompletePrediction 对象列表。如果没有与查询和筛选条件对应的已知地点,缓冲区可能为空。

对于每个预测地点,都可以调用以下方法来检索地点详情:

  • getDescription() – 返回地点说明。

  • getMatchedSubstrings() – 通过与此地点匹配的 query 返回子字符串列表。例如,您可以使用这些子字符串突出显示用户查询中的匹配文本。

  • getDescription() – 返回预测地点的地点 ID。地点 ID 是唯一标识地点的文本标识符。有关地点 ID 的详细信息,请参阅地点 ID 概览

  • `getPlaceTypes() – 返回与此地点关联的地点类型列表。


具体实现

-activity_main.xml

<?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"
    android:layout_margin="@dimen/margin_16dp"
    android:orientation="vertical">
    
    <AutoCompleteTextView
        android:id="@+id/autocomplete_places"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:hint="@string/autocomplete_hint" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingTop="@dimen/margin_8dp">
        <Button
            android:id="@+id/ll_current_location"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:text="@string/get_current_place"
            android:textAppearance="?android:attr/textAppearanceSmall" />
    </LinearLayout>
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:src="@drawable/powered_by_google_light" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/selected_place"
        android:textAppearance="?android:attr/textAppearanceMedium" />
    <TextView
        android:id="@+id/place_details"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:autoLink="all"
        android:paddingTop="@dimen/margin_8dp"
        android:text=""
        android:textAppearance="?android:attr/textAppearanceMedium" />
    <TextView
        android:id="@+id/place_attribution"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:autoLink="all"
        android:paddingTop="@dimen/margin_16dp"
        android:text=""
        android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>

-PlaceAutocompleteAdapter.java

public class PlaceAutocompleteAdapter
        extends ArrayAdapter<PlaceAutocompleteAdapter.PlaceAutocomplete> implements Filterable {
    private static final String TAG = "PlaceAutocompleteAdapter";
    /**
     * Current results returned by this adapter.
     */
    private ArrayList<PlaceAutocomplete> mResultList;
    /**
     * Handles autocomplete requests.
     */
    private GoogleApiClient mGoogleApiClient;
    /**
     * The bounds used for Places Geo Data autocomplete API requests.
     */
    private LatLngBounds mBounds;
    /**
     * The autocomplete filter used to restrict queries to a specific set of place types.
     */
    private AutocompleteFilter mPlaceFilter;
    /**
     * Initializes with a resource for text rows and autocomplete query bounds.
     *
     * @see ArrayAdapter#ArrayAdapter(Context, int)
     */
    public PlaceAutocompleteAdapter(Context context, int resource, GoogleApiClient googleApiClient,
                                    LatLngBounds bounds, AutocompleteFilter filter) {
        super(context, resource);
        mGoogleApiClient = googleApiClient;
        mBounds = bounds;
        mPlaceFilter = filter;
    }
    /**
     * Sets the bounds for all subsequent queries.
     */
    public void setBounds(LatLngBounds bounds) {
        mBounds = bounds;
    }
    /**
     * Returns the number of results received in the last autocomplete query.
     */
    @Override
    public int getCount() {
        return mResultList.size();
    }
    /**
     * Returns an item from the last autocomplete query.
     */
    @Override
    public PlaceAutocomplete getItem(int position) {
        return mResultList.get(position);
    }
    /**
     * Returns the filter for the current set of autocomplete results.
     */
    @Override
    public Filter getFilter() {
        Filter filter = new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults results = new FilterResults();
                // Skip the autocomplete query if no constraints are given.
                if (constraint != null) {
                    // Query the autocomplete API for the (constraint) search string.
                    mResultList = getAutocomplete(constraint);
                    if (mResultList != null) {
                        // The API successfully returned results.
                        results.values = mResultList;
                        results.count = mResultList.size();
                    }
                }
                return results;
            }
            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                if (results != null && results.count > 0) {
                    // The API returned at least one result, update the data.
                    notifyDataSetChanged();
                } else {
                    // The API did not return any results, invalidate the data set.
                    notifyDataSetInvalidated();
                }
            }
        };
        return filter;
    }
    private ArrayList<PlaceAutocomplete> getAutocomplete(CharSequence constraint) {
        if (mGoogleApiClient.isConnected()) {
            Log.i(TAG, "Starting autocomplete query for: " + constraint);
            // Submit the query to the autocomplete API and retrieve a PendingResult that will
            // contain the results when the query completes.
            PendingResult<AutocompletePredictionBuffer> results =
                    Places.GeoDataApi
                            .getAutocompletePredictions(mGoogleApiClient, constraint.toString(),
                                    mBounds, mPlaceFilter);
            // This method should have been called off the main UI thread. Block and wait for at most 60s
            // for a result from the API.
            AutocompletePredictionBuffer autocompletePredictions = results
                    .await(60, TimeUnit.SECONDS);
            // Confirm that the query completed successfully, otherwise return null
            final Status status = autocompletePredictions.getStatus();
            if (!status.isSuccess()) {
                Toast.makeText(getContext(), "Error contacting API: " + status.toString(),
                        Toast.LENGTH_SHORT).show();
                Log.e(TAG, "Error getting autocomplete prediction API call: " + status.toString());
                autocompletePredictions.release();
                return null;
            }
            Log.i(TAG, "Query completed. Received " + autocompletePredictions.getCount()
                    + " predictions.");
            // Copy the results into our own data structure, because we can't hold onto the buffer.
            // AutocompletePrediction objects encapsulate the API response (place ID and description).
            Iterator<AutocompletePrediction> iterator = autocompletePredictions.iterator();
            ArrayList resultList = new ArrayList<>(autocompletePredictions.getCount());
            while (iterator.hasNext()) {
                AutocompletePrediction prediction = iterator.next();
                // Get the details of this prediction and copy it into a new PlaceAutocomplete object.
                resultList.add(new PlaceAutocomplete(prediction.getPlaceId(),
                        prediction.getDescription()));
            }
            // Release the buffer now that all data has been copied.
            autocompletePredictions.release();
            return resultList;
        }
        Log.e(TAG, "Google API client is not connected for autocomplete query.");
        return null;
    }
    /**
     * Holder for Places Geo Data Autocomplete API results.
     */
    class PlaceAutocomplete {
        public CharSequence placeId;
        public CharSequence description;
        PlaceAutocomplete(CharSequence placeId, CharSequence description) {
            this.placeId = placeId;
            this.description = description;
        }
        @Override
        public String toString() {
            return description.toString();
        }
    }
}

-MainActivity.java

public class MainActivity extends AppCompatActivity
        implements GoogleApiClient.OnConnectionFailedListener {
    /**
     * GoogleApiClient wraps our service connection to Google Play Services and provides access
     * to the user's sign in state as well as the Google's APIs.
     */
    private static final int GOOGLE_API_CLIENT_ID = 0;
    protected GoogleApiClient mGoogleApiClient;
    private PlaceAutocompleteAdapter mAdapter;
    private AutoCompleteTextView mAutocompleteView;
    private TextView mPlaceDetailsText;
    private TextView mPlaceDetailsAttribution;
    private Button mCurrentLocation;
    private static final String TAG = "MainActivity";
    private static final LatLngBounds BOUNDS_GREATER_SYDNEY = new LatLngBounds(
            new LatLng(-34.041458, 150.790100), new LatLng(-33.682247, 151.383362));
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Construct a GoogleApiClient for the {@link Places#GEO_DATA_API} using AutoManage
        // functionality, which automatically sets up the API client to handle Activity lifecycle
        // events. If your activity does not extend FragmentActivity, make sure to call connect()
        // and disconnect() explicitly.
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this, GOOGLE_API_CLIENT_ID /* clientId */, this)
                .addApi(Places.GEO_DATA_API)
                .addApi(Places.PLACE_DETECTION_API)
                .build();
        setContentView(R.layout.activity_main);
        // Retrieve the AutoCompleteTextView that will display Place suggestions.
        mAutocompleteView = (AutoCompleteTextView)
                findViewById(R.id.autocomplete_places);
        // Register a listener that receives callbacks when a suggestion has been selected
        mAutocompleteView.setOnItemClickListener(mAutocompleteClickListener);
        // Retrieve the TextViews that will display details and attributions of the selected place.
        mPlaceDetailsText = (TextView) findViewById(R.id.place_details);
        mPlaceDetailsAttribution = (TextView) findViewById(R.id.place_attribution);
        // CurrentLocation
        mCurrentLocation = (Button) findViewById(R.id.ll_current_location);
        mCurrentLocation.setOnClickListener(mOnClickListener);
        // Set up the adapter that will retrieve suggestions from the Places Geo Data API that cover
        // the entire world.
        mAdapter = new PlaceAutocompleteAdapter(this, android.R.layout.simple_list_item_1,
                mGoogleApiClient, BOUNDS_GREATER_SYDNEY, null);
        mAutocompleteView.setAdapter(mAdapter);
    }
    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            PendingResult<PlaceLikelihoodBuffer> result = Places.PlaceDetectionApi
                    .getCurrentPlace(mGoogleApiClient, null);
            result.setResultCallback(new ResultCallback<PlaceLikelihoodBuffer>() {
                @Override
                public void onResult(PlaceLikelihoodBuffer likelyPlaces) {
                    if (!likelyPlaces.getStatus().isSuccess()) {
                        // Request did not complete successfully
                        Log.e(TAG, "Place query did not complete. Error: " + likelyPlaces.getStatus().toString());
                        likelyPlaces.release();
                        return;
                    }
                    String placeName = String.format("%s", likelyPlaces.get(0).getPlace().getName());
                    String placeAttributuion = String.format("%s", likelyPlaces.get(0).getPlace().getAddress());
                    mPlaceDetailsText.setText(placeName);
                    mPlaceDetailsAttribution.setText(placeAttributuion);
                    likelyPlaces.release();
                }
            });
        }
    };
    /**
     * Listener that handles selections from suggestions from the AutoCompleteTextView that
     * displays Place suggestions.
     * Gets the place id of the selected item and issues a request to the Places Geo Data API
     * to retrieve more details about the place.
     *
     * @see com.google.android.gms.location.places.GeoDataApi#getPlaceById(com.google.android.gms.common.api.GoogleApiClient,
     * String...)
     */
    private AdapterView.OnItemClickListener mAutocompleteClickListener
            = new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            /*
             Retrieve the place ID of the selected item from the Adapter.
             The adapter stores each Place suggestion in a PlaceAutocomplete object from which we
             read the place ID.
              */
            final PlaceAutocompleteAdapter.PlaceAutocomplete item = mAdapter.getItem(position);
            final String placeId = String.valueOf(item.placeId);
            Log.i(TAG, "Autocomplete item selected: " + item.description);
            /*
             Issue a request to the Places Geo Data API to retrieve a Place object with additional
              details about the place.
              */
            PendingResult<PlaceBuffer> placeResult = Places.GeoDataApi
                    .getPlaceById(mGoogleApiClient, placeId);
            placeResult.setResultCallback(mUpdatePlaceDetailsCallback);
            Log.i(TAG, "Called getPlaceById to get Place details for " + item.placeId);
        }
    };
    
    /**
     * Callback for results from a Places Geo Data API query that shows the first place result in
     * the details view on screen.
     */
    private ResultCallback<PlaceBuffer> mUpdatePlaceDetailsCallback
            = new ResultCallback<PlaceBuffer>() {
        @Override
        public void onResult(PlaceBuffer places) {
            if (!places.getStatus().isSuccess()) {
                // Request did not complete successfully
                Log.e(TAG, "Place query did not complete. Error: " + places.getStatus().toString());
                places.release();
                return;
            }
            // Get the Place object from the buffer.
            final Place place = places.get(0);
            // Format details of the place for display and show it in a TextView.
            mPlaceDetailsText.setText(formatPlaceDetails(getResources(), place.getName(),
                    place.getId(), place.getAddress(), place.getPhoneNumber(),
                    place.getWebsiteUri()));
            // Display the third party attributions if set.
            final CharSequence thirdPartyAttribution = places.getAttributions();
            if (thirdPartyAttribution == null) {
                mPlaceDetailsAttribution.setVisibility(View.GONE);
            } else {
                mPlaceDetailsAttribution.setVisibility(View.VISIBLE);
                mPlaceDetailsAttribution.setText(Html.fromHtml(thirdPartyAttribution.toString()));
            }
            Log.i(TAG, "Place details received: " + place.getName());
            places.release();
        }
    };
    private static Spanned formatPlaceDetails(Resources res, CharSequence name, String id,
                                              CharSequence address, CharSequence phoneNumber, Uri websiteUri) {
        Log.e(TAG, res.getString(R.string.place_details, name, id, address, phoneNumber,
                websiteUri));
        return Html.fromHtml(res.getString(R.string.place_details, name, id, address, phoneNumber,
                websiteUri));
    }
    /**
     * Called when the Activity could not connect to Google Play services and the auto manager
     * could resolve the error automatically.
     * In this case the API is not available and notify the user.
     *
     * @param connectionResult can be inspected to determine the cause of the failure
     */
    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.e(TAG, "onConnectionFailed: ConnectionResult.getErrorCode() = "
                + connectionResult.getErrorCode());
        // TODO(Developer): Check error code and notify the user of error state and resolution.
        Toast.makeText(this,
                "Could not connect to Google API Client: Error " + connectionResult.getErrorCode(),
                Toast.LENGTH_SHORT).show();
        MainActivity.this.finish();
    }
}


最后

  • Demo 下载链接:https://github.com/tangqi92/MyGooglePlaces

  • 友情提醒:以上的功能,手机需要安装 Google Play services 才能正常使用,至于如何安装,就交给万能的 Google 吧:)

  • 如本文存在任何错误或遗漏,欢迎指正。

  • 我最爱交朋友了,所以欢迎在社交网络上互粉!!!


References & More