마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

GnssStatus 클래스 사용하기

0 추천

안드로이드 스튜디오 버전 Arctic Fox 2020.3.1 Patch 4

제가 GPS 정보를 받고 이를 활용하는 어플을 개발하고 있는데, 현재 위치 정보에 사용된 위성의 개수 등을 textView에 출력하려고 합니다. 버튼이 눌리면 관련 정보를 나타내는 단순한 프로그램 입니다.

찾아보니까, 기존의 GpsStatus가 이제 deprecated 되어서 GnssStatus를 사용해야한다고 합니다. 그런데 이 GnssStatus 클래스에 있는 메소드를 어떻게 사용해야하는지 모르겠습니다.

GpsStatus의 경우에는 GpsStatus gpsStatus = manager.getGpsStatus(null); 이런 식으로 할 수 있는데, GnssStatus는 관련된 예시도 많이 보이지 않는 것 같습니다.

혹시 GnssStatus 메소드를 어떻게 사용하는지 아시면 도움주시면 감사하겠습니다ㅠㅠ

코드는 도움되시진 않을 것 같지만 올려봅니다.

public class Fragment3 extends Fragment {

    Button bt2;
    TextView textView;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment3, container, false);

        bt2 = rootView.findViewById(R.id.button2);
        textView = rootView.findViewById(R.id.textView);

        bt2.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.R)
            @Override
            public void onClick(View v) {

                LocationManager manager = (LocationManager) getActivity().getSystemService(Context.LOCATION_SERVICE);
                if (ActivityCompat.checkSelfPermission(getContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(getContext(), android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                    // TODO: Consider calling
                    //    ActivityCompat#requestPermissions
                    // here to request the missing permissions, and then overriding
                    //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                    //                                          int[] grantResults)
                    // to handle the case where the user grants the permission. See the documentation
                    // for ActivityCompat#requestPermissions for more details.
                    return;
                }
                Location location = manager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                GpsStatus gpsStatus = manager.getGpsStatus(null);


                String message = "설정된 경도 : " + location.getLongitude() + "\n설정된 위도 : "+ location.getLatitude() + "\n설정된 정확도 : " + location.getAccuracy();
                textView.setText(message);
            }
        });
        return rootView;
    }
}

 

Sprite_ZERO (470 포인트) 님이 2022년 2월 13일 질문

2개의 답변

0 추천
 
채택된 답변

전체 골격은 아래처럼 잡아서 하시면 좋을 것 같습니다. 아이디어를 보여드리는 것이지 동작하는 코드는 아니므로, 구체적인 부분은 공부를 해가시면 채워가시기 바랍니다.

먼저 API레벨에 따라 리스너/콜백을 사용할 수 있도록 abstract 클래스를 만들어 줍니다.

public abstract class GpsStatusHandler {
    protected final LocationManager manager;

    public GpsStatusHandler(LocationManager manager) {
        this.manager = manager;
    }

    abstract void registerCallback();

    abstract void unregisterCallback();
}

Nougat이전과 Nougat - R, R이후의 API 사용법이 달라지므로, 여기에 해당하는 서브클래스 3개를 만듭니다. 이렇게 함으로써 각 API 레벨에 대한 구체적인 구현방법은 해당 클래스만 알면됩니다. 나중에 Min SDK를 변경해서 Nougat버전이 필요없다면, Nougat버전에 해당하는 클래스를 삭제하면 깔끔하겠죠?

import android.annotation.SuppressLint;
import android.location.GpsStatus;
import android.location.LocationManager;

public class GpsStatusHandlerBeforeN extends GpsStatusHandler {

    private final GpsStatus.Listener listener;

    public GpsStatusHandlerBeforeN(LocationManager manager, GpsStatus.Listener listener) {
        super(manager);
        this.listener = listener;
    }

    @SuppressLint("MissingPermission")
    @Override
    void registerCallback() {
        manager.addGpsStatusListener(listener);
    }

    @Override
    void unregisterCallback() {
        manager.removeGpsStatusListener(listener);
    }
}

 

