한 걸음 두 걸음

안드로이드 시스템 프로그래밍 #03 ] 위치 데이터 활용하기 본문

FrontEnd/mobile system programming

안드로이드 시스템 프로그래밍 #03 ] 위치 데이터 활용하기

언제나 변함없이 2019. 3. 19. 11:37
반응형

위치 데이터 얻는 방법

  1. GPS 이용
    GPS 위성으로부터 수신한 신호를 기반으로 위치를 계산한다.
    단점은 실외에서만 사용가능하며 배터리 소모가 심하다는 것이 있다.

  2. 전화 기지국 이용(셀룰러)
    여러 기지국에서 오는 전파의 시간 차이나 세기 등을 이용하여 위치를 계산한다.
    때문에 실내 위치도 잡아낼 수 있지만 오차범위가 수백미터까지 날 수 있다..

  3. WiFi AP 이용
    실내 위치 추적용으로 사용하기 용이하며, AP의 위치 및 특정 AP로부터 수신한 신호의 세기 등으로 계산하여 위치를 구한다.

android 위치데이터 제공 API


안드로이드에서 제공하는 위치 데이터 제공 API 최신버전이 나왔으니 이를 활용하는 것이 좋다(Google Location Services API)

참고 URL : https://developer.android.com/training/location/index.html?hl=ko


하지만 오늘은 depressed될지라도 이전에 있던
LocationManager(Android framework location APIs)를 활용하자~

Class

• LocationManager
• LocationProvider
• Location
• Criteria

Interface

• LocationListener

물론 LocationProvider의 데이터는 GPS가 제공해준 것이다.
Criteria 기준 설정( 받고자하는 위치 데이터의 정확도는 ~이상으로 설정하는 등)

LocationManager 클래스

이용 가능한 위치 제공자(LocationProvider) 리스트, GPS 상태 정보와 같은 위치 시
스템의 현재 상태 정보를 제공하거나 기기에 최근에 기록된(캐시된) 위치 정보를 제
공한다.

LocationManager manager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);

이런식으로 시스템에게 위치데이터를 받겠다고 호출하여 사용합니다.
https://developer.android.com/reference/android/location/LocationManager.html?hl=ko

LacationProvider Class

안드로이드에서 위치 정보를 제공하는 다양한 공급원
(GPS : LocationManager.GPS_PROVIDER /
전화기지국 : LocationManager.NETWORK_PROVIDER /
WiFi AP : LocationManager.PASSIVE_PROVIDER)에 대해 추상화한 것
정보수집방법은 구체적으로 몰라도 가공된 데이터로 반환해주기 때문에 가져다 쓰시면 됩니다.

Lacation Calss

LocationProvider에서 앱에 제공하는 실제 위치 데이터(위도, 경도, 고도, 속도, timestamp)를 캡슐화 한 것이다. 여기서 LocationProvider가 위의 데이터를 항상 모두 제공하는 것이 아니므로 hasAltitude(), hasSpeed(), hasAccuracy()등의 함수로 확인하여 사용하는 것이 좋다.

Criteria Class

원하는 특징을 가진 위치 제공자 목록과 위치 제공자의 특성 기준 등을 지정하여 데이터를 받을 수 있다.
예시 )
accuracy: 위치 제공자의 전반적인 정확도 정도
• 종류 : Criteria.ACCURACY_FINE / Criteria.ACCURACY_COARSE

LocationManager manager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_COARSE);
List<String> enabledProviders = manager.getProviders(criteria, true);

그 외의 종류
• altitudeRequired: 위치 제공자가 고도 정보를 제공할 필요가 있는지 여부
• bearingRequired: 위치 제공자가 방향(이동 방향)을 제공할 필요가 있는지 여부
• bearingAccuracy: 요구되는 방향 정보의 정확도
• Criteria.ACCURACY_HIGH / Criteria.ACCURACY_LOW
• costAllowed: 위치 제공자가 비용이 드는 것을 허용하는지 여부
• horizontalAccuracy: 요구되는 위도 및 경도의 정확도
• Criteria.ACCURACY_HIGH / Criteria.ACCURACY_LOW / Criteria.ACCURACY_MEDIUM
• powerRequirement: 위치 제공자가 요구하는 배터리 소모량
• Criteria.POWER_LOW / Criteria.POWER_MEDIUM / Criteria.POWER_HIGH

LocationListener interface

위치 서비스의 상태가 변할 때 호출되는 콜백 메소드를 포함
구현해야 하는 메소드
• abstract void onLocationChanged(Location location)
• abstract void onProviderDisabled(String provider)
• abstract void onProviderEnabled(String provider)
• abstract void onStatusChanged(String provider, int status, Bundle extras)

onLocationChanged(Location location)메소드
위치 데이터를 받고자 할 때 구현해야 하는 메소드로, 앱에서 사용할 새로운 위치 데이터가 준비되었을 때 호출된다. 이 메소드 안에 Location 객체에 담긴 데이터를 이용하여 필요한 작업을 수행하도록 코드를 작성한다.

예시 위도, 경도, Provider 정보를 표시

@Override
public void onLocationChanged(Location location) {
latitudeValue.setText(String.valueOf(location.getLatitude()));
longitudeValue.setText(String.valueOf(location.getLongitude()));
providerValue.setText(String.valueOf(location.getProvider()));
}

onStatusChanged(String provider, int status, Bundle extras)메소드는
위치 제공자의 이용 가능 상태의 변화가 발생했을 때 호출되는 메소드로, 사용자의 설정 변경에 의한 것이 아닙니다. 사용자가 GPS를 이용하도록 설정했음에도 GPS 신호가 수신이 안 된다거나 등의 이유로 위치 데이터를 얻을 수 없는 경우 호출됩니다.

onStatusChanged(String provider, int status, Bundle extras)메소드는
• LocationProvider.OUT_OF_SERVICE
위치 제공자는 현재 이용 가능하지 않고 가까운 미래에도 가능하지 않을 것으로 예상
• LocationProvider.TEMPORARILY_UNAVAILABLE
위치 제공자는 현재 이용 가능하지 않지만, 조만간 이용 가능해질 것으로 예상
• LocationProvider.AVAILABLE
위치 제공자는 현재 이용 가능
한다는 등의 status 값을 설정할 수 있습니다.

  • Bundle Key:Value형태로 활용합니다ㅎㅎ

위치데이터 허용

• onProviderDisabled(String provider)
/ onProviderEnabled(String provider)
이는 사용자가 위치 제공자를 위치 설정 메뉴에서 사용 가능 혹은 사용 불가능으로 변경할 때 이를 통지 받을 수 있도록 해주는 메소드로, 위치에너지 값을 얻어올 것인지 사용자가 어떻게 설정해두었는지 확인하고 disabled하면 데이터수집을 하지 않습니다.

LocationProvider.OUT_OF_SERVICE
• 위치 제공자는 현재 이용 가능하지 않고 가까운 미래에도 가능하지 않을 것으로 예상
• LocationProvider.TEMPORARILY_UNAVAILABLE
• 위치 제공자는 현재 이용 가능하지 않지만, 조만간 이용 가능해질 것으로 예상
• LocationProvider.AVAILABLE
• 위치 제공자는 현재 이용 가능

Permission 허가권한

android 6.0버전부터는 manifest에 permission 주는 것 뿐만 아니라,사용자로부터 Runtime permission을 얻어야 데이터를 받을 수 있습니다.

android.permission.ACCESS_FINE_LOCATION (GPS허가)
android.permission.ACCESS_COARSE_LOCATION (셀룰러 허가)

사용예시
<uses-permission android:name=“android.permission.ACCESS_FINE_LOCATION” />

이제 원하는 위치제공자(GPS, Network, Passive)중에서 원하는 것을 LocationManager에 등록하여 사용합니다~ 그러니 위치 제공자의 각 특성을 이해하고 골라야겠죠~?
위치데이터를 처음 얻기까지 걸리는 시간(TTFF) 및 정확도 및 배터리 소모 정도 그리고 실내 위치데이터 필요여부 등.



사용자의 위치 얻기~

LocationManager 클래스에 정의된 메소드
• requestLocationUpdates() // 계속해서 위치값을 받을 것입니다.
• requestSingleUpdate() // 한 번만 위치 값을 받을 것입니다.
• void removeUpdates(LocationListener listener) // 해당 listener에 대한 위치 업데이트를 제거하겠습니다.

여기서도 마찬가지로 register하는 과정이 필요한데, 아래의 메소드를 활용합니다.

public void requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener)
• provider: 사용 등록할 위치 제공자 이름
• minTime: 위치 업데이트 간의 최소 시간 간격 (ms)
• minDistance: 위치 업데이트 간의 최소 거리 (m)
• listener: 위치 업데이트가 발생했을 때 호출될 onLocationChanged()
메소드가 구현된 LocationListener 객체

• minTime = 0, minDistance = 0 으로 지정하면 가급적 자주 위치 정보가 갱신되므로 정확도는 향상되지만 그만큼 배터리를 많이 소모하므로 적당한 수준에서 결정해야 합니다.


Oreo 버전 업데이트 > 백그라운드 실행 제한 / 백그라운드 위치 제한
백그라운드에 있는 application의 경우 위치 검색 요청 빈도가 제한됩니다.

포그라운드란?

액티비티가 시작되든 일시 중지되든 상관없이, 보이는 액티비티가 있는 경우
포그라운드 서비스가 있는 경우
앱의 서비스 중 하나에 바인드하거나 앱의 콘텐츠 제공자 중 하나를 사용하여 앱에 또 다른 포그라운드 앱이 연결된 경우

위의 경우가 아니라면 백그라운드 앱입니당ㅎㅎ


실습코드 예시,
MainActivity.java와 activity_main.xml로 이루어져있음
MainAc.java코드

package kr.ac.koreatech.swkang.msp03_locationrequest;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements LocationListener {
    final static String TAG = "MSP03";
    final int MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
    TextView logView;
    TextView gps;
    TextView network;
    LocationManager lm;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        logView = (TextView)findViewById(R.id.location);
        gps = (TextView)findViewById(R.id.gps);
        network = (TextView)findViewById(R.id.network);

        // LocationManager 참조 객체
        lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);

        // GPS 프로바이더 사용 가능 여부
        if (lm.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            gps.setText("GPS Provider: Available");
        }

        // 네트워크 프로바이더 사용 가능 여부
        if (lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
            network.setText("Network Provider: Available");
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        //*******************************************************************
        // Runtime permission check
        //*******************************************************************
        if (ContextCompat.checkSelfPermission(MainActivity.this,
                Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {

            // Should we show an explanation?
            if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                    Manifest.permission.ACCESS_FINE_LOCATION)) {

                // Show an expanation to the user *asynchronously* -- don't block
                // this thread waiting for the user's response! After the user
                // sees the explanation, try again to request the permission.

            } else {

                // No explanation needed, we can request the permission.

                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
            }
        } else {
            // ACCESS_FINE_LOCATION 권한이 있는 것이므로
            // location updates 요청을 할 수 있다.

            // GPS provider를 이용
            lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
        }
        //*********************************************************************
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        switch (requestCode) {
            case MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                    // permission was granted, yay! Do the
                    // read_external_storage-related task you need to do.

                    // ACCESS_FINE_LOCATION 권한을 얻었으므로
                    // 관련 작업을 수행할 수 있다
                    try {
                        lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
                    } catch(SecurityException e) {
                        Log.d(TAG, "SecurityException: permission required");
                    }

                } else {

                    // permission denied, boo! Disable the
                    // functionality that depends on this permission.

                    // 권한을 얻지 못 하였으므로 location 요청 작업을 수행할 수 없다
                    // 적절히 대처한다

                }
                return;
            }

            // other 'case' lines to check for other
            // permissions this app might request
        }
    }


    @Override
    protected void onPause() {
        super.onPause();

        lm.removeUpdates(this);
    }

    // LocationListener 구현을 위한 메소드
    // onLocationChanged, onStatusChanged, onProviderEnabled, onProviderDisabled

    public void onLocationChanged(Location location) {
        double lat = location.getLatitude();
        double lng = location.getLongitude();

        logView.setText("latitude: "+ lat +", longitude: "+ lng);
    }

    public void onStatusChanged(String provider, int status, Bundle extras) {

    }

    public void onProviderEnabled(String provider) {

    }

    public void onProviderDisabled(String provider) {

    }
}

• LocationListener 구현
onLocationChanged(Location location) 메소드 외
• onCreate()
LocationManager 객체 얻음
• onResume()
requestLocationUpdates 호출 (runtime permission check 관련 코드 참고)
• onPause()
removeUpdates 호출

main.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:orientation="vertical"
    tools:context="kr.ac.koreatech.swkang.msp03_locationrequest.MainActivity">

    <TextView
        android:id="@+id/gps"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="GPS Provider:  "
        android:textSize="20dp" />

    <TextView
        android:id="@+id/network"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Network Provider: "
        android:textSize="20dp" />

    <TextView
        android:id="@+id/location"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Location: "
        android:textSize="24dp" />

</LinearLayout>


반응형