非ブロッキング FusedLocationProvider API をブロッキング API として使用しようとしています。これが私がやっていることです(大まかに):
- AsyncTask を開始する
- バックグラウンド スレッドで、PlayServices/FusedProvider API に接続します。これに使用できるタイムアウト付きのブロック方法があります。
- 上記の API を使用して、場所の更新をリクエストします。API は、デバイスを使用して位置を計算し、呼び出し元のスレッドで実行されるコールバックをトリガーします。
- を使用してコールバックを待ちます
countDownLatch.await(timeOut,timeUnit)
- Fused API がコールバックをトリガーし、場所を取得して を実行すると
countDownLatch.countdown()
、ラッチが解放されます。 - 場所を返します。
いつでも場所を計算して操作する必要がある可能性があるため、ブロックする方法が必要です。上記のアプローチは機能していますが、ブロックしている同じスレッドから CountDownLatch がトリガーされているため、より良い方法を探しています。これを行うことで冒涜/完全なダンベリーを犯しているかどうかはわかりません。
これが私のコードです:
public class BlockingLocationProvider implements ConnectionCallbacks,
OnConnectionFailedListener, LocationListener {
private static final String TAG = "BlockingLocationProvider";
private LocationInfo mLocInfoRow;
private long mCallTime;
private Double mDel;
private GoogleApiClient mGoogleApiClient;
private Location mLocation;
private LocationRequest locationRequest;
private boolean mLocationCalculated = false;
private CountDownLatch latch;
private Context context;
private ConnectionResult gApiConnResult;
@Override
public void onConnectionFailed(ConnectionResult arg0) {
Logger.error(TAG, "Google play services error while getting location.");
mLocInfoRow = new LocationInfo().timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_G_PLAY).statusCode(arg0.getErrorCode());
mLocation = null;
mLocationCalculated = true;
latch.countDown();
}
@Override
public void onConnected(Bundle arg0) {
locationRequest = new LocationRequest();
locationRequest.setInterval(0).setNumUpdates(1)
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setExpirationDuration(2000);
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, locationRequest, this);
}
@Override
public void onConnectionSuspended(int arg0) {
Logger.warning(TAG, "Can't handle suspension.");
mLocInfoRow = new LocationInfo().timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_UNKNOWN).statusCode(-200);
latch.countDown();
}
@Override
public void onLocationChanged(Location arg0) {
mLocation = arg0;
if (mLocation == null) {
Logger.debug(TAG, "Fused provider returned null");
mLocInfoRow = new LocationInfo()
.timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_FUSED_NULL).statusCode(-3);
} else {
Logger.debug(TAG, "Got a location from fused provider.");
mLocInfoRow = new LocationInfo().timestamp(mLocation.getTime())
.lat(mLocation.getLatitude()).lng(mLocation.getLongitude())
.travelSpeed(mLocation.getSpeed())
.altitude(mLocation.getAltitude())
.acc(mLocation.getAccuracy()).provider("fused")
.status("OK").statusCode(0);
}
mLocationCalculated = true;
latch.countDown();
}
public LocationInfo getLocationInfo(Context ctx) {
this.context = ctx;
calcLocation();
return mLocInfoRow;
}
public Location getLocation(Context ctx) {
this.context = ctx;
calcLocation();
return mLocation;
}
// This should block
public void calcLocation() {
if (Thread.currentThread().getName().equals("main")) {
throw new RuntimeException("Cannot run " + TAG + " on UI thread.");
}
latch = new CountDownLatch(1);
/* To figure how long it takes to calc location */
mCallTime = System.currentTimeMillis();
Logger.verbose(TAG, "Checking play services.");
// First check play services
int playServicesAvailable = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(context);
// whoopsie!
if (playServicesAvailable != ConnectionResult.SUCCESS) {
Logger.error(TAG,
"Google play services error while getting location.");
mLocInfoRow = new LocationInfo()
.timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_G_PLAY)
.statusCode(playServicesAvailable);
mLocation = null;
return;
}
Logger.verbose(TAG, "Checking GPS.");
// Then check GPS enabled or not
if (!isGpsEnabled(context)) {
Logger.error(TAG,
"User has disabled GPS. Unable to provide location updates.");
mLocInfoRow = new LocationInfo()
.timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_NO_GPS).statusCode(-2);
mLocation = null;
return;
}
Logger.verbose(TAG, "Connecting to play services.");
// Then connect to play services client
mGoogleApiClient = new GoogleApiClient.Builder(context)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API).build();
if (!(mGoogleApiClient.isConnected() || mGoogleApiClient.isConnecting())) {
gApiConnResult = mGoogleApiClient.blockingConnect(5,
TimeUnit.SECONDS);
}
boolean timeout = false;
if (gApiConnResult.isSuccess()) {
try {
Logger.warning(TAG, "Waiting latch on location request...");
timeout = !latch.await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (timeout) {
Logger.warning(TAG, "Latch timeout!");
mLocInfoRow = new LocationInfo()
.timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_TIMEOUT).statusCode(-100);
} else {
Logger.debug(TAG, "Latch triggered!");
}
} else {
Logger.warning(TAG, "gApi connect timeout!");
mLocInfoRow = new LocationInfo()
.timestamp(System.currentTimeMillis())
.status(LocStatus.ERR_TIMEOUT).statusCode(-300);
}
mDel = (Double.valueOf(System.currentTimeMillis() - mCallTime) / 1000);
Logger.debug(TAG, "Took " + mDel + " seconds for location update.");
LocationServices.FusedLocationApi.removeLocationUpdates(
mGoogleApiClient, this);
mGoogleApiClient.disconnect();
}
private static boolean isGpsEnabled(final Context ctx) {
LocationManager lm = (LocationManager) ctx
.getSystemService(Context.LOCATION_SERVICE);
return lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
}
}
この場合、無限ループのラッチとブロックするフラグ以外に戦略はありますか? ありがとう。