5. 네이버 지도 연동하기¶
3장에서 만들어 둔 메인 화면의 회색 상자를 기억하십니까? 그 자리에 "지도가 들어갈 자리"라고 적어 두었습니다. 이번 장에서 그 약속을 지킬 차례입니다. 진짜로 움직이는 네이버 지도를 띄우고, 지도 위에 마커를 꽂고, 내 위치까지 표시해 보겠습니다.
이번 장이 끝나면 버킷맵은 "지도 앱"이라고 부를 수 있는 모습이 됩니다.
지도 SDK 란?¶
여러분이 지도를 직접 그린다고 상상해 봅시다. 전국의 도로, 건물, 지하철 노선을 모두 그려야 하고, 손가락으로 밀면 부드럽게 움직이게 만들어야 하고, 확대하면 더 자세한 그림으로 바꿔치기해야 합니다. 혼자서는 몇 년이 걸려도 못 만듭니다.
그래서 지도 회사들은 자기네 지도를 앱에 통째로 넣을 수 있는 부품 상자를 나눠 줍니다. 이런 부품 상자를 SDK(Software Development Kit)라고 한다. 개발자는 부품 상자를 가져다 조립만 하면 되고, 지도 그림과 데이터는 지도 회사의 서버가 실시간으로 보내 준다.
안드로이드에서 쓸 수 있는 지도 SDK 는 여러 가지가 있다.
| 지도 SDK | 만든 곳 | 특징 |
|---|---|---|
| Google Maps SDK | 구글 | 전 세계 지도. 해외 지역이 상세하다 |
| 네이버 지도 SDK | 네이버 | 국내 도로·건물·대중교통 정보가 매우 정밀하다 |
| 카카오맵 SDK | 카카오 | 국내 지도. 카카오 서비스와 연동이 쉽다 |
버킷맵은 네이버 지도 SDK 를 사용합니다. 우리가 담을 장소 대부분이 국내이고, 국내 지도는 네이버가 가장 정밀하기 때문입니다.
GoogleMap 을 배웠다면
안드로이드 교재의 지도 단원은 보통 GoogleMap 으로 진행됩니다. 지도를 붙이는 회사만 다를 뿐, 지도 뷰를 화면에 넣고 → 카메라를 옮기고 → 마커를 꽂는 핵심 개념은 완전히 같습니다. 네이버 지도로 익혀 두면 GoogleMap 도 금방 다룰 수 있습니다.
지도 키 발급받기¶
부품 상자는 공짜로 열리지 않습니다. 네이버는 "누가 우리 지도를 쓰는지"를 알아야 하므로, 개발자마다 열쇠(Client ID) 를 발급해 줍니다. 앱이 지도를 요청할 때 이 열쇠를 함께 보내면, 네이버 서버가 확인하고 지도를 내려 준다.
[CH-1 아래 과정을 그대로 진행하시오.]¶
- 브라우저에서
https://www.ncloud.com(네이버 클라우드 플랫폼)에 접속해 회원 가입 후 로그인한다. - 상단의 콘솔 버튼을 눌러 콘솔로 들어간다.
- 왼쪽 메뉴에서 Services → Application Services → Maps 를 찾아 들어간다.
- Application 등록 버튼을 누른다.
- 애플리케이션 이름에
bucketmap처럼 알아볼 수 있는 이름을 적는다. - Application 서비스 선택에서 Dynamic Map 에 반드시 체크한다.
- Android 앱 패키지 이름 항목에
com.example.myapplication을 정확히 입력하고 등록한다. - 등록된 애플리케이션의 인증 정보를 열어 Client ID 값을 복사해 둔다.
콘솔 화면이 책과 다르게 보인다면
웹 콘솔 화면은 수시로 개편됩니다. 메뉴 위치가 다르면 네이버 지도 SDK 공식 시작 가이드(https://navermaps.github.io/android-map-sdk/guide-ko/1.html)의 최신 절차를 함께 확인하십시오.
[설명]
- Dynamic Map 체크를 빼먹으면 열쇠는 발급되지만 지도 서비스 사용 허가가 없는 열쇠가 된다. 앱 실행 시 인증 오류(429)가 난다.
- Android 앱 패키지 이름은 열쇠를 쓸 수 있는 앱을 제한하는 장치다. 1장에서 프로젝트 패키지명을
com.example.myapplication으로 만들었는데, 바로 여기 등록할 이름과 맞추기 위해서였다. 철자가 하나라도 다르면 인증 오류(401)가 난다. - Client ID 는 열쇠 그 자체다. 다른 사람에게 보여 주지 않는 것이 원칙이다.
열쇠를 안전하게 보관하기¶
복사한 Client ID 를 어디에 둘까요? 가장 쉬운 방법은 Java 코드에 직접 적는 것이지만, 이 방법은 쓰지 않습니다.
[CH-2 아래 내용을 local.properties 파일 끝에 추가하시오.]¶
Android Studio 왼쪽 파일 목록에서 Project 보기로 전환하면 프로젝트 루트에 local.properties 파일이 있다. 파일을 열고 맨 아래에 두 줄을 추가한다. (PLACES_API_KEY 는 6장에서 채운다 — 지금은 빈 값으로 둔다.)
NCP_KEY_ID=발급받은_클라이언트_ID
PLACES_API_KEY=
[설명]
- 코드에 열쇠를 직접 적으면, 코드를 친구에게 보내거나 GitHub 에 올리는 순간 열쇠도 함께 공개된다. 남이 내 열쇠로 지도를 쓰면 사용량이 내 몫으로 쌓인다.
local.properties는 "이 컴퓨터에서만 쓰는 값"을 담는 파일로, 버전 관리(Git)에서 자동으로 제외되도록 처음부터 설정되어 있다. 열쇠를 두기에 알맞은 자리다.=양옆에 공백이나 따옴표를 넣지 않는다.
[CH-3 아래 코드를 그대로 작성하고 실행하시오.]¶
이제 빌드 설정이 local.properties 의 값을 읽어 코드에서 쓸 수 있게 만들어 줍니다. app/build.gradle 파일을 열어 세 군데를 수정한다.
먼저 plugins { ... } 블록 바로 아래에 읽기 코드를 추가한다.
// API 키는 VCS 에 커밋되지 않는 local.properties 에서 읽어 온다
def localProps = new Properties()
def localPropsFile = rootProject.file('local.properties')
if (localPropsFile.exists()) {
localPropsFile.withInputStream { localProps.load(it) }
}
다음으로 defaultConfig { ... } 안의 versionName "1.0" 아래에 두 줄을 추가한다.
buildConfigField "String", "NCP_KEY_ID", "\"${localProps.getProperty('NCP_KEY_ID', '')}\""
buildConfigField "String", "PLACES_API_KEY", "\"${localProps.getProperty('PLACES_API_KEY', '')}\""
마지막으로 defaultConfig { ... } 블록이 끝난 다음 줄에 블록 하나를 추가한다.
buildFeatures {
buildConfig true
}
수정을 마치면 편집기 위에 나타나는 Sync Now 를 누른다.
실행 결과
BUILD SUCCESSFUL in 2s
[설명]
buildConfigField는 빌드할 때BuildConfig라는 Java 클래스를 자동으로 만들어 그 안에 값을 넣어 준다. 이후 코드에서BuildConfig.NCP_KEY_ID라고 쓰면 열쇠 값이 나온다.- 열쇠의 실제 값은
local.properties에만 있고, 코드에는BuildConfig.NCP_KEY_ID라는 이름만 등장한다. 코드를 통째로 공개해도 열쇠는 새지 않는다. buildFeatures { buildConfig true }는 "BuildConfig 클래스를 만들어 달라"는 스위치다.
부품 가게 등록과 SDK 내려받기¶
라이브러리(부품)는 인터넷의 저장소(repository) 라는 부품 가게에서 내려받습니다. 구글·마벤센트럴 같은 큰 가게는 프로젝트를 만들 때부터 등록되어 있지만, 네이버 지도 SDK 는 네이버의 전용 가게에만 있습니다. 가게 주소부터 등록해야 한다.
[CH-4 아래 코드를 그대로 작성하고 실행하시오.]¶
프로젝트 루트의 settings.gradle 을 열어 dependencyResolutionManagement 블록 안 repositories 를 다음처럼 만든다.
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
// 네이버 지도 SDK 는 네이버 전용 저장소에서 받는다 (5장)
maven { url = uri("https://repository.map.naver.com/archive/maven") }
}
}
[CH-5 아래 코드를 그대로 작성하고 실행하시오.]¶
app/build.gradle 의 dependencies { ... } 안, 4장에서 추가한 RecyclerView 아래에 두 줄을 추가하고 Sync Now 를 누른다.
// 네이버 지도 + 현위치 (5장)
implementation 'com.naver.maps:map-sdk:3.22.1'
implementation 'com.google.android.gms:play-services-location:21.0.1'
실행 결과
BUILD SUCCESSFUL in 41s
[설명]
com.naver.maps:map-sdk:3.22.1이 네이버 지도 부품 상자다. 버전 숫자까지 책과 똑같이 맞춘다. 버전이 다르면 책의 코드가 그대로 동작하지 않을 수 있다. 최신 버전 번호는 공식 가이드에서 확인할 수 있다.play-services-location은 구글이 제공하는 위치 부품이다. "지금 폰이 어디에 있는가"를 알아내는 데 쓰이고, 네이버 지도의 현위치 기능이 이 부품을 이용한다.- 첫 Sync 는 부품을 인터넷에서 내려받느라 시간이 걸린다.
위치 권한 선언하기¶
지도는 인터넷에서 내려오고, 내 위치는 민감한 개인정보입니다. 안드로이드는 앱이 이런 것을 쓰려면 미리 신고(선언)하고, 민감한 것은 사용자에게 허락까지 받도록 정해 두었다.
[CH-6 아래 코드를 그대로 작성하고 실행하시오.]¶
app/src/main/AndroidManifest.xml 의 <manifest ...> 여는 태그 바로 아래에 세 줄을 추가한다.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
[설명]
INTERNET— 지도 그림을 네이버 서버에서 받아 오려면 인터넷이 필요하다. 선언만 하면 되는 순한 권한이다.ACCESS_FINE_LOCATION— GPS 급의 정확한 위치. 민감한 권한이라 실행 중에 사용자 허락 창이 뜬다.ACCESS_COARSE_LOCATION— 기지국·와이파이 수준의 대략적 위치. 사용자가 "대략적인 위치만 허용"을 고를 수 있게 함께 선언한다.
드디어 지도 띄우기¶
준비가 끝났습니다. 회색 상자를 걷어 내고 진짜 지도를 넣습니다.
[CH-7 아래 코드를 그대로 작성하고 실행하시오.]¶
res/layout/activity_main.xml 에서 3장에 만든 map_container FrameLayout(안의 TextView 포함)을 통째로 지우고, 그 자리에 아래 조각을 넣는다. 파일 전체는 다음과 같아진다.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/map_fragment"
android:name="com.naver.maps.map.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_margin="16dp"
android:orientation="horizontal">
<Button
android:id="@+id/btn_find_place"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:text="@string/btn_find_place" />
<Button
android:id="@+id/btn_my_places"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_my_places" />
</LinearLayout>
</FrameLayout>
strings.xml 에서 이제 쓰지 않는 map_placeholder 문자열 한 줄은 지워도 된다.
[설명]
FragmentContainerView는 "다른 화면 조각(Fragment)을 끼워 넣는 액자"다.android:name에 적힌com.naver.maps.map.MapFragment가 네이버 SDK 가 만들어 둔 지도 조각이고, 액자에 자동으로 끼워진다.- 지도가 화면 전체를 덮고, 버튼들은 FrameLayout 의 겹치기 성질 덕분에 지도 위에 떠 있게 된다 — 3장에서 배운 구조가 그대로 살아 있다.
[CH-8 아래 코드를 그대로 작성하고 실행하시오.]¶
MainActivity.java 를 아래 내용으로 통째로 바꾼다.
package com.example.myapplication;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.naver.maps.geometry.LatLng;
import com.naver.maps.map.CameraUpdate;
import com.naver.maps.map.LocationTrackingMode;
import com.naver.maps.map.MapFragment;
import com.naver.maps.map.NaverMap;
import com.naver.maps.map.NaverMapSdk;
import com.naver.maps.map.OnMapReadyCallback;
import com.naver.maps.map.overlay.Marker;
import com.naver.maps.map.util.FusedLocationSource;
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1000;
private NaverMap naverMap;
private FusedLocationSource locationSource;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 지도를 그리기 전에 네이버 클라우드에서 발급받은 키를 SDK 에 알려 준다
NaverMapSdk.getInstance(this).setClient(
new NaverMapSdk.NcpKeyClient(BuildConfig.NCP_KEY_ID));
setContentView(R.layout.activity_main);
Button findButton = findViewById(R.id.btn_find_place);
Button myPlacesButton = findViewById(R.id.btn_my_places);
findButton.setOnClickListener(v -> {
Intent intent = new Intent(this, SearchActivity.class);
startActivity(intent);
});
myPlacesButton.setOnClickListener(v -> {
Intent intent = new Intent(this, MyPlacesActivity.class);
startActivity(intent);
});
locationSource = new FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE);
MapFragment mapFragment = (MapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map_fragment);
mapFragment.getMapAsync(this);
}
@Override
public void onMapReady(@NonNull NaverMap naverMap) {
this.naverMap = naverMap;
// 시작 위치: 서울 시청 (현위치 권한을 허용하면 내 위치로 이동한다)
naverMap.moveCamera(CameraUpdate.scrollTo(new LatLng(37.5665, 126.9780)));
// 연습용 마커 — 7장에서 저장된 장소들의 마커로 바뀐다
Marker marker = new Marker();
marker.setPosition(new LatLng(37.5796, 126.9770));
marker.setCaptionText("경복궁");
marker.setMap(naverMap);
naverMap.setLocationSource(locationSource);
naverMap.getUiSettings().setLocationButtonEnabled(true);
naverMap.setLocationTrackingMode(LocationTrackingMode.Follow);
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
if (locationSource.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
if (!locationSource.isActivated()) {
// 권한을 거부하면 위치 추적만 끈다 — 지도는 그대로 쓸 수 있다
naverMap.setLocationTrackingMode(LocationTrackingMode.None);
}
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
실행 결과

앱을 실행하면 진짜 지도가 그려지고, 그 위로 위치 권한을 묻는 창이 뜬다. 앱 사용 중에만 허용을 누른다.

허용하면 파란 점이 내 위치를 가리키고, 경복궁 자리에 연습용 마커가 꽂혀 있다. 지도를 손가락으로 끌고, 두 손가락으로 확대·축소해 보십시오.
[설명]
NaverMapSdk...setClient(new NaverMapSdk.NcpKeyClient(BuildConfig.NCP_KEY_ID))— 지도를 그리기 전에 열쇠를 SDK 에 건네는 코드다.setContentView()보다 먼저 와야 한다.getMapAsync(this)의 Async 는 "비동기"라는 뜻이다. 지도는 준비에 시간이 걸리므로, "준비되면 알려 줘" 하고 부탁만 해 두고 코드는 계속 진행한다. 준비가 끝나는 순간onMapReady()가 호출된다.implements OnMapReadyCallback— "지도가 준비되면 연락받는 쪽"이 되겠다는 선언이다. 그래서onMapReady()메서드를 반드시 만들어야 한다.LatLng는 위도(latitude)·경도(longitude) 한 쌍이고,CameraUpdate.scrollTo()는 지도 카메라를 그 좌표로 옮긴다. 지도는 세상 전체이고, 화면은 카메라가 비추는 부분이다.Marker는 지도 위에 꽂는 압정이다. 좌표를 정하고(setPosition), 이름표를 달고(setCaptionText),setMap(naverMap)하는 순간 지도에 나타난다.FusedLocationSource는 아까 추가한 구글 위치 부품과 네이버 지도를 이어 주는 다리다.LocationTrackingMode.Follow는 "내 위치를 따라다녀라"라는 모드로, 이 모드를 켜는 순간 SDK 가 알아서 권한 요청 창을 띄운다.onRequestPermissionsResult()는 사용자가 허용/거부를 고른 결과가 돌아오는 곳이다. 결과 처리를locationSource에 위임하고, 거부라면 위치 추적만 끈다.- 에뮬레이터는 진짜 GPS 가 없어서 가짜 위치를 쓴다. 기본값이 미국(구글 본사)이라 파란 점이 미국에 찍힐 수 있다. 에뮬레이터 오른쪽 도구에서 ⋮ (Extended Controls) → Location 을 열어 서울 좌표(위도 37.5665, 경도 126.9780)를 SET POINT 하면 된다.
잘 안 될 때¶
| 증상 | 원인 | 해결 |
|---|---|---|
| "인증에 실패하였습니다" 401 | 패키지명 불일치 또는 Client ID 오타 | NCP 콘솔의 Android 패키지명이 com.example.myapplication 인지, local.properties 의 값에 오타·공백이 없는지 확인 |
| 인증 오류 429 | Application 등록 때 Dynamic Map 미체크 | NCP 콘솔에서 해당 Application 수정 → Dynamic Map 체크 |
| 지도가 회색으로만 나온다 | 인터넷 권한 누락 또는 네트워크 문제 | CH-6 의 INTERNET 권한, 에뮬레이터의 인터넷 연결 확인 |
| 파란 점이 미국에 있다 | 에뮬레이터 기본 가짜 위치 | Extended Controls → Location 에서 서울 좌표 설정 |
[self Test 1] - 앱의 시작 위치를 우리 학교(또는 우리 집) 좌표로 바꾸고, 연습용 마커도 그 근처의 좋아하는 장소로 옮겨 이름표를 바꿔 보시오. (좌표는 네이버 지도 웹에서 원하는 지점을 우클릭하면 확인할 수 있다.)
[self Test 2] - CameraUpdate 에는 scrollTo() 말고 좌표와 확대 수준을 한 번에 정하는 scrollAndZoomTo(좌표, 줌레벨) 도 있다. 줌 레벨을 8, 12, 16 으로 바꿔 가며 화면이 어떻게 달라지는지 실행해 보시오.