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

나만의 Current Place 선택 도구 만들기(자바) - Android

Google Maps Platform 및 Android용 Places SDK를 사용하여 현재 위치를 나타내는 장소 목록을 사용자에게 제시하는 방법을 알아봅니다.

bd07a9ad2cb27a06.png

사전 준비 사항

  • 기본 자바 기술

수행할 작업

  • Android 앱에 지도 추가
  • 위치 정보 액세스 권한을 사용하여 사용자의 위치 파악
  • 사용자의 현재 위치에 가까운 장소 가져오기
  • 현재 위치와 일치할 가능성이 높은 장소를 사용자에게 제시

빌드할 내용

이 Android 앱은 완전히 처음부터 빌드하는 것이지만 디버깅할 때 샘플 코드를 다운로드하여 비교해 볼 수 있습니다. GitHub에서 샘플 코드를 다운로드하거나, 명령줄 사용이 가능하도록 Git이 설정된 경우 다음을 입력합니다.

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

이 Codelab을 진행하는 동안 문제(코드 버그, 문법 오류, 불분명한 문구 등)가 발생하는 경우 Codelab 왼쪽 하단에 있는 Report a mistake 링크를 통해 문제를 신고해 주세요.

이 Codelab을 시작하기 전에 다음을 설정해야 합니다.

Android 스튜디오

https://developer.android.com/studio에서 Android 스튜디오를 다운로드합니다.

Android 스튜디오가 이미 있는 경우 Android Studio > Check for Updates...를 클릭하여 최신 버전인지 확인합니다.

1f36bae83b64e33.png

이 실습 자료는 Android 스튜디오 3.4를 사용하여 작성되었습니다.

Android SDK

Android 스튜디오에서 SDK Manager를 사용하여 원하는 SDK를 구성할 수 있습니다. 이 실습에서는 Android Q SDK를 사용합니다.

  1. Android 스튜디오 시작 화면에서 Configure > SDK Manager를 클릭합니다.

d3fa03c269ec231c.png

  1. 원하는 SDK 체크박스를 선택하고 Apply를 클릭합니다.

아직 SDK가 없으면 시스템에 SDK가 다운로드되기 시작합니다.

884e0aa1314f70d.png

Google Play 서비스

SDK Manager에서 Google Play 서비스도 설치해야 합니다.

  1. SDK Tools 탭을 클릭하고 Google Play services 체크박스를 선택합니다.

상태가 Update available로 표시되면 업데이트합니다.

ad6211fd78f3b629.png

앱을 실행하려면 본인의 기기를 연결하거나 Android Emulator를 사용합니다.

본인의 기기를 사용하는 경우 이 페이지 하단의 실제 기기 안내: Google Play 서비스 업데이트 섹션으로 건너뛰세요.

에뮬레이터 추가

  1. Android 스튜디오 시작 화면에서 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. 아이콘을 클릭하여 실행합니다.

기본 지도가 표시됩니다.

  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 services를 검색해서 엽니다.
  2. More Details를 클릭합니다.

Update 버튼을 사용할 수 있는 경우 클릭합니다.

ad16cdb975b5c3f7.png baf0379ef8a9c88c.png

  1. Android 스튜디오 시작 화면에서 Start a new Android Studio project를 선택합니다.
  2. Phone and Tablet 탭에서 Google Maps Activity를 선택합니다.

c9c80aa8211a8761.png

Configure your project 대화상자가 열립니다. 여기에서 앱 이름을 지정하고 도메인을 기반으로 패키지를 만듭니다.

다음은 com.google.codelab.currentplace 패키지에 해당하는 Current Place라는 앱의 설정입니다.

37f5b93b94ee118c.png

  1. 언어로 Java를 선택하고 Use androidx. artifacts*를 선택합니다.

나머지 설정은 기본값을 유지합니다.

  1. Finish를 클릭합니다.

Android에서 위치 정보 액세스 권한에 접근하려면 Google Play 서비스의 Google Location API and Activity Recognition API가 필요합니다. 이 API 및 다른 Google Play 서비스 API 추가에 관한 자세한 내용은 Google Play 서비스 설정을 참고하세요.

