위치정보의 관리자를 활용하여 위치정보를 가져올 수 있습니다.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
tv = findViewById(R.id.tv);
List<String> providers = locationManager.getAllProviders();
StringBuffer buffer = new StringBuffer();
for(String provider : providers){
buffer.append(provider+", ");
}
tv.setText(buffer.toString());
}
다른 앱에서 취득했던 위치정보를 가져오는 방법입니다. 단, 마지막에 취득했던 위치정보이기 때문에 언제 취득된 정보인지는 알 수 없습니다. 그래서 가장 신뢰도가 낮은 방법입니다.
네트워크를 통해 위치정보를 가져오는 방법입니다.
권장되는 방법입니다. 실내에서는 network 로 실외에서는 GPS 를 사용하게 되며, 자동으로 처리해줍니다.
위치 정보의 정확성이 가장 높은 방법입니다.
버튼을 누르면 내 위치를 얻어와 보는 테스트를 해봅시다.
protected void onCreate(Bundle savedInstanceState) {
... 중략
findViewById(R.id.btn).setOnClickListener(view -> clickBtn());
int checkPermission = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
if (checkPermission == PackageManager.PERMISSION_DENIED) {
launcher.launch(Manifest.permission.ACCESS_FINE_LOCATION);
}
}
ActivityResultLauncher<String> launcher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback<Boolean>() {
@Override
public void onActivityResult(Boolean result) {
if (result) Toast.makeText(MainActivity.this, "퍼미션 허용", Toast.LENGTH_SHORT).show();
else Toast.makeText(MainActivity.this, "퍼미션 불가", Toast.LENGTH_SHORT).show();
}
});
void clickBtn() {
Location location = null;
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
if (locationManager.isProviderEnabled("fused")) {
location = locationManager.getLastKnownLocation("fused");
}else if( locationManager.isProviderEnabled("gps") ){
location = locationManager.getLastKnownLocation("gps");
}else if( locationManager.isProviderEnabled("network") ){
location = locationManager.getLastKnownLocation("network");
}
if(location == null){
tv2.setText("위치를 찾을수 없습니다.");
} else {
double latitude = location.getLatitude();
double longitude = location.getLongitude();
tv2.setText(latitude + " , " + longitude);
}
}
위치 정보 제공을 받기 위해서는 퍼미션이 필요합니다. 그래서 Manifest 파일에 퍼미션을 추가해줍시다.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
위쪽 퍼미션은 GPS 사용을 위한 퍼미션이며, 아래쪽 퍼미션은 network 사용을 위한 퍼미션 입니다. 추가로, GPS 사용을 위한 퍼미션은 동적 퍼미션으로 처리해주어야 하므로, 동적 퍼미션 다이얼로그를 띄워주러 가봅시다.
int checkPermission = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
checkSelfPermission
을 이용하여 퍼미션의 허용 상태를 checkPermission
에 넣어줍시다. 허용이라면 0, 아니라면 -1 을 리턴합니다. 또한 ACCESS_FINE_LOCATION 에 대한 동적퍼미션만 설정하면 ACCESS_COARSE_LOCATION 는 자동으로 허용됩니다.
ActivityResultLauncher<String> launcher
퍼미션 요청 및 결과를 받아주는 런처를 만들어줍시다. RequestPermission()
계약을 처리해주고, 퍼미션이 허용된다면 허용되었다는 토스트를, 허용되지 않았다면 불가라는 토스트를 띄워서 테스트합시다.
checkPermission == PackageManager.PERMISSION_DENIED
퍼미션의 결과가 PERMISSION_DENIED
라면 퍼미션이 허용되지 않았다는 의미이므로, 런처에게 퍼미션 허용 요청을 사용자에게 받아올 수 있도록 요청합시다.
여기까지 했으면, 동적 퍼미션은 완료했습니다. 그러면 사용자에게는 위치 기반 서비스를 사용할것이냐 요청하는 다이얼로그가 뜨게 됩니다. 자 그러면 버튼을 눌렀을 때, 위치 정보를 받아오는 코드를 짜봅시다. 단, 우리가 퍼미션을 허용해준곳은 onCreate()
메소드 이며, 버튼을 눌렀을 때, 퍼미션이 허용되었음을 체크하는 코드가 추가로 필요합니다.
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
if 문의 조건으로는 ACCESS_FINE_LOCATION
와ACCESS_COARSE_LOCATION
가 둘다 모두 허용되었다면 위치기반 서비스를 시작할 수 있으며, 둘 중 하나라도 허용되지 않는다면 return 으로 메소드를 종료시켜주어야 합니다.
만일 둘 다 허용이 되었다면 이제 위치 정보를 받아와야 합니다. 단, 앞서 이야기 했듯 위치정보를 받아올 수 있는 서비스에는 4가지가 있었습니다. 그 중 3가지, fused
, gps
, network
를 분기문으로 처리해주어야 합니다.
if (locationManager.isProviderEnabled("fused")) {
location = locationManager.getLastKnownLocation("fused");
}else if( locationManager.isProviderEnabled("gps") ){
location = locationManager.getLastKnownLocation("gps");
}else if( locationManager.isProviderEnabled("network") ){
location = locationManager.getLastKnownLocation("network");
}
어떠한 위치기반 서비스를 사용하느냐에 따라, location 객체에 들어갈 Provider 가 달라집니다.
자 그러면, 여기까지 왔다면, location 객체 안에는 어떠한 위치정보가 들어있을 겁니다. 이 위치정보를 위도와 경도로 얻어와봅시다.
if(location == null){
tv2.setText("위치를 찾을수 없습니다.");
} else {
double latitude = location.getLatitude();
double longitude = location.getLongitude();
tv2.setText(latitude + " , " + longitude);
}
위도와 경도를 location 객체에게 요청하고 받아와서, 텍스트뷰에 뿌려주었습니다. 이런식으로 LocationManager 를 이용하면 위치의 정보를 받아올 수 있습니다.
void clickBtn2(){
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
if(locationManager.isProviderEnabled("fused")){
locationManager.requestLocationUpdates("fused",5000, 2, listener );
} else if(locationManager.isProviderEnabled("gps")){
locationManager.requestLocationUpdates("gps",5000, 2, listener );
} else if(locationManager.isProviderEnabled("network")){
locationManager.requestLocationUpdates("network",5000, 2, listener );
}
}
LocationListener listener = new LocationListener() {
@Override
public void onLocationChanged(@NonNull Location location) {
double latitude = location.getLatitude();
double longitude = location.getLongitude();
tv3.setText(latitude + ", " + longitude);
}
};
void clickBtn3(){
locationManager.removeUpdates(listener);
}
퍼미션을 허용하는것은 앞선 테스트와 동일합니다. 다만, 앞선 테스트와는 좀 다르게 내 위치의 정보를 업데이트를 해주어야 합니다. requestLocationUpdates()
를 이용하면 위치정보를 업데이트 할 수 있습니다.
locationManager.requestLocationUpdates("fused",5000, 2, listener );
LocationListener listener = new LocationListener() {
@Override
public void onLocationChanged(@NonNull Location location) {
double latitude = location.getLatitude();
double longitude = location.getLongitude();
tv3.setText(latitude + ", " + longitude);
}
};
리스너를 만들어 변경된 위치 정보를 받아올 수 있습니다. 각각의 위도와 경도 정보를 변수에 담아 텍스트뷰에 뿌려줄 수 있습니다.
Google 지도앱에 사용되고 있는 위치정보 제공자 최적화 라이브러리 입니다. 라이브러리이기 떄문에 play-services-location
을 추가해주어야 합니다.
public class MainActivity extends AppCompatActivity {
FusedLocationProviderClient providerClient;
TextView tv;
LocationCallback locationCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv);
findViewById(R.id.btn).setOnClickListener(view -> clickBtn());
}
void clickBtn(){
int checkPermission = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
if(checkPermission == PackageManager.PERMISSION_DENIED){
launcher.launch(Manifest.permission.ACCESS_FINE_LOCATION);
return;
}
providerClient = LocationServices.getFusedLocationProviderClient(this);
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(5000);
providerClient.requestLocationUpdates(locationRequest,locationCallback, Looper.getMainLooper());
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(@NonNull LocationResult locationResult) {
super.onLocationResult(locationResult);
Location location = locationResult.getLastLocation();
Double latitude = location.getLatitude();
Double longitude = location.getLongitude();
tv.setText(latitude +", " + longitude);
}
};
}
@Override
protected void onPause() {
super.onPause();
if(providerClient != null){
providerClient.removeLocationUpdates(locationCallback);
}
}
ActivityResultLauncher<String> launcher = registerForActivityResult(new ActivityResultContracts.RequestPermission(),
result -> {
if(result) Toast.makeText(MainActivity.this, "퍼미션 허용됨", Toast.LENGTH_SHORT).show();
else Toast.makeText(MainActivity.this, "퍼미션 거부됨", Toast.LENGTH_SHORT).show();
});
}
FusedLocationProviderClient providerClient;
play-services-location
라이브러리를 사용하기 위한 참조변수 입니다. Fused API 또한 위치정보를 받아오는 API 이므로, 동적 퍼미션이 필수입니다. 동적 퍼미션을 받는 코드는 앞선 테스트와 동일하기 때문에 넘어가겠습니다.
providerClient = LocationServices.getFusedLocationProviderClient(this);
FusedLocationProviderClient
의 객체를 소환합시다.
LocationRequest locationRequest = LocationRequest.create();
위치정보의 최적화를 위해서는 기준이 필요합니다. 정확한 위치정보가 우선일수도, 배터리의 효율최적화가 우선일수도 있습니다.locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
이나 locationRequest.setInterval(5000);
와 같이 그 기준을 설정해줄 수 있습니다.
providerClient.requestLocationUpdates(locationRequest,locationCallback, Looper.getMainLooper());
requestLocationUpdates
을 이용하여 위치정보를 갱신할 수 있습니다. 첫번째 파라미터는 앞서 만들었던 LocationRequest
의 객체가, 두번째 파라미터로는 LocationCallback
의 리스너가 와야 합니다.
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(@NonNull LocationResult locationResult) {
super.onLocationResult(locationResult);
Location location = locationResult.getLastLocation();
double latitude = location.getLatitude();
double longitude = location.getLongitude();
tv.setText(latitude +", " + longitude);
}
};
위치정보가 변경되면 onLocationResult
콜백메서드로 위치정보를 받아옵니다. 메소드 내부에서 위치정보를 받아와 위도와 경도로 받아오는 작업을 해줄 수 있습니다.
@Override
protected void onPause() {
super.onPause();
if(providerClient != null){
providerClient.removeLocationUpdates(locationCallback);
}
}
화면이 없어졌을때, 위치정보를 받아오는 작업을 멈춰야하기 때문에 onPause()
를 이용하여 위치정보 업데이트를 중단해줍시다. removeLocationUpdates()
의 파라미터로 LocationCallback
의 리스너를 전달해주면 됩니다.
Fused API 가 편리한 점은, Location Manager 와는 다르게 어떠한 Provider 를 사용하는지에 대한 분기문이 필요없다는 점입니다. Fused API 가 알아서 network 를 쓸지, gps 를 쓸지 결정하기 때문입니다.
추가적으로, 앱이 종료되어있더라도 위치정보를 받아오고 싶을수도 있겠죠. 그럴때는 아래와 같은 퍼미션이 필요합니다. 일단 퍼미션이 필요하다는 사실만 알고 넘어갑시다.
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
지오코딩의 작업은 Google 지도 서버를 이용합니다. 그렇기 때문에 GeoCoder 를 사용하기 위해서 인터넷과 관련한 퍼미션을 하나 허용해줍시다. 정적퍼미션이기 때문에 아래 코드 한줄만 메니페스트 파일에 추가해줍시다.
<uses-permission android:name="android.permission.INTERNET"/>
첫번째 버튼을 누르면, 지오코딩을 통해 입력된 주소를 좌표로 반환하며, 두번째 버튼은 역 지오코딩을 통해 입력된 좌표를 주소로, 세번째 버튼을 누르면 첫번째 버튼을 눌러 입력된 주소를 좌표로 바꾸고, 그 좌표를 기반으로 맵을 띄워주는 코드를 짜봅시다. 즉, 세번째 버튼을 눌러 맵을 띄우려면 주소를 입력하고 첫번째 버튼을 누른 뒤 세번째 버튼을 눌러주어야 합니다. 자 그럼 코드를 봅시다.
void clickBtn(){
String addr = et.getText().toString();
Geocoder geocoder = new Geocoder(this, Locale.KOREA);
try {
List<Address> addresses = geocoder.getFromLocationName(addr,3);
StringBuffer buffer = new StringBuffer();
for(Address address : addresses){
buffer.append(address.getLatitude() + ", " + address.getLongitude() + "\n");
}
latitude = addresses.get(0).getLatitude();
longitude = addresses.get(0).getLongitude();
new AlertDialog.Builder(this).setMessage(buffer.toString()).create().show();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
첫번째 버튼을 눌렀을 때의 코드입니다.
String addr = et.getText().toString();
EditText 를 통해 입력받은 주소를 좌표로 반환해주기 위해 String 문자열변수에 먼저 담아둡시다.
Geocoder geocoder = new Geocoder(this, Locale.KOREA);
Geocoder 객체를 하나 만들어줍시다. 파라미터로는 Context 객체하나와, 어느지역인지를 전달합니다.
List<Address> addresses = geocoder.getFromLocationName(addr,3);
getFromLocationName()
의 파라미터로 입력받은 주솟값을 전달하고, 그 주솟값에 해당하는 좌표를 3개까지만 리턴합니다. 그리고 리턴값은 Address 를 제네릭타입으로 가지는 List 로 반환합니다.
StringBuffer buffer = new StringBuffer();
좌표값을 넣어주기 위해 버퍼를 만들어줍니다.
buffer.append(address.getLatitude() + ", " + address.getLongitude() + "\n");
버퍼에 주소의 위도와 경도를 append 해줍니다.
latitude = addresses.get(0).getLatitude();
와 longitude = addresses.get(0).getLongitude();
은 세번째 버튼을 눌렀을 때 사용할 좌표값을 미리 설정해놓았습니다.
new AlertDialog.Builder(this).setMessage(buffer.toString()).create().show();
다이얼로그를 통해 만든 좌표값을 화면에 띄워줍니다.
void clickBtn2(){
double latitude = Double.parseDouble(etLat.getText().toString());
double longitude = Double.parseDouble(etLong.getText().toString());
Geocoder geocoder = new Geocoder(this,Locale.KOREA);
try {
List<Address> addresses = geocoder.getFromLocation(latitude,longitude,3);
StringBuffer buffer = new StringBuffer();
for(Address address : addresses){
buffer.append(address.getCountryName() +"\n");
buffer.append(address.getCountryCode() +"\n");
buffer.append(address.getPostalCode() +"\n");
buffer.append(address.getAddressLine(0) +"\n");
buffer.append(address.getAddressLine(1) +"\n");
buffer.append(address.getAddressLine(2) +"\n");
buffer.append("========================\n");
}
new AlertDialog.Builder(this).setMessage(buffer.toString()).create().show();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
두번째 버튼을 눌렀을 때의 코드입니다.
List<Address> addresses = geocoder.getFromLocation(latitude,longitude,3);
getFromLocation()
를 이용하여 좌표를 주고 주소값을 받아왔습니다. 역시 반환값은 Address 를 제네릭 타입으로 갖는 List 입니다. void clickBtn3(){
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse("geo:"+latitude+","+longitude);
intent.setData(uri);
startActivity(intent);
}
세번째 버튼을 눌렀을 때의 코드입니다. 맵을 띄워줘야 하기 때문에 Intent
객체를 활용했습니다. 인텐트에 setDate()
를 이용하여 좌표값을 넣어준 뒤, startActivity()
로 맵을 실행시켜 주었습니다.
앱 안에 지도를 보여줄 수 있는 API 입니다. Google 이나 Naver 에서도 Map API 를 제공하나, 여러가지 제약사항들이 있어, 일단 이번 테스트에서는 Kakao Map API 를 활용하여 테스트를 진행해봅시다.
Kakao Map API 의 경우 AVD 에서 동작을 확인해볼수 없습니다. 실디바이스 혹은 Mac OS 의 M1,M2 chip AVD 에서만 동작한다는걸 참고합시다. 또한 이번 테스트는 어떠한 자바의 문법이 중요한게 아닙니다. 이 Kakao Map API 는 안드로이드 고유의 기능이 아니라, 카카오라는 회사에서 제공하는 API 입니다. 그렇기 때문에 카카오에서 제공하는 개발 튜토리얼에 친숙해지는걸 목표로 합시다. 아래쪽에는 Google, Naver, Kakao 의 Map API 사이트의 링크를 남겨놓았습니다.
Google Map API 사이트
Naver Map API 사이트
Kakao Map API 사이트
카카오 Map API 의 라이브러리를 추가하는 방법이나, 네이티브 앱 키 발급, API 사용법, Permission 등은 사이트에 모두 수록되어 있으니 보고 그대로 따라하면 됩니다.
다만, 라이브러리를 추가하고 앱과 연결시켜주어야 하는데 그 부분은 빠져 있어 그 부분만 보고, 플랫폼 설정과 키 해시를 받는법에 대해서만 알아봅시다.
카카오 사이트에서 라이브러리를 다운받고 내 앱에 추가를 시켜줬다고 라이브러리를 사용할 수 있는건 아닙니다. 이 라이브러리를 등록을 시켜줘야하는데요, 예전에 circleImageView 이나 Glide 등을 사용했을 때 사용했었던 라이브러리 등록방법을 사용해주면 됩니다.
안드로이드 스튜디오의 File
탭에 들어가면 Project Structure
메뉴가 있습니다. 들어가서 왼편 메뉴에 Dependencies
탭으로 이동하고, app
으로 가면 디펜던시를 추가할 수 있는 창이 뜹니다. +
를 눌러보면 라이브러리 디펜던시와 JAR/AAR 디펜던시를 추가할 수 있다고 합니다. 우리는 JAR 를 추가해야 하므로 두번째를 클릭합시다.
그러면 위 그림과 같은 창이 뜹니다. 그럼 여기에서 jar 파일이 있는 경로를 입력해주면 됩니다. 그러면 이제 라이브러리가 모두 등록되었으므로 카카오의 Map API 를 사용할 수 있습니다.
카카오의 개발자 페이지로 가서, 내 애플리케이션으로 들어가보면 카카오 로그인을 통해 내 앱을 등록할 수 있습니다. 그리고 플랫폼 또한 설정이 가능합니다. 우리는 안드로이드에서 Map API 를 사용할 예정이므로, 안드로이드로 플랫폼을 설정해주면 됩니다. 또한 여기에서 네이티브 앱 키를 받아올 수 있습니다. 이 네이티브 앱 키를 메니페스트 파일의 <meta-data>
에 설정해주면 됩니다.
이제 플랫폼 설정을 봅시다. 플랫폼 등록을 하기 위해 우리가 사용할 앱의 패키지명과, 키 해시를 주어야 합니다. 패키지명은 Gradle 파일에 보면 나와있으니 참고합시다. 키 해시값은 네이티브 키 값을 주면 앱 내부에 키 해시값이 설정됩니다. 그래서 로그값을 이용하여 키 해시값을 알아와서, 카카오 개발자 페이지에 등록해주면 됩니다.
String keyHash = Utility.getKeyHash(this);
Log.i("keyHash",keyHash);
위 코드를 작성하고, Logcat 창에서 keyHash 를 검색하면, 키 해시값을 받아올 수 있습니다. 받아온 키 해시값을 플랫폼 등록시 넣어주면 끝입니다.