2017년 2월 5일 일요일

gps example in android (안드로이드에서 gps 사용하기 예제)

Android GPS, location manager


기본 소스는 아래 링크를 참고하였습니다.
http://www.androidhive.info/2012/07/android-gps-location-manager-tutorial/
그러나 소스가 오래된거라 빌드가 안되어서 약간 손 본 부분과 분석하기위한 로그를 추가하였습니다.

GPSTracker.java 예제는 service를 상속 받았지만 실제 구현은 Service형태의 예제는 아니고 Activity를 종료시키면 같이 종료되는 형태로 작업이 되었습니다.

아래 예제는 LocationListener의 onLocationChanged, onProviderDisabled, onProviderEnabled, onStatusChanged 의 의미와 발생에 대해서 알아보도록 하겠습니다.

아래 소스는 Android 6.0 M OS 에서 테스트 되었습니다.

0. 전체적인 이해

https://developer.android.com/guide/topics/location/strategies.html

1. 권한

ACCESS_FINE_LOCATION(android.permission.ACCESS_FINE_LOCATION) 권한이 필요합니다. 이 권한은 GPS로 부터 정확한 위치를 얻기위한 것입니다.
ACCESS_COARSE_LOCATION 은 네트워크로부터 위치를 얻기위한 permission인데 FINE location 권한을 획득하면 포함하게됩니다.

Main 소스에서는 아래와 같은 runtime permission을 획득 하도록 하였습니다.
        if ( Build.VERSION.SDK_INT >= 23 &&
                ContextCompat.checkSelfPermission( this, android.Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) {
            ActivityCompat.requestPermissions( this, new String[] {  android.Manifest.permission.ACCESS_FINE_LOCATION  },
                    0 );
        }

AndroidManifest.xml
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

2. Provider
위치 정보를 주는 곳이 두군데 있습니다.
위성을 이용하는 방식이 GPS_PROVIDER 이고 Network을 이용하는게 NETWORK_PROVIDER 입니다.
location manager 서비스를 이용해서 위치가 변경되거나 또는 주기적으로 위치정보가 업데이트 될때 정보를 보내도록 요청합니다.
해당코드는 아래와 같습니다. 이때 어떤 provider를 사용할지 인자로 넘기게 됩니다.

Network provider
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
MIN_TIME_BW_UPDATES,
MIN_DISTANCE_CHANGE_FOR_UPDATES, this);

GPS provider
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
MIN_TIME_BW_UPDATES,
MIN_DISTANCE_CHANGE_FOR_UPDATES, this);

그외에 인자로 넘어가는 아래값은 언제 업데이트 할지를 결정하는 인자입니다. 해당값에 따라서 거리가 변경되거나 일정 시간이 흐르거나 하면 위치정보를 받을 수 있습니다.
    // The minimum distance to change Updates in meters
    private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; // 10 meters

    // The minimum time between updates in milliseconds
    private static final long MIN_TIME_BW_UPDATES = 1000 * 60 * 1; // 1 minute

지금 당장 위치를 구하려고 한다면 location provider의 마지막 location 정보를 이용해서 현재 좌표를 얻기위한 방법입니다.
                            location = locationManager
                                    .getLastKnownLocation(LocationManager.GPS_PROVIDER);
                            if (location != null) {
                                latitude = location.getLatitude();
                                longitude = location.getLongitude();
                            }

이후로는 주기적으로 아래 listener로 함수가 호출됩니다.

    @Override
    public void onLocationChanged(Location location) {
        if(location != null){
            double latitude= location.getLatitude();
            double longitude = location.getLongitude();
            //Toast.makeText(mContext, "onLocationChanged is - \nLat: " + latitude + "\nLong: " + longitude, Toast.LENGTH_SHORT).show();
            sendString("onLocationChanged is - \nLat: " + latitude + "\nLong: " + longitude + " provider:"+location.getProvider()+" mock:"+location.isFromMockProvider());
        }
    }




Provider enable disable 은 설정에서 provider 설정을 변경하면 호출됩니다.
여기에서는 GPS update설정을 변경하도록 구현하였습니다.
    @Override
    public void onProviderDisabled(String provider) {
        //Toast.makeText(mContext, "onProviderDisabled " + provider, Toast.LENGTH_SHORT).show();
        mHandler.sendEmptyMessage(MainActivity.RENEW_GPS);
        sendString( "onProviderDisabled " + provider);
    }

    @Override
    public void onProviderEnabled(String provider) {
        //Toast.makeText(mContext, "onProviderEnabled " + provider, Toast.LENGTH_SHORT).show();
        mHandler.sendEmptyMessage(MainActivity.RENEW_GPS);
        sendString( "onProviderEnabled " + provider);
    }
뒤쪽 provider의 String은 gps, network 등이 출력됩니다. 아래 참고


참고로 location.isFromMockProvider 값을 출력해봤습니다. mock 위치라고 해서 GPS조작앱을 통한 값인지 여부를 판단할 수 있을지 알았는데요.
조작앱을 설치해서 실제 테스트해보니 항상 false값이 확인이 됩니다.
이 부분은 fake GPS 앱들이 특수한 방법으로 true가 안되게 하는것 같습니다. 기회가 닿으면 따로 분석 해보도록 하겠습니다.
http://swlock.blogspot.com/2017/02/using-mock-gps-mock-gps-gps-in-android.html

소스에서 onLocationChanged 될때 Bundle 값을 출력시켜 봤는데요.
satellites 숫자가 나오는데 이건 위성의 숫자라고 합니다.

3. 전체 소스는 아래와 같습니다.



MainActivity
package com.example.xxxx.gpstest;

import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.text.SimpleDateFormat;
import java.util.Date;

// http://www.androidhive.info/2012/07/android-gps-location-manager-tutorial/

public class MainActivity extends AppCompatActivity {
    Button btnShowLocation;
    EditText editText;

    // GPSTracker class
    GPSTracker gps = null;

    public Handler mHandler;

    public static int RENEW_GPS = 1;
    public static int SEND_PRINT = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        if ( Build.VERSION.SDK_INT >= 23 &&
                ContextCompat.checkSelfPermission( this, android.Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) {
            ActivityCompat.requestPermissions( this, new String[] {  android.Manifest.permission.ACCESS_FINE_LOCATION  },
                    0 );
        }

        editText = (EditText) findViewById(R.id.editText);
        btnShowLocation = (Button) findViewById(R.id.btnShowLocation);

        mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg){
                if(msg.what==RENEW_GPS){
                    makeNewGpsService();
                }
                if(msg.what==SEND_PRINT){
                    logPrint((String)msg.obj);
                }
            }
        };

        // show location button click event
        btnShowLocation.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                // create class object
                if(gps == null) {
                    gps = new GPSTracker(MainActivity.this,mHandler);
                }else{
                    gps.Update();
                }

                // check if GPS enabled
                if(gps.canGetLocation()){
                    double latitude = gps.getLatitude();
                    double longitude = gps.getLongitude();
                    // \n is for new line
                    Toast.makeText(getApplicationContext(), "Your Location is - \nLat: " + latitude + "\nLong: " + longitude, Toast.LENGTH_LONG).show();
                }else{
                    // can't get location
                    // GPS or Network is not enabled
                    // Ask user to enable GPS/network in settings
                    gps.showSettingsAlert();
                }
            }
        });
    }
    public void makeNewGpsService(){
        if(gps == null) {
            gps = new GPSTracker(MainActivity.this,mHandler);
        }else{
            gps.Update();
        }

    }
    public void logPrint(String str){
        editText.append(getTimeStr()+" "+str+"\n");
    }
    public String getTimeStr(){
        long now = System.currentTimeMillis();
        Date date = new Date(now);
        SimpleDateFormat sdfNow = new SimpleDateFormat("MM/dd HH:mm:ss");
        return sdfNow.format(date);
    }
}



GPSTracker.java
package com.example.xxxx.gpstest;

import android.Manifest;
import android.app.AlertDialog;
import android.app.Service;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.widget.Toast;

import java.util.Set;

/**
 * Created by xxxx on 2017-02-04.
 */
public class GPSTracker extends Service implements LocationListener {

    private final Context mContext;

    // flag for GPS status
    boolean isGPSEnabled = false;

    // flag for network status
    boolean isNetworkEnabled = false;

    // flag for GPS status
    boolean canGetLocation = false;

    Location location; // location
    double latitude; // latitude
    double longitude; // longitude

    // The minimum distance to change Updates in meters
    private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; // 10 meters

    // The minimum time between updates in milliseconds
    private static final long MIN_TIME_BW_UPDATES = 1000 * 60 * 1; // 1 minute

    // Declaring a Location Manager
    protected LocationManager locationManager;

    private Handler mHandler;

    public GPSTracker(Context context, Handler handler) {
        this.mContext = context;
        this.mHandler = handler;
        getLocation();
    }
    public void Update(){
        getLocation();
    }
    public Location getLocation() {

        if ( Build.VERSION.SDK_INT >= 23 &&
                ContextCompat.checkSelfPermission( mContext, android.Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) {
            return null;
        }
        try {
            locationManager = (LocationManager) mContext
                    .getSystemService(LOCATION_SERVICE);

            // getting GPS status
            isGPSEnabled = locationManager
                    .isProviderEnabled(LocationManager.GPS_PROVIDER);

            // getting network status
            isNetworkEnabled = locationManager
                    .isProviderEnabled(LocationManager.NETWORK_PROVIDER);

            if (!isGPSEnabled && !isNetworkEnabled) {
                // no network provider is enabled
            } else {
                this.canGetLocation = true;
                // First get location from Network Provider
                if (isNetworkEnabled) {
                    locationManager.requestLocationUpdates(
                            LocationManager.NETWORK_PROVIDER,
                            MIN_TIME_BW_UPDATES,
                            MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
                    Log.d("Network", "Network");
                    if (locationManager != null) {
                        location = locationManager
                                .getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
                        if (location != null) {
                            latitude = location.getLatitude();
                            longitude = location.getLongitude();
                        }
                    }
                }
                // if GPS Enabled get lat/long using GPS Services
                if (isGPSEnabled) {
                    if (location == null) {
                        locationManager.requestLocationUpdates(
                                LocationManager.GPS_PROVIDER,
                                MIN_TIME_BW_UPDATES,
                                MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
                        Log.d("GPS Enabled", "GPS Enabled");
                        if (locationManager != null) {
                            location = locationManager
                                    .getLastKnownLocation(LocationManager.GPS_PROVIDER);
                            if (location != null) {
                                latitude = location.getLatitude();
                                longitude = location.getLongitude();
                            }
                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return location;
    }

    /**
     * Stop using GPS listener
     * Calling this function will stop using GPS in your app
     * */
    public void stopUsingGPS(){
        if ( Build.VERSION.SDK_INT >= 23 &&
                ContextCompat.checkSelfPermission( mContext, android.Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) {
            return;
        }
        if(locationManager != null){
            locationManager.removeUpdates(GPSTracker.this);
        }
    }

    /**
     * Function to get latitude
     * */
    public double getLatitude(){
        if(location != null){
            latitude = location.getLatitude();
        }

        // return latitude
        return latitude;
    }

    /**
     * Function to get longitude
     * */
    public double getLongitude(){
        if(location != null){
            longitude = location.getLongitude();
        }

        // return longitude
        return longitude;
    }

    /**
     * Function to check GPS/wifi enabled
     * @return boolean
     * */
    public boolean canGetLocation() {
        return this.canGetLocation;
    }

    /**
     * Function to show settings alert dialog
     * On pressing Settings button will lauch Settings Options
     * */
    public void showSettingsAlert(){
        AlertDialog.Builder alertDialog = new AlertDialog.Builder(mContext);

        // Setting Dialog Title
        alertDialog.setTitle("GPS is settings");

        // Setting Dialog Message
        alertDialog.setMessage("GPS is not enabled. Do you want to go to settings menu?");

        // On pressing Settings button
        alertDialog.setPositiveButton("Settings", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog,int which) {
                Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                mContext.startActivity(intent);
            }
        });

        // on pressing cancel button
        alertDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                dialog.cancel();
            }
        });

        // Showing Alert Message
        alertDialog.show();
    }

    @Override
    public void onLocationChanged(Location location) {
        if(location != null){
            double latitude= location.getLatitude();
            double longitude = location.getLongitude();
            //Toast.makeText(mContext, "onLocationChanged is - \nLat: " + latitude + "\nLong: " + longitude, Toast.LENGTH_SHORT).show();
            sendString("onLocationChanged is - \nLat: " + latitude + "\nLong: " + longitude + " provider:"+location.getProvider()+" mock:"+location.isFromMockProvider());
        }
    }

    @Override
    public void onProviderDisabled(String provider) {
        //Toast.makeText(mContext, "onProviderDisabled " + provider, Toast.LENGTH_SHORT).show();
        mHandler.sendEmptyMessage(MainActivity.RENEW_GPS);
        sendString( "onProviderDisabled " + provider);
    }

    @Override
    public void onProviderEnabled(String provider) {
        //Toast.makeText(mContext, "onProviderEnabled " + provider, Toast.LENGTH_SHORT).show();
        mHandler.sendEmptyMessage(MainActivity.RENEW_GPS);
        sendString( "onProviderEnabled " + provider);
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
        //Toast.makeText(mContext, "onStatusChanged " + provider + " : " + status, Toast.LENGTH_SHORT).show();
        mHandler.sendEmptyMessage(MainActivity.RENEW_GPS);
        sendString("onStatusChanged " + provider + " : " + status + ":" + printBundle(extras));
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    private void sendString(String str){
        Message msg = mHandler.obtainMessage();
        msg.what = MainActivity.SEND_PRINT;
        msg.obj = new String(str);
        mHandler.sendMessage(msg);
    }

    public static String printBundle(Bundle extras) {
        StringBuilder sb = new StringBuilder();
        try {
                sb.append("extras = " + extras);
                sb.append("\n");
                if (extras != null) {
                    Set keys = extras.keySet();
                    sb.append("++ bundle key count = " + keys.size());
                    sb.append("\n");

                    for (String _key : extras.keySet()) {
                        sb.append("key=" + _key + " : " + extras.get(_key)+",");
                    }
                    sb.append("\n");
                }
        } catch (Exception e) {

        } finally {

        }
        return sb.toString();
    }

}



AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.xxxx.gpstest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        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>
    </application>

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>


activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.darts.gpstest.MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="START"
        android:id="@+id/btnShowLocation"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/editText"
        android:layout_below="@+id/btnShowLocation"
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true"
        android:editable="false"
        android:textSize="10dp"
        android:text="log print here\n" />
</RelativeLayout>


소스
https://drive.google.com/open?id=0B9vAKDzHthQIenZKLU1JeV9SY2c

APK
https://drive.google.com/open?id=0B9vAKDzHthQIOWhydXRGTjV6cWs







댓글 9개:

  1. 안녕하세요. 잘 정리해주셔서 감사합니다.
    위의 예제를 앱이 활성화 돼 있을 때 뿐만 아니라 앱이 종료되어 있어도 좌표값을 계속 받고 있으면 어떤식으로 수정해야 할까요???

    답글삭제
  2. 안녕하세요. 예제 잘 정리해주셔서 감사합니다.
    위의 예제는 앱이 활성화 되어있을때 좌표값을 받아오는 형식인거 같습니다.
    혹시 위에서 말씀하신대로 앱이 종료되더라도 좌표값을 주기적으로 받아오려면 어떻게 수정해야 할까요???

    답글삭제
    답글
    1. 댓글 검토 기능이 최근에 들어있어서 확인전까지는 댓글이 달렸는지도 미쳐 확인을 못했습니다. 답변 늦어진점 죄송합니다.
      일반적으로 앱이 종료되어도 값을 받으려면 Service로 제작을 하셔야합니다. 이 예제는 Service로 제작되긴하였으나 서비스 Start 부분이 빠져서 서비스 형태로 있는것이아니라 instance로 존재만 하는 예제입니다.
      질문이 오래되어서 이미 해답을 찾으셨길 바랍니다.

      삭제
  3. 안녕하세요, 포스팅을 보고 많은 도움을 받아갑니다 감사합니다
    궁금한게 하나 있는데, 메인액티비티의 if(gps.canGetLocation())이 부분은 '클릭'할 때 마다 위치 값을 불러와 토스트로,
    GPSTracker 클래스의 onLocationChanged메서드 안의 위치 값은 위치가 바뀔 때마다 '자동' 호출되어 뷰에 뿌려주는 건가요??
    그리고 자동 호출이 필요없다면 onLocationChanged속의 내용은 구현할 필요가 없나요??
    감사합니다 오늘도 좋은 하루 되시길 바랍니당

    답글삭제
    답글
    1. onLocationChanged 은 GPSTracker가 생성이 되면 자동으로 불리기 됩니다....
      onLocationChanged 이게 호출될때 값을 저장을 해둡니다.

      if(gps.canGetLocation()) 이 부분은 클릭시 곧장 값을 가져오는게 아니라 마지막으로 onLocationChanged이 호출될 당시의 마지막에 저장된 값을 가져오는 역할을 합니다.

      삭제
  4. onLocationChanged 은 GPSTracker가 생성이 되면 자동으로 불리기 됩니다....
    onLocationChanged 이게 호출될때 값을 저장을 해둡니다.

    if(gps.canGetLocation()) 이 부분은 클릭시 곧장 값을 가져오는게 아니라 마지막으로 onLocationChanged이 호출될 당시의 마지막에 저장된 값을 가져오는 역할을 합니다.

    답글삭제
  5. 작성자가 댓글을 삭제했습니다.

    답글삭제
  6. Service를 Start 안하셔놨다 했는데 만일 하게된다면 어떻게 만져야 할까요 ?

    답글삭제
    답글
    1. Service공부하셨다면 어렵지 않게 작업하실수 있을 것 같은데요.
      직접 제가 테스트 해본 코드는 아니지만,
      https://stackoverflow.com/questions/28535703/best-way-to-get-user-gps-location-in-background-in-android
      answered Mar 22 at 12:05 Puri 란 분이 답변하신 아래 답변 항목을 참고하세요.
      Download the source code from here (Get Current Location Using Background Service)

      https://deepshikhapuri.wordpress.com/2016/11/25/service-in-android/

      삭제