import static androidx.core.content.ContextCompat.getMainExecutor;
import android.annotation.SuppressLint;
import android.content.Context;
import android.location.GnssStatus;
import android.location.LocationManager;
import android.os.Build;

import androidx.annotation.RequiresApi;

@RequiresApi(Build.VERSION_CODES.R)
public class GpsStatusHandlerFromR extends GpsStatusHandlerNToR {
    public GpsStatusHandlerFromR(LocationManager manager, Context context, GnssStatus.Callback callback) {
        super(manager, context, callback);
    }

    @SuppressLint("MissingPermission")
    @Override
    void registerCallback() {
        manager.registerGnssStatusCallback(getMainExecutor(context), callback);
    }

    @Override
    void unregisterCallback() {
        manager.unregisterGnssStatusCallback(callback);
    }
}

 

import android.annotation.SuppressLint;
import android.content.Context;
import android.location.GnssStatus;
import android.location.LocationManager;
import android.os.Build;

import androidx.annotation.RequiresApi;

@RequiresApi(Build.VERSION_CODES.N)
public class GpsStatusHandlerNToR extends GpsStatusHandler {
    protected final Context context;
    protected final GnssStatus.Callback callback;

    public GpsStatusHandlerNToR(LocationManager manager, Context context, GnssStatus.Callback callback) {
        super(manager);
        this.context = context;
        this.callback = callback;
    }

    @SuppressLint("MissingPermission")
    @Override
    void registerCallback() {
        manager.registerGnssStatusCallback(callback);
    }

    @Override
    void unregisterCallback() {
        manager.unregisterGnssStatusCallback(callback);
    }
}

 

이제 Helper클래스를 하나 만들어 API 레벨에 따라 어떤 구현 클래스를 사용할 건지 결정해 줍니다.  이렇게 함으로써, 복잡한 API 레벨에 따른 처리를 프레그먼트와 완전히 분리시킬 수 있습니다. 나중에 API레벨에 대한 추가사항이 생기게 되면 이 클래스만 건드리면 끝입니다. GpsStatusHelper는 observer패턴을 사용해 여러 observer가 Gps의 변경에 대해 반응할 수 있습니다. observer 패턴을 사용하기 때문에 registerListener/unregisterListener 메소드를 추가했습니다.

import android.content.Context;
import android.location.GnssStatus;
import android.location.GpsStatus;
import android.location.LocationManager;
import android.os.Build;

import androidx.annotation.NonNull;

import java.util.HashSet;
import java.util.Set;

public class GpsStatusHelper extends GnssStatus.Callback implements GpsStatus.Listener {
    interface Listener {
        void onSatelliteStatusChanged(GnssStatus status);
        void onGpsStatusChanged(int event);
    }

    private final Set<Listener> listeners = new HashSet<>();

    @NonNull
    private final GpsStatusHandler gpsStatusHandler;
    public GpsStatusHelper(LocationManager manager, Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            this.gpsStatusHandler = new GpsStatusHandlerFromR(manager, context, this);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            this.gpsStatusHandler = new GpsStatusHandlerNToR(manager, context, this);
        } else {
            this.gpsStatusHandler = new GpsStatusHandlerBeforeN(manager, this);
        }
    }

    public void registerListener(Listener listener) {
        gpsStatusHandler.registerCallback();
        listeners.add(listener);
    }

    public void unregisterListener(Listener listener) {
        gpsStatusHandler.unregisterCallback();
        listeners.remove(listener);
    }

    @Override
    public void onGpsStatusChanged(int event) {
        for (Listener listener : listeners) {
            listener.onGpsStatusChanged(event);
        }
    }

    @Override
    public void onSatelliteStatusChanged(@NonNull GnssStatus status) {
        for (Listener listener : listeners) {
            listener.onSatelliteStatusChanged(status);
        }
    }
}

 

프레그먼트도 손을 좀 봐줘야 합니다.

public class Fragment3 extends Fragment implements GpsStatusHelper.Listener { // GpsStatusHelper.Listener 구현
 
    ...
    
    private GpsStatusHelper gpsStatusHelper;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        ...

        bt2.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.R)
            @Override
            public void onClick(View v) {
 
                ...
                Location location = manager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                String message = "설정된 경도 : " + location.getLongitude() + "\n설정된 위도 : "+ location.getLatitude() + "\n설정된 정확도 : " + location.getAccuracy();
                textView.setText(message);

                // 아래처럼 처리할 수 없음. 대신 GpsStatusHelper.Listener의 콜백메소드 안에서 처리해야 함.
                //GpsStatus gpsStatus = manager.getGpsStatus(null);
            }
        });
        return rootView;
    }

   
   @Override
   protecte void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState)

        gpsStatusHelper = new GpsStatusHelper(manager, requireContext());
    }

    pulblic void onStart() {
        super.onStart();
        gpsStatusHelper.registerListener(this);   // 이 부분이 중요함.
    }

    public void onStop() {
        super.onStop();
        gpsStatusHelper.unregisterListener(this);  // 이 부분이 중요함.
    }

   @Override
   public void onSatelliteStatusChanged(GnssStatus status) {
         // 여기에 필요한 처리
   }

   @Override
   public void onGpsStatusChanged(int event) {
       // 여기에 필요한 처리
   }

}

콜백은 비동기 이므로 콜백 메소드 내에서 필요한 처리를 하도록 변경하셔야 해요. onStart()에서 리스너를 등록하고 onStop에서 해제하는 부분은 observer 패턴을 사용할 때 중요합니다. 참고로 UI 애플리케이션에서는 이 observer 패턴을 엄청나게 많이 사용합니다.

 

 

spark (226,720 포인트) 님이 2022년 2월 13일 답변
Sprite_ZERO님이 2022년 2월 14일 채택됨
윗 분과 같은 분이신 것 같은데 성심성의껏 답변 달아주셔서 감사합니다.
제가 정말 안드로이드 시작한지 얼마 안되어서 기초적인 이해도 부족한 점이 많아 안드로이드 디벨로퍼에 나온 내용들을 조합해서 어떤 프로그램을 만들어나가기에는 실력이 없는데, 이렇게 설명과 함께 로드맵 제시해주신 것이 큰 도움이 될 것 같습니다.
꼼꼼히 공부해보겠습니다. 정말 감사드립니다.
0 추천

해당 API 문서를 살펴봤는데요. 우선 getGpsStatus가 deprecate 된 것과는 별개로, 사용하실 때 API에서 사용하라고 나온대로 해주셔야할 것 같은데, 사용법이 잘못된 듯합니다.

https://developer.android.com/reference/android/location/LocationManager#getGpsStatus(android.location.GpsStatus)

Retrieves information about the current status of the GPS engine. This should only be called from within the GpsStatus.Listener#onGpsStatusChanged callback to ensure that the data is copied atomically. The caller may either pass in an existing GpsStatus object to be overwritten, or pass null to create a new GpsStatus object.
Requires Manifest.permission.ACCESS_FINE_LOCATION

 

설명을 보시면 GpsStatus.Listener의 onGpsStatusChanged 메소드 안헤서 사용하라고 나와 있는데, 님의 코드에는 Listener를 등록하는 부분이 안보이네요.

이걸 말씀드리는 이유는 GnssStatus의 사용법이 유사하기 때문입니다. 리스너를 등록하고 리스너 안에서 값을 처리하셔야 하는 걸로 나옵니다.

https://developer.android.com/reference/android/location/GnssStatus

GnssStatus

This class represents the current state of the GNSS engine and is used in conjunction with GnssStatus.Callback.

LocationManager.registerGnssStatusCallback(GnssStatus.Callback)

LocationManager에 GnssStatus.Callback을 등록하고 사용하셔야 한다고 나와 있습니다.

한가지 주의하실 점은 GnssStatus는API 24 Nougat 부터 도입이 되었고 registerGnssStatusCallback경우도 API 24+와  R에서 사용방법이 조금 다릅니다. 제 생각에는 별도의 클래스를 만들어서 이 부분을 처리하는 클래스를 만들어서 사용하시기를 권장합니다. 

 

 

spark (226,720 포인트) 님이 2022년 2월 13일 답변
...