Android 스튜디오 프로젝트에는 일반적으로 두 개의 build.gradle 파일이 있습니다. 하나는 전체 프로젝트용이고 다른 하나는 앱용입니다. Android 뷰에 Android 스튜디오 프로젝트 탐색기가 있는 경우 두 파일 모두 Gradle Scripts 폴더에 표시됩니다. Google 서비스를 추가하려면 build.gradle (Module: app) 파일을 수정해야 합니다.

f3043429cf719c47.png

  1. dependencies 섹션에 두 행을 추가하여 Google 위치 서비스 및 Places API를 추가합니다(컨텍스트가 반영된 샘플 코드).

build.gradle(모듈: 앱)

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'
}

다음 사용 설정 단계를 진행하려면 Android용 Maps SDKPlaces 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의 Credentials 페이지에서 API 키를 생성합니다. 이 동영상 또는 이 문서의 단계를 따릅니다. Google Maps Platform으로 전송되는 모든 요청에는 API 키가 필요합니다.

방금 만든 API 키를 복사합니다. Android 스튜디오로 다시 전환하여 Android > app > res > value에서 google_maps_api.xml 파일을 찾습니다.

YOUR_KEY_HERE를 복사한 API 키로 바꿉니다.

aa576e551a7a1009.png

이제 앱이 구성되었습니다.

  1. 프로젝트 탐색기에서 Android > app > res > layout으로 이동하여 activity_maps.xml 파일을 엽니다.

4e0d986480c57efa.png

  1. 화면 오른쪽에 기본 UI가 열리는데, 하단의 탭을 통해 레이아웃의 디자인 또는 텍스트 편집기를 선택할 수 있습니다. 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. 프로젝트 탐색기에서 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. 프로젝트 탐색기에서 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. 프로젝트 탐색기에서 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. 프로젝트 탐색기에서 Android > app > res > values를 펼친 후 안에 있는 styles.xml 파일을 엽니다.
  2. <style> 태그에서 상위 속성을 "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. 프로젝트 탐색기에서 MapsActivity.java 파일을 찾습니다.

이 파일은 1단계에서 앱용으로 만든 패키지에 해당하는 폴더에 있습니다.

8b0fa27d417f5f55.png

  1. 파일을 열면 자바 코드 편집기로 이동하게 됩니다.

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 {

클래스 변수 추가

다음으로, 여러 클래스 메서드에 사용되는 다양한 클래스 변수를 선언합니다. 여기에는 UI 요소 및 상태 코드가 포함됩니다. 이러한 변수는 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 메서드 업데이트

UI 요소를 설정하고 Places API 클라이언트를 만드는 등 위치 서비스에 대한 런타임 사용자 권한을 처리하려면 onCreate 메서드를 업데이트해야 합니다.

작업 툴바, 뷰 설정, Places 클라이언트와 관련된 다음 코드 행을 기존 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);
    }

앱 바 메뉴 코드 추가

아래의 두 메서드는 앱 바 메뉴(단일 항목, Pick Place 아이콘 포함)를 추가하고 사용자의 아이콘 클릭을 처리합니다.

이 두 메서드를 파일의 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 스튜디오에서 Run 또는 Run 메뉴 > Run 'app'을 클릭합니다.

28bea91c68c36fb2.png

  1. 배포 대상을 선택하라는 메시지가 표시됩니다. 실행 중인 에뮬레이터가 이 목록에 표시됩니다. 에뮬레이터를 선택하면 Android 스튜디오가 앱을 에뮬레이터에 배포합니다.

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 호출 및 응답 처리

getCurrentPlaceLikelihoodsfindCurrentPlaceRequest를 생성하고 Places API findCurrentPlace 작업을 호출합니다. 작업이 성공하면 placeLikelihood 객체 목록이 포함된 findCurrentPlaceResponse가 반환됩니다. 각 항목에는 장소의 이름과 주소, 내가 해당 장소에 있을 확률(0부터 1까지의 double 값)을 비롯한 다양한 속성이 있습니다. 이 메서드는 placeLikelihoods에서 장소 세부정보 목록을 생성하여 응답을 처리합니다.

이 코드는 일치할 가능성이 높은 5개의 장소를 처리하는 동안 반복되면서 확률이 0보다 큰 장소를 목록에 추가하고 그러면 이 목록이 렌더링됩니다. 5개보다 많이 또는 적게 표시하려면 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 스튜디오에서 ⌘6을 클릭하여 Logcat의 로그에서 MapsActivity 태그가 지정된 문을 살펴보면 새 메서드가 올바르게 작동하는지 확인할 수 있습니다.
  • 권한을 부여한 경우 로그에 Latitude:에 대한 문과 기기의 감지된 위치를 보여주는 Longitude:에 대한 문이 포함됩니다. 에뮬레이터의 위치를 지정하기 위해 앞서 에뮬레이터의 확장 메뉴와 Google 지도를 사용한 경우 두 문에 해당 위치가 표시됩니다.
  • findCurrentPlace 호출이 성공하면 일치 가능성이 가장 높은 장소 5개의 이름과 위치가 출력된 5개의 문이 로그에 포함됩니다.

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();

지금까지 Current Place 선택 도구에 필요한 코드를 모두 설명했습니다.

장소 선택 테스트

  1. 앱을 다시 실행합니다.

이번에는 Pick Place를 탭하면 해당 위치 근처의 이름이 지정된 장소가 목록에 표시됩니다. 마우이의 이 위치와 가까운 장소로는 울룰라니 하와이 셰이브 아이스, 슈가 비치 제과점 등이 있습니다. 여러 장소가 이 위치의 좌표에 매우 가깝기 때문에 이 목록이 내가 있을 가능성이 높은 장소의 목록입니다.

  1. ListView에서 장소 이름을 클릭합니다.

지도에 추가된 마커가 표시됩니다.

  1. 마커를 탭합니다.

장소 세부정보를 볼 수 있습니다.

e52303cc0de6a513.png 864c74342fb52a01.png

다른 위치 테스트

에뮬레이터를 사용 중인데 위치를 변경하려는 경우 에뮬레이터의 확장 메뉴에서 위치 좌표를 업데이트해도 기기 위치가 자동으로 업데이트되지 않습니다.

이 문제를 해결하려면 아래 단계에 따라 네이티브 Google 지도 앱을 사용하여 에뮬레이터의 위치를 강제로 업데이트합니다.

  1. Google 지도를 엽니다.
  2. ... > Location을 탭하여 위도와 경도를 새 좌표로 변경한 다음 Send를 탭합니다.
  3. 예를 들어 위도 49.2768, 경도 -123.1142를 사용하여 위치를 캐나다 밴쿠버 시내로 설정할 수 있습니다.
  4. Google 지도가 새 좌표를 중심으로 다시 표시되는지 확인합니다. 가운데 재정렬을 요청하려면 Google 지도 앱에서 My Location 버튼을 탭해야 할 수 있습니다.
  5. Current Place 앱으로 돌아가 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개의 두 자리 16진수 시퀀스입니다.

앱을 출시할 준비가 되면 이 문서의 안내에 따라 출시 인증서를 가져옵니다.

API 키에 제한사항 추가

  1. Cloud Console에서 APIs & Services > Credentials로 이동합니다.

이 앱에 사용한 키는 API Keys 아래에 나열됩니다.

  1. 6454a04865d551e6.png 아이콘을 클릭하여 키 설정을 수정합니다.

316b052c621ee91c.png

  1. API 키 페이지에서 Key Restrictions를 설정한 후에 다음을 수행하여 Application restrictions를 설정합니다.
  2. Android apps를 선택하고 안내를 따릅니다.
  3. Add an item을 클릭합니다.
  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 Restrictions를 설정합니다.
  2. API 제한사항을 설정한 후 Restrict key를 선택합니다.
  3. Android용 Maps SDK 및 Places API를 선택합니다.
  4. Done을 클릭한 다음 Save를 클릭합니다.

현재 위치의 장소일 가능성이 높은 장소를 확인하고 사용자가 선택한 장소의 마커를 지도에 추가하는 간단한 앱을 만들었습니다.

자세히 알아보기