개요
이 튜토리얼에서는 안드로이드 앱에서 사용자의 현재 위치를 가장 간단한 방법으로 가져오고, 나아가 해당 좌표를 실제 주소(도시 이름 포함)로 변환해 화면에 표시하는 방법을 단계별로 살펴봅니다.
위치 정보를 가져오기 위해 Google Play Services에서 제공하는 FusedLocationProviderClient(통합 위치 제공자)를 사용합니다. 이 API는 GPS, Wi-Fi, 셀룰러 네트워크 등 다양한 위치 소스를 자동으로 조합하여 배터리 효율적이면서도 정확도 높은 위치 데이터를 제공합니다. 또한 Geocoder를 활용해 위도·경도 좌표를 사람이 읽을 수 있는 주소로 변환하는 역지오코딩(Reverse Geocoding)을 수행합니다.
1단계 — 새 프로젝트 만들기
Android Studio를 실행하고 File → New Project 메뉴로 이동한 뒤, 빈 액티비티(Empty Activity) 템플릿을 선택하여 새 프로젝트를 생성합니다. 필요한 모든 세부 정보를 입력하고 프로젝트를 완료하세요.
2단계 — 레이아웃 구성 (activity_main.xml)
res/layout/activity_main.xml 파일에 아래 코드를 추가합니다. 화면 상단에는 안내 문구가, 중앙에는 현재 위치와 주소가 표시될 TextView가 배치됩니다.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_marginTop="20dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Get Current Location and City Name"
android:textAlignment="center"
android:layout_centerHorizontal="true"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"
android:layout_centerInParent="true"
android:textSize="16sp"
android:textStyle="bold"/>
</RelativeLayout>3단계 — Gradle 의존성 추가
위치 서비스를 사용하기 위해 앱 수준(app-level) build.gradle 파일의 dependencies 블록에 아래 의존성을 추가합니다.
implementation 'com.google.android.gms:play-services-location:17.0.0'
의존성을 추가한 후 반드시 Sync Now를 클릭해 Gradle 동기화를 진행하세요.
4단계 — MainActivity.java 작성
src/MainActivity.java에 아래 코드를 추가합니다. 이 코드는 위치 권한을 확인·요청하고, 위치 업데이트를 시작하며, 받아온 좌표로 주소 조회를 요청하는 전체 흐름을 담당합니다.
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Geocoder;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
public class MainActivity extends AppCompatActivity {
private FusedLocationProviderClient fusedLocationClient;
private static final int LOCATION_PERMISSION_REQUEST_CODE = 2;
private LocationAddressResultReceiver addressResultReceiver;
private TextView currentAddTv;
private Location currentLocation;
private LocationCallback locationCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addressResultReceiver = new LocationAddressResultReceiver(new Handler());
currentAddTv = findViewById(R.id.textView);
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
currentLocation = locationResult.getLocations().get(0);
getAddress();
}
};
startLocationUpdates();
}
@SuppressWarnings("MissingPermission")
private void startLocationUpdates() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new
String[]{Manifest.permission.ACCESS_FINE_LOCATION},
LOCATION_PERMISSION_REQUEST_CODE);
}
else {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(2000);
locationRequest.setFastestInterval(1000);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null);
}
}
@SuppressWarnings("MissingPermission")
private void getAddress() {
if (!Geocoder.isPresent()) {
Toast.makeText(MainActivity.this, "Can't find current address, ",
Toast.LENGTH_SHORT).show();
return;
}
Intent intent = new Intent(this, GetAddressIntentService.class);
intent.putExtra("add_receiver", addressResultReceiver);
intent.putExtra("add_location", currentLocation);
startService(intent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull
int[] grantResults) {
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startLocationUpdates();
}
else {
Toast.makeText(this, "Location permission not granted, " + "restart the app if you want the
feature", Toast.LENGTH_SHORT).show();
}
}
}
private class LocationAddressResultReceiver extends ResultReceiver {
LocationAddressResultReceiver(Handler handler) {
super(handler);
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == 0) {
Log.d("Address", "Location null retrying");
getAddress();
}
if (resultCode == 1) {
Toast.makeText(MainActivity.this, "Address not found, ", Toast.LENGTH_SHORT).show();
}
String currentAdd = resultData.getString("address_result");
showResults(currentAdd);
}
}
private void showResults(String currentAdd) {
currentAddTv.setText(currentAdd);
}
@Override
protected void onResume() {
super.onResume();
startLocationUpdates();
}
@Override
protected void onPause() {
super.onPause();
fusedLocationClient.removeLocationUpdates(locationCallback);
}
}주요 코드 설명
- startLocationUpdates(): 위치 권한이 부여되어 있는지 먼저 확인하고, 없다면 사용자에게 권한을 요청합니다. 권한이 있으면 2초 간격으로 고정밀도(PRIORITY_HIGH_ACCURACY) 위치 업데이트를 요청합니다.
- onResume() / onPause(): 앱이 화면에 보일 때만 위치 업데이트를 시작하고, 화면에서 벗어나면 즉시 해제하여 불필요한 배터리 소모를 막습니다.
- LocationAddressResultReceiver: IntentService에서 전달된 주소 결과를 받아 TextView에 표시하는 내부 클래스입니다.
5단계 — GetAddressIntentService.java 작성
새 Java 클래스 GetAddressIntentService.java를 생성하고 아래 코드를 추가합니다. 이 서비스는 백그라운드에서 Geocoder를 사용해 좌표를 상세 주소로 변환합니다.
package app.com.sample;
import android.app.IntentService;
import android.content.Intent;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.util.Log;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import androidx.annotation.Nullable;
public class GetAddressIntentService extends IntentService {
private static final String IDENTIFIER = "GetAddressIntentService";
private ResultReceiver addressResultReceiver;
public GetAddressIntentService() {
super(IDENTIFIER);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
String msg;
addressResultReceiver = Objects.requireNonNull(intent).getParcelableExtra("add_receiver");
if (addressResultReceiver == null) {
Log.e("GetAddressIntentService", "No receiver, not processing the request further");
return;
}
Location location = intent.getParcelableExtra("add_location");
if (location == null) {
msg = "No location, can't go further without location";
sendResultsToReceiver(0, msg);
return;
}
Geocoder geocoder = new Geocoder(this, Locale.getDefault());
List<Address> addresses = null;
try {
addresses = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);
}
catch (Exception ioException) {
Log.e("", "Error in getting address for the location");
}
if (addresses == null || addresses.size() == 0) {
msg = "No address found for the location";
sendResultsToReceiver(1, msg);
}
else {
Address address = addresses.get(0);
String addressDetails = address.getFeatureName() + "\n" + address.getThoroughfare() + "\n" +
"Locality: " + address.getLocality() + "\n" + "County: " + address.getSubAdminArea() + "\n" +
"State: " + address.getAdminArea() + "\n" + "Country: " + address.getCountryName() + "\n" +
"Postal Code: " + address.getPostalCode() + "\n";
sendResultsToReceiver(2, addressDetails);
}
}
private void sendResultsToReceiver(int resultCode, String message) {
Bundle bundle = new Bundle();
bundle.putString("address_result", message);
addressResultReceiver.send(resultCode, bundle);
}
}이 서비스는 건물 이름, 도로명, 지역(locality), 군/구(sub-admin area), 시/도(admin area), 국가, 우편번호까지 포함한 상세 주소를 구성해 결과를 액티비티로 되돌려줍니다.
6단계 — AndroidManifest.xml 설정
androidManifest.xml에 아래 코드를 추가합니다. 서비스 컴포넌트를 등록하고, 위치 관련 권한과 인터넷 권한을 선언하는 것이 핵심입니다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="app.com.sample">
<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">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".GetAddressIntentService" />
</application>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
</manifest>권한 설명
- ACCESS_FINE_LOCATION: GPS 기반의 정확한 위치 접근 권한
- ACCESS_COARSE_LOCATION: 네트워크 기반의 대략적인 위치 접근 권한
- INTERNET: Geocoder가 주소 조회를 위해 서버와 통신할 때 필요한 권한
앱 실행 및 결과 확인
이제 애플리케이션을 실행해 보겠습니다. 실제 안드로이드 스마트폰이 컴퓨터에 연결되어 있다고 가정합니다. Android Studio에서 프로젝트의 액티비티 파일 중 하나를 연 뒤, 툴바에서 Run
아이콘을 클릭하세요. 실행 옵션 목록에서 본인의 모바일 기기를 선택하면, 기기에 앱이 설치되고 실행됩니다.
앱이 시작되면 위치 권한 허용 여부를 묻는 대화상자가 나타납니다. 권한을 허용하면 잠시 후 화면 중앙에 현재 위치의 좌표와 함께 도시 이름, 상세 주소가 표시되는 것을 확인할 수 있습니다.
