Статьи

Сервисы Google Play: Использование Places API

В марте 2015 года Google выпустила API Google Places для Android в составе сервисов Google Play. Этот API позволяет разработчикам получать доступ к большому количеству информации из Google, чтобы предоставить пользователям опыт, адаптированный к их текущему местоположению, используя имена и информацию о местах, а не набор координат.

В этом руководстве вы узнаете, как представить своим пользователям компонент «Выбор мест», использовать API «Места», чтобы угадать текущее место пользователя, выполнить поиск места по его идентификатору и позволить пользователю ввести текстовое поле, чтобы представить их. с прогнозирующими результатами.

Если у вас еще нет открытого ключа API Android, вам нужно будет создать открытый ключ Google API для приложений Android. Вы можете сделать это, посетив Консоль разработчиков Google . Инструкции по созданию ключа на основе вашего сертификата подписи и имени пакета доступны в документации Google и выходят за рамки этой статьи.

Когда вы создали ключ, найдите API Адресов и установите его включенным. Некоторые вызовы API Адресов ограничены количеством запросов, которые можно отправить за 24 часа. На момент написания, учетная запись без платежного профиля может отправлять до 1000 запросов, а учетная запись с платежным профилем может отправлять 150 000 запросов. Если вам требуется больше, вы можете отправить запрос на увеличение этого лимита, как описано в документации по лимитам использования .

Когда ключ API готов к использованию, настало время начать работу над демонстрационным проектом. Создайте проект в Android Studio и установите минимальную поддерживаемую версию SDK как минимум 9. Это минимальное требование для использования Google Play Services.

Как только Android Studio создаст шаблонный проект Hello World , откройте файл build.gradle и в узле dependencies добавьте требуемую зависимость Play Services 7.0. Это самая последняя на момент написания статьи, но вы можете проверить последнюю версию, проверив документацию Google .

1
2
3
4
5
dependencies {
    compile fileTree(dir: ‘libs’, include: [‘*.jar’])
    compile ‘com.android.support:appcompat-v7:22.0.0’
    compile ‘com.google.android.gms:play-services:7.0.0’
}

Затем откройте AndroidManifest.xml , добавьте необходимые разрешения для проекта и укажите, что приложение требует OpenGL версии 2.

1
2
3
4
5
6
<uses-permission android:name=»com.google.android.providers.gsf.permission.READ_GSERVICES»/>
<uses-permission android:name=»android.permission.ACCESS_FINE_LOCATION» />
 
<uses-feature
    android:glEsVersion=»0x00020000″
    android:required=»true»/>

Последнее, что вам нужно сделать в манифесте, — это добавить два <meta-data> чтобы установить версию gms и ключ API для приложения в <application> .

1
2
3
4
5
6
<meta-data
    android:name=»com.google.android.geo.API_KEY»
    android:value=»@string/google_api_key» />
<meta-data
    android:name=»com.google.android.gms.version»
    android:value=»@integer/google_play_services_version» />

Когда вы закончите с манифестом, вы готовы начать писать код. Поскольку это компонент Play Services, вам нужно будет инициализировать свой GoogleApiClient и подключать / отключать его в течение жизненного цикла вашей Activity . Мы делаем это в onCreate , onStart и onStop класса Activity .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
protected void onCreate( Bundle savedInstanceState ) {
    //—Snippet
    mGoogleApiClient = new GoogleApiClient
            .Builder( this )
            .enableAutoManage( this, 0, this )
            .addApi( Places.GEO_DATA_API )
            .addApi( Places.PLACE_DETECTION_API )
            .addConnectionCallbacks( this )
            .addOnConnectionFailedListener( this )
            .build();
}
 
@Override
protected void onStart() {
    super.onStart();
    if( mGoogleApiClient != null )
        mGoogleApiClient.connect();
}
 
@Override
protected void onStop() {
    if( mGoogleApiClient != null && mGoogleApiClient.isConnected() ) {
        mGoogleApiClient.disconnect();
    }
    super.onStop();
}

Виджет «Выбор места» — это компонент пользовательского интерфейса, предоставляемый Play Services, который позволяет пользователю видеть карту своего окружения. Компонент включает в себя список ближайших мест, которые могут быть использованы вашим приложением. Используя этот компонент, вы сможете следовать стандартному дизайну, с которым ваши пользователи будут знать, как взаимодействовать, и при этом сэкономить время на разработку.

Place Picker Widget

Чтобы использовать средство выбора места, вам нужно создать намерение и прослушать результат действия, чтобы получить место, выбранное пользователем. Следующий метод показывает, как вы будете запускать это действие.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private void displayPlacePicker() {
    if( mGoogleApiClient == null || !mGoogleApiClient.isConnected() )
        return;
 
    PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
     
    try {
        startActivityForResult( builder.build( getApplicationContext() ), PLACE_PICKER_REQUEST );
    } catch ( GooglePlayServicesRepairableException e ) {
            Log.d( «PlacesAPI Demo», «GooglePlayServicesRepairableException thrown» );
    } catch ( GooglePlayServicesNotAvailableException e ) {
        Log.d( «PlacesAPI Demo», «GooglePlayServicesNotAvailableException thrown» );
    }
}

PlacePicker.IntentBuilder используется для создания Intent который будет использоваться для запуска Place Picker. У него также есть метод setLatLngBounds , который позволяет вам разместить географическую границу от юго-западного угла до северо-восточного угла для управления областью поиска.

Intent может быть построено с использованием метода build из PlacePicker.IntentBuilder и запущено с использованием метода startActivityForResult из вашей Activity . Следует отметить, что при использовании метода build есть возможность генерировать исключение GooglePlayServicesRepairableException или GooglePlayServicesNotAvailableException , поэтому их следует проверять на использование стандартного блока try / catch и обрабатывать их корректно, если они происходят.

Если пользователь выбирает местоположение из списка выбора мест, этот объект Place упаковывается в Intent и отправляется обратно в вызывающую Activity . Используя метод PlacePicker.getPlace , вы можете извлечь данные Place из возвращенного Intent .

1
2
3
4
5
protected void onActivityResult( int requestCode, int resultCode, Intent data ) {
    if( requestCode == PLACE_PICKER_REQUEST && resultCode == RESULT_OK ) {
        displayPlace( PlacePicker.getPlace( data, this ) );
    }
}

После извлечения объекта Place его можно рассматривать как объект модели для отображения или использования в приложении.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
private void displayPlace( Place place ) {
    if( place == null )
        return;
 
    String content = «»;
    if( !TextUtils.isEmpty( place.getName() ) ) {
        content += «Name: » + place.getName() + «\n»;
    }
    if( !TextUtils.isEmpty( place.getAddress() ) ) {
        content += «Address: » + place.getAddress() + «\n»;
    }
    if( !TextUtils.isEmpty( place.getPhoneNumber() ) ) {
        content += «Phone: » + place.getPhoneNumber();
    }
 
    mTextView.setText( content );
}
API мест в действии

Еще одна интересная особенность API Places заключается в том, что вы можете использовать его, чтобы угадать, находится ли пользователь в списке. API также обеспечит вероятность того, что вы сможете принимать обоснованные решения о том, как ваше приложение должно взаимодействовать с пользователем. Следует отметить, что это одна из функций API, которая требует запроса на выделенные вам средства.

Чтобы определить место пользователя, вам потребуется использовать метод Places.PlacesDetectionApi.getCurrentPlace чтобы создать PendingIntent который возвращается с объектом PlaceLikelihoodBuffer . Используя ResultCallBack , вы можете взять первое и, скорее всего, место из буфера и использовать его в своем приложении.

Если вашему приложению нужна дополнительная информация, вы можете извлечь другие элементы PlaceLikelihood из буфера, просматривая его. Вероятность того, что это место находится там, где в данный момент находится пользователь, передается обратно в каждый объект PlaceLikelihood в виде значения с плавающей запятой от 0,0 до 1,0 , 1,0 почти гарантированное совпадение. Не забудьте вызвать release для PlaceLikelihoodBuffer чтобы избежать утечек памяти.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
private void guessCurrentPlace() {
    PendingResult<PlaceLikelihoodBuffer> result = Places.PlaceDetectionApi.getCurrentPlace( mGoogleApiClient, null );
    result.setResultCallback( new ResultCallback<PlaceLikelihoodBuffer>() {
        @Override
        public void onResult( PlaceLikelihoodBuffer likelyPlaces ) {
 
            PlaceLikelihood placeLikelihood = likelyPlaces.get( 0 );
            String content = «»;
            if( placeLikelihood != null && placeLikelihood.getPlace() != null && !TextUtils.isEmpty( placeLikelihood.getPlace().getName() ) )
                content = «Most likely place: » + placeLikelihood.getPlace().getName() + «\n»;
            if( placeLikelihood != null )
                content += «Percent change of being there: » + (int) ( placeLikelihood.getLikelihood() * 100 ) + «%»;
            mTextView.setText( content );
 
            likelyPlaces.release();
        }
    });
}
Спрашивая API Мест для вероятности текущего места

Следующая и самая сложная тема, которую мы рассмотрим в этом руководстве, — это прогнозирование и отображение мест для пользователя при вводе поискового запроса. Опять же, этот вызов API также учитывает ограничения использования API. Однако, это неоценимо для того, чтобы сделать ваше приложение более полезным.

Прогнозирование мест

В этой части руководства вы будете использовать AutoCompleteTextView и пользовательский адаптер в приложении, чтобы ввести название места для прогнозов. Практически вся работа выполняется в адаптере. Однако нам потребуется передать ссылку на GoogleApiClient адаптеру для доступа к API.

Это можно сделать с помощью стандартного обратного вызова onConnected , и мы можем удалить экземпляр клиента в onStop где mAdapter — это экземпляр нашего пользовательского класса Adapter , AutoCompleteAdapter .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Override
protected void onStop() {
    if( mGoogleApiClient != null && mGoogleApiClient.isConnected() ) {
        mAdapter.setGoogleApiClient( null );
        mGoogleApiClient.disconnect();
    }
    super.onStop();
}
 
@Override
public void onConnected( Bundle bundle ) {
    if( mAdapter != null )
        mAdapter.setGoogleApiClient( mGoogleApiClient );
}

Чтобы инициировать вызов API всякий раз, когда пользователь вводит новую букву в AutoCompleteTextView , необходимо переопределить метод getFilter объекта ArrayAdapter . Этот метод запускается всякий раз, когда пользователь изменяет содержимое представления, связанного с адаптером. Позволяет изменить содержимое адаптера AutoCompleteTextView . В следующем примере constraints — это содержимое представления.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public Filter getFilter() {
    return new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
 
            if( mGoogleApiClient == null || !mGoogleApiClient.isConnected() ) {
                Toast.makeText( getContext(), «Not connected», Toast.LENGTH_SHORT ).show();
                return null;
            }
 
            clear();
 
            displayPredictiveResults( constraint.toString() );
 
            return null;
        }
 
        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            notifyDataSetChanged();
        }
    };
}

Метод displayPredictiveResults — это место, где происходит фактическое взаимодействие с API. Есть несколько различных объектов, которые можно сделать, чтобы настроить ваши прогнозы.

Первый — это объект LatLngBounds который создает квадратную границу от юго-западной точки до северо-восточной точки для локализации запроса. Если вместо инициализированного объекта LatLngBounds передается значение null , то географические ограничения на запрос не накладываются.

1
LatLngBounds bounds = new LatLngBounds( new LatLng( 39.906374, -105.122337 ), new LatLng( 39.949552, -105.068779 ) );

Второй объект, который можно создать для настройки запроса, — это фильтр для запроса API. Фильтр для вызова Places AutoCompletePredictions представляет собой список объектов Integer представляющих фильтры различных типов. На данный момент только один тип фильтра может быть применен к запросу. Приемлемые значения можно найти в документации . Если список Integer пуст или пропущен ноль, возвращаются все типы результатов.

Когда вы будете готовы сделать запрос, вы можете использовать метод Places.GeoDataApi.getAutocompletePredictions чтобы вернуть PendingIntent , который может быть связан с ResultCallback для отображения возвращаемой информации.

Важно отметить, что пользовательский объект, представляющий объекты AutoCompletePrediction из буфера, используется для хранения данных в ArrayAdapter . В противном случае исключение IllegalArgumentsException будет выдано сразу после освобождения буфера, что крайне важно для предотвращения утечки памяти.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private void displayPredictiveResults( String query )
{
    //Southwest corner to Northeast corner.
    LatLngBounds bounds = new LatLngBounds( new LatLng( 39.906374, -105.122337 ), new LatLng( 39.949552, -105.068779 ) );
 
    //Filter: https://developers.google.com/places/supported_types#table3
    List<Integer> filterTypes = new ArrayList<Integer>();
    filterTypes.add( Place.TYPE_ESTABLISHMENT );
 
    Places.GeoDataApi.getAutocompletePredictions( mGoogleApiClient, query, bounds, AutocompleteFilter.create( filterTypes ) )
        .setResultCallback (
            new ResultCallback<AutocompletePredictionBuffer>() {
                @Override
                public void onResult( AutocompletePredictionBuffer buffer ) {
 
                    if( buffer == null )
                        return;
 
                    if( buffer.getStatus().isSuccess() ) {
                        for( AutocompletePrediction prediction : buffer ) {
                            //Add as a new item to avoid IllegalArgumentsException when buffer is released
                            add( new AutoCompletePlace( prediction.getPlaceId(), prediction.getDescription() ) );
                        }
                    }
 
                    //Prevent memory leak by releasing buffer
                    buffer.release();
                }
            }, 60, TimeUnit.SECONDS );
}

Содержимое в AutoCompleteAdapter отображается с использованием макета android.R.layout.simple_list_item_1 и стандартного шаблона getView в getView .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Override
public View getView( int position, View convertView, ViewGroup parent ) {
    ViewHolder holder;
 
    if( convertView == null ) {
        holder = new ViewHolder();
        convertView = LayoutInflater.from( getContext() ).inflate( android.R.layout.simple_list_item_1, parent, false );
        holder.text = (TextView) convertView.findViewById( android.R.id.text1 );
        convertView.setTag( holder );
    } else {
        holder = (ViewHolder) convertView.getTag();
    }
 
    holder.text.setText( getItem( position ).getDescription() );
 
    return convertView;
}

Когда элемент выбирается из этого списка, идентификатор выбранного Place передается в onItemClickedListener и ищется для отображения.

Последняя часть этого руководства посвящена поиску объекта Place на основе его идентификатора. Это работает аналогично другим вызовам API, создав PendingIntent и взаимодействуя с возвращенным буфером для получения места. Как и другие объекты буфера, с которыми вы работали, PlaceBuffer должен вызывать release чтобы избежать любых утечек памяти.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private void findPlaceById( String id ) {
    if( TextUtils.isEmpty( id ) || mGoogleApiClient == null || !mGoogleApiClient.isConnected() )
        return;
 
   Places.GeoDataApi.getPlaceById( mGoogleApiClient, id ) .setResultCallback( new ResultCallback<PlaceBuffer>() {
       @Override
       public void onResult(PlaceBuffer places) {
           if( places.getStatus().isSuccess() ) {
               Place place = places.get( 0 );
               displayPlace( place );
               mPredictTextView.setText( «» );
               mAdapter.clear();
           }
 
           //Release the PlaceBuffer to prevent a memory leak
           places.release();
       }
   } );
}

API Places — это мощный инструмент для информирования ваших приложений о местоположении пользователя и предоставления им контекстной информации. В этом руководстве вы узнали, как использовать компонент «Выбор места», угадать место пользователя, представить ему прогнозирующие результаты при поиске и найти место на основе заданного идентификатора. В дополнение к темам, рассмотренным здесь, также можно представить новые места в Google, чтобы помочь расширить информацию, к которой имеет доступ API.