![Android Google Map에 현재 위치 표시하기( FusedLocationProviderClient 사용)](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNyGmI%2FbtsyFnOVwPU%2FZGaI22SONNZQSSF1cgd8g0%2Fimg.png)
FusedLocationProviderClient를 사용하여 Google Map에 현재 위치를 표시하는 예제입니다.
2018. 10.2
2019. 7. 19 androidx 관련 내용 추가
2019. 8. 15 SupportMapFragment로 변경
2020. 9. 12 테스트 및 수정 ( Android Studio 4.0.1, Android 11.0 API 30 )
2021. 5. 11 테스트 및 수정 ( Android Studio 4.1.3, Android 11.0 API 30 )
2022. 5. 22 테스트 및 수정 ( Android Studio Chipmunk 2021.2.1, Android 12.0 API 31,
테스트 디바이스 Android 11 )
2022, 10. 12 5.22일 수정본이 블로그에 반영안된걸 발견하여 업데이트함
1. 구현된 내용 2. 실행 과정 설명 3. 프로그램 흐름 4. 소스 코드 5. 관련 포스팅 5.1. Google Maps Android API 사용 방법 및 예제 5.2. Places API Web Service를 사용하여 Android Google Map에 현재 위치 주변의 음식점 표시하기 5.3. GenyMotion 가상머신에 Google Apps설치하여 Google Maps Android API 테스트 하기 |
1. 구현된 내용
현재 구현된 내용은 다음과 같습니다.
- 현재 위치를 지도 상에 마커로 표시해줍니다.
- 디바이스의 위치 서비스(GPS)가 비활성화 되어 있는 경우 사용자가 활성화하도록 요구합니다.
- 디바이스의 운영체제 버전이 안드로이드 6.0이상일 경우에는 위치 관련 퍼미션을 런타임에 요구합니다.
- 현재 위치가 변경되면 계속 카메라가 이동하여 현재 위치를 중심으로 지도를 보여주게 됩니다.
2. 실행 과정 설명
처음 실행하면 지도의 초기위치를 서울로 이동시키고 위치 정보 사용을 위한 퍼미션 허가를 사용자에게 요청합니다.
Android 6.0 미만 운영체제를 사용하는 안드로이드 디바이스에서는 보이지 않습니다.
처음 실행하면 안드로이드 디바이스가 현재 위치를 찾는데 시간이 걸립니다.
적절한 조치를 안해주면 아프리카 대륙 옆에 있는 바다가 보입니다.
여러가지 방법이 있지만 저는 서울로 초기위치를 이동시켜주는 방법을 사용했습니다.
onMapReady() 메소드에서 서울로 카메라를 이동시켜줍니다.
그래서 앱에서 GPS가 사용 못하더라도 서울을 보여주게 됩니다.
다른 방법은 마지막에 안드로이드폰이 위치를 인식했던 정보를 가져오는 방법이 있습니다.
아래 링크를 참고하세요..
https://developer.android.com/training/location/retrieve-current.html
위치 서비스가 비활성화되어 있는 경우 활성화 여부를 사용자에게 물어봅니다.
설정을 선택했다면, 위치 정보 설정이 보입니다.
활성화시키고 백버튼을 눌러 앱으로 돌아오면
현재 위치에 코드에서 추가한 파란색 마커와 구글맵에서 현재 위치를 표시하는데 사용되는 파란색 동그라미가 표시됩니다.
마커를 터치하면 현재 위치의 주소와 좌표가 표시됩니다.
안드로이드 디바이스를 움직이면 현재 위치가 달라짐에 따라 파란색 마커의 위치가 이동하게 됩니다.
3. 프로그램 흐름
앱을 실행시키면 다음 순서대로 메소드들이 실행됩니다.
(런타임 퍼미션, GPS 활성화를 제외한 상태입니다.)
2018-10-02 11:35:40.478 15364-15364/? D/googlemap_example: onCreate 2018-10-02 11:35:40.481 15364-15364/? D/googlemap_example: onStart 2018-10-02 11:35:40.483 15364-15364/? D/googlemap_example: onStart : call mFusedLocationClient.requestLocationUpdates 2018-10-02 11:35:40.493 15364-15364/? D/googlemap_example: onMapReady : 2018-10-02 11:35:40.574 15364-15364/? D/googlemap_example: startLocationUpdates : call mFusedLocationClient.requestLocationUpdates |
이제 일정시간마다 현재 위치를 업데이트합니다.
2018-10-02 11:35:40.644 15364-15364/? D/googlemap_example: onLocationResult : 위도:37.5173504 경도:126.8945132 2018-10-02 11:35:43.604 15364-15364/? D/googlemap_example: onLocationResult : 위도:37.5174425 경도:126.8949426 2018-10-02 11:35:44.230 15364-15364/? D/googlemap_example: onLocationResult : 위도:37.5174422 경도:126.8949434 2018-10-02 11:35:45.244 15364-15364/? D/googlemap_example: onLocationResult : 위도:37.5174414 경도:126.8949452 2018-10-02 11:35:46.258 15364-15364/? D/googlemap_example: onLocationResult : 위도:37.5174395 경도:126.8949457 |
백버튼을 눌러서 앱종료시 다음 메소드가 실행됩니다.
2018-10-02 11:36:59.110 15364-15364/? D/googlemap_example: onStop : call stopLocationUpdates |
4. 소스 코드
다음 포스팅을 기반으로 수정합니다. 중복되는 내용은 빠져있습니다.
Google Maps SDK for Android 사용방법 및 예제 https://webnautes.tistory.com/2050 |
1. 매네페스트 파일 AndroidManifest.xml의 <manifest> 태그 하위요소로 <uses-permission> 태그를 사용하여 위치정보 접근을 위한 퍼미션을 추가해줍니다.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
2. buidle.gradle를 수정합니다. Snackbar를 사용하기 위해 com.google.android.material:material를 추가해야 합니다.
추가한 후, Sync Now를 클릭합니다.
implementation 'com.google.android.material:material:1.4.0-alpha02'
3. Snackbar를 사용하기 위해 레이아웃에 ID를 추가해야 합니다.
android:id="@+id/layout_main"
4. MainActivity.java를 다음 내용으로 변경합니다.
import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.location.Address; import android.location.Geocoder; import android.location.Location; import android.location.LocationManager; import android.os.Bundle; import android.os.Looper; import android.util.Log; import android.view.View; import android.view.WindowManager; 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; import com.google.android.gms.location.LocationSettingsRequest; import com.google.android.gms.maps.CameraUpdate; 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.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.material.snackbar.Snackbar; import java.io.IOException; import java.util.List; import java.util.Locale; public class MainActivity extends AppCompatActivity implements OnMapReadyCallback, ActivityCompat.OnRequestPermissionsResultCallback{ private GoogleMap mMap; private Marker currentMarker = null; private static final String TAG = "googlemap_example"; private static final int GPS_ENABLE_REQUEST_CODE = 2001; private static final int UPDATE_INTERVAL_MS = 1000; // 1초 private static final int FASTEST_UPDATE_INTERVAL_MS = 500; // 0.5초 // onRequestPermissionsResult에서 수신된 결과에서 ActivityCompat.requestPermissions를 사용한 퍼미션 요청을 구별하기 위해 사용됩니다. private static final int PERMISSIONS_REQUEST_CODE = 100; boolean needRequest = false; // 앱을 실행하기 위해 필요한 퍼미션을 정의합니다. String[] REQUIRED_PERMISSIONS = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}; // 외부 저장소 Location mCurrentLocatiion; LatLng currentPosition; private FusedLocationProviderClient mFusedLocationClient; private LocationRequest locationRequest; private Location location; private View mLayout; // Snackbar 사용하기 위해서는 View가 필요합니다. // (참고로 Toast에서는 Context가 필요했습니다.) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(R.layout.activity_main); mLayout = findViewById(R.id.layout_main); locationRequest = new LocationRequest() .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) .setInterval(UPDATE_INTERVAL_MS) .setFastestInterval(FASTEST_UPDATE_INTERVAL_MS); LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder(); builder.addLocationRequest(locationRequest); mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this); SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this); } @Override public void onMapReady(final GoogleMap googleMap) { Log.d(TAG, "onMapReady :"); mMap = googleMap; //런타임 퍼미션 요청 대화상자나 GPS 활성 요청 대화상자 보이기전에 //지도의 초기위치를 서울로 이동 setDefaultLocation(); //런타임 퍼미션 처리 // 1. 위치 퍼미션을 가지고 있는지 체크합니다. int hasFineLocationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION); int hasCoarseLocationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION); if (hasFineLocationPermission == PackageManager.PERMISSION_GRANTED && hasCoarseLocationPermission == PackageManager.PERMISSION_GRANTED ) { // 2. 이미 퍼미션을 가지고 있다면 // ( 안드로이드 6.0 이하 버전은 런타임 퍼미션이 필요없기 때문에 이미 허용된 걸로 인식합니다.) startLocationUpdates(); // 3. 위치 업데이트 시작 }else { //2. 퍼미션 요청을 허용한 적이 없다면 퍼미션 요청이 필요합니다. 2가지 경우(3-1, 4-1)가 있습니다. // 3-1. 사용자가 퍼미션 거부를 한 적이 있는 경우에는 if (ActivityCompat.shouldShowRequestPermissionRationale(this, REQUIRED_PERMISSIONS[0])) { // 3-2. 요청을 진행하기 전에 사용자가에게 퍼미션이 필요한 이유를 설명해줄 필요가 있습니다. Snackbar.make(mLayout, "이 앱을 실행하려면 위치 접근 권한이 필요합니다.", Snackbar.LENGTH_INDEFINITE).setAction("확인", new View.OnClickListener() { @Override public void onClick(View view) { // 3-3. 사용자게에 퍼미션 요청을 합니다. 요청 결과는 onRequestPermissionResult에서 수신됩니다. ActivityCompat.requestPermissions( MainActivity.this, REQUIRED_PERMISSIONS, PERMISSIONS_REQUEST_CODE); } }).show(); } else { // 4-1. 사용자가 퍼미션 거부를 한 적이 없는 경우에는 퍼미션 요청을 바로 합니다. // 요청 결과는 onRequestPermissionResult에서 수신됩니다. ActivityCompat.requestPermissions( this, REQUIRED_PERMISSIONS, PERMISSIONS_REQUEST_CODE); } } mMap.getUiSettings().setMyLocationButtonEnabled(true); // 현재 오동작을 해서 주석처리 //mMap.animateCamera(CameraUpdateFactory.zoomTo(15)); mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() { @Override public void onMapClick(LatLng latLng) { Log.d( TAG, "onMapClick :"); } }); } LocationCallback locationCallback = new LocationCallback() { @Override public void onLocationResult(LocationResult locationResult) { super.onLocationResult(locationResult); List<Location> locationList = locationResult.getLocations(); if (locationList.size() > 0) { location = locationList.get(locationList.size() - 1); //location = locationList.get(0); currentPosition = new LatLng(location.getLatitude(), location.getLongitude()); String markerTitle = getCurrentAddress(currentPosition); String markerSnippet = "위도:" + String.valueOf(location.getLatitude()) + " 경도:" + String.valueOf(location.getLongitude()); Log.d(TAG, "onLocationResult : " + markerSnippet); //현재 위치에 마커 생성하고 이동 setCurrentLocation(location, markerTitle, markerSnippet); mCurrentLocatiion = location; } } }; private void startLocationUpdates() { if (!checkLocationServicesStatus()) { Log.d(TAG, "startLocationUpdates : call showDialogForLocationServiceSetting"); showDialogForLocationServiceSetting(); }else { int hasFineLocationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION); int hasCoarseLocationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION); if (hasFineLocationPermission != PackageManager.PERMISSION_GRANTED || hasCoarseLocationPermission != PackageManager.PERMISSION_GRANTED ) { Log.d(TAG, "startLocationUpdates : 퍼미션 안가지고 있음"); return; } Log.d(TAG, "startLocationUpdates : call mFusedLocationClient.requestLocationUpdates"); mFusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper()); if (checkPermission()) mMap.setMyLocationEnabled(true); } } @Override protected void onStart() { super.onStart(); Log.d(TAG, "onStart"); if (checkPermission()) { Log.d(TAG, "onStart : call mFusedLocationClient.requestLocationUpdates"); mFusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null); if (mMap!=null) mMap.setMyLocationEnabled(true); } } @Override protected void onStop() { super.onStop(); if (mFusedLocationClient != null) { Log.d(TAG, "onStop : call stopLocationUpdates"); mFusedLocationClient.removeLocationUpdates(locationCallback); } } public String getCurrentAddress(LatLng latlng) { //지오코더... GPS를 주소로 변환 Geocoder geocoder = new Geocoder(this, Locale.getDefault()); List<Address> addresses; try { addresses = geocoder.getFromLocation( latlng.latitude, latlng.longitude, 1); } catch (IOException ioException) { //네트워크 문제 Toast.makeText(this, "지오코더 서비스 사용불가", Toast.LENGTH_LONG).show(); return "지오코더 서비스 사용불가"; } catch (IllegalArgumentException illegalArgumentException) { Toast.makeText(this, "잘못된 GPS 좌표", Toast.LENGTH_LONG).show(); return "잘못된 GPS 좌표"; } if (addresses == null || addresses.size() == 0) { Toast.makeText(this, "주소 미발견", Toast.LENGTH_LONG).show(); return "주소 미발견"; } else { Address address = addresses.get(0); return address.getAddressLine(0).toString(); } } public boolean checkLocationServicesStatus() { LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); } public void setCurrentLocation(Location location, String markerTitle, String markerSnippet) { if (currentMarker != null) currentMarker.remove(); LatLng currentLatLng = new LatLng(location.getLatitude(), location.getLongitude()); MarkerOptions markerOptions = new MarkerOptions(); markerOptions.position(currentLatLng); markerOptions.title(markerTitle); markerOptions.snippet(markerSnippet); markerOptions.draggable(true); currentMarker = mMap.addMarker(markerOptions); CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLng(currentLatLng); mMap.moveCamera(cameraUpdate); } public void setDefaultLocation() { //디폴트 위치, Seoul LatLng DEFAULT_LOCATION = new LatLng(37.56, 126.97); String markerTitle = "위치정보 가져올 수 없음"; String markerSnippet = "위치 퍼미션과 GPS 활성 요부 확인하세요"; if (currentMarker != null) currentMarker.remove(); MarkerOptions markerOptions = new MarkerOptions(); markerOptions.position(DEFAULT_LOCATION); markerOptions.title(markerTitle); markerOptions.snippet(markerSnippet); markerOptions.draggable(true); markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)); currentMarker = mMap.addMarker(markerOptions); CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(DEFAULT_LOCATION, 15); mMap.moveCamera(cameraUpdate); } //여기부터는 런타임 퍼미션 처리을 위한 메소드들 private boolean checkPermission() { int hasFineLocationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION); int hasCoarseLocationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION); if (hasFineLocationPermission == PackageManager.PERMISSION_GRANTED && hasCoarseLocationPermission == PackageManager.PERMISSION_GRANTED ) { return true; } return false; } /* * ActivityCompat.requestPermissions를 사용한 퍼미션 요청의 결과를 리턴받는 메소드입니다. */ @Override public void onRequestPermissionsResult(int permsRequestCode, @NonNull String[] permissions, @NonNull int[] grandResults) { if ( permsRequestCode == PERMISSIONS_REQUEST_CODE && grandResults.length == REQUIRED_PERMISSIONS.length) { // 요청 코드가 PERMISSIONS_REQUEST_CODE 이고, 요청한 퍼미션 개수만큼 수신되었다면 boolean check_result = true; // 모든 퍼미션을 허용했는지 체크합니다. for (int result : grandResults) { if (result != PackageManager.PERMISSION_GRANTED) { check_result = false; break; } } if ( check_result ) { // 퍼미션을 허용했다면 위치 업데이트를 시작합니다. startLocationUpdates(); } else { // 거부한 퍼미션이 있다면 앱을 사용할 수 없는 이유를 설명해주고 앱을 종료합니다.2 가지 경우가 있습니다. if (ActivityCompat.shouldShowRequestPermissionRationale(this, REQUIRED_PERMISSIONS[0]) || ActivityCompat.shouldShowRequestPermissionRationale(this, REQUIRED_PERMISSIONS[1])) { // 사용자가 거부만 선택한 경우에는 앱을 다시 실행하여 허용을 선택하면 앱을 사용할 수 있습니다. Snackbar.make(mLayout, "퍼미션이 거부되었습니다. 앱을 다시 실행하여 퍼미션을 허용해주세요. ", Snackbar.LENGTH_INDEFINITE).setAction("확인", new View.OnClickListener() { @Override public void onClick(View view) { finish(); } }).show(); }else { // "다시 묻지 않음"을 사용자가 체크하고 거부를 선택한 경우에는 설정(앱 정보)에서 퍼미션을 허용해야 앱을 사용할 수 있습니다. Snackbar.make(mLayout, "퍼미션이 거부되었습니다. 설정(앱 정보)에서 퍼미션을 허용해야 합니다. ", Snackbar.LENGTH_INDEFINITE).setAction("확인", new View.OnClickListener() { @Override public void onClick(View view) { finish(); } }).show(); } } } } //여기부터는 GPS 활성화를 위한 메소드들 private void showDialogForLocationServiceSetting() { AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setTitle("위치 서비스 비활성화"); builder.setMessage("앱을 사용하기 위해서는 위치 서비스가 필요합니다.\n" + "위치 설정을 수정하실래요?"); builder.setCancelable(true); builder.setPositiveButton("설정", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { Intent callGPSSettingIntent = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS); startActivityForResult(callGPSSettingIntent, GPS_ENABLE_REQUEST_CODE); } }); builder.setNegativeButton("취소", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); builder.create().show(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case GPS_ENABLE_REQUEST_CODE: //사용자가 GPS 활성 시켰는지 검사 if (checkLocationServicesStatus()) { if (checkLocationServicesStatus()) { Log.d(TAG, "onActivityResult : GPS 활성화 되있음"); needRequest = true; return; } } break; } } } |
'Android > Google Map & 카카오 지도' 카테고리의 다른 글
Places API Web Service를 사용하여 Android Google Map에 현재 위치 주변의 음식점 표시하기 (11) | 2023.10.17 |
---|---|
Google Maps SDK for Android 사용방법 및 예제 (3) | 2023.10.17 |
Places SDK for Android 사용해보기 (3) | 2021.01.09 |
Android Google Map에서 목적지 Marker와 이동 중인 현재 위치 간의 거리 계산해서 보여주기 (87) | 2019.11.25 |
Android 예제 - 현재 위치 주소 가져오기(Get current location without google map) (66) | 2019.11.21 |