전체 골격은 아래처럼 잡아서 하시면 좋을 것 같습니다. 아이디어를 보여드리는 것이지 동작하는 코드는 아니므로, 구체적인 부분은 공부를 해가시면 채워가시기 바랍니다.
먼저 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 패턴을 엄청나게 많이 사용합니다.