问题
I am building an app that needs to send the user's location to a remote server (Pusher in this case). The goal is to update their location on a map in near real-time but only when they are on a job, otherwise the app will not need to track their location.
I need the location updates to remain active if they leave the activity where they accepted the job (and therefore were placed on the map) and if they leave the app altogether. Once they have reached their destination, I wish to stop this background tracking.
I have been looking at Android's Service
component but I am not sure if it is what I need for this. The updates should occur in the background indefinitely but only while the user is assigned to a Job (updates start when they accept a job, end when they reach their destination).
Would a Bound service be the best for this? If so, some code as it relates to this problem would be GREATLY appreciated, as much of what I can find is generic and does things like return random integers.
Here is the code I wish to execute in the background:
package com.example.locationtester;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import com.pusher.client.Pusher;
import com.pusher.client.PusherOptions;
import com.pusher.client.channel.PrivateChannel;
import com.pusher.client.channel.PrivateChannelEventListener;
import com.pusher.client.connection.ConnectionEventListener;
import com.pusher.client.connection.ConnectionState;
import com.pusher.client.connection.ConnectionStateChange;
import com.pusher.client.util.HttpAuthorizer;
import org.json.JSONException;
import org.json.JSONObject;
public class MainActivity extends AppCompatActivity {
private TextView mLatLabel;
private TextView mLongLabel;
private TextView mAccuracy;
private Double mLat;
private Double mLong;
private boolean isSubscribed = false;
private Pusher mPusher;
private PrivateChannel mChannel;
private static final long MIN_TIME_BW_UPDATES = 1000 * 60;
private static final String TAG = "GPSTest";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLatLabel = (TextView) findViewById(R.id.latLabel);
mLongLabel = (TextView) findViewById(R.id.longLabel);
mAccuracy = (TextView) findViewById(R.id.accuracyLabel);
HttpAuthorizer authorizer = new HttpAuthorizer("http://example.com");
PusherOptions options = new PusherOptions().setAuthorizer(authorizer).setEncrypted(true);
mPusher = new Pusher("PUSHER_API_KEY", options);
mPusher.connect(new ConnectionEventListener() {
@Override
public void onConnectionStateChange(ConnectionStateChange change) {
Log.d(TAG, "State changed to " + change.getCurrentState() +
" from " + change.getPreviousState());
}
@Override
public void onError(String message, String code, Exception e) {
Log.d(TAG, "There was a problem connecting! " + e.toString());
}
}, ConnectionState.ALL);
mChannel = mPusher.subscribePrivate("private-location", new PrivateChannelEventListener() {
@Override
public void onAuthenticationFailure(String message, Exception e) {
Log.e(TAG, "Error " + message);
}
@Override
public void onSubscriptionSucceeded(String channelName) {
Log.d(TAG, "Subscribed to " + channelName);
isSubscribed = true;
}
@Override
public void onEvent(String channelName, String eventName, String data) {
}
});
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
handleLocationUpdate(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Toast.makeText(getApplicationContext(), "GPS Status Changed " + provider, Toast.LENGTH_LONG).show();
}
@Override
public void onProviderEnabled(String provider) {
Toast.makeText(getApplicationContext(), "GPS Provider Enabled " + provider, Toast.LENGTH_LONG).show();
}
@Override
public void onProviderDisabled(String provider) {
}
};
// Register the listener with the Location Manager to receive location updates
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_TIME_BW_UPDATES, 0, locationListener);
}
private void handleLocationUpdate(Location location) {
mLat = location.getLatitude();
mLong = location.getLongitude();
mLatLabel.setText("Long: " + location.getLongitude());
mLongLabel.setText("Lat: " + location.getLatitude());
mAccuracy.setText("Accuracy: " + location.getAccuracy() + " at " + location.getTime());
Log.d(TAG, mLat + "");
if (isSubscribed)
{
JSONObject json = new JSONObject();
try {
json.put("lat", mLat);
json.put("long", mLong);
json.put("time", location.getTime());
json.put("accuracy", location.getAccuracy());
mChannel.trigger("client-location-changed", json.toString());
} catch (JSONException e) {
Log.e(TAG, "Problem adding JSON");
}
}
}
}
UPDATE
This is what I came up with after switching to Google Play Service's Location API. I have tested leaving this activity (and the app in general) and everything keeps running smoothly, proving location updates until I click on the button to make them stop.
Feedback would be appreciated on this code:
public class MainActivity extends AppCompatActivity implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
LocationListener {
private GoogleApiClient mGoogleApiClient;
private LocationRequest mLocationRequest;
public static final String TAG = MainActivity.class.getSimpleName();
public static final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;
private boolean isSubscribed = false;
private Pusher mPusher;
private PrivateChannel mChannel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startTracking = (Button) findViewById(R.id.btnStartTracking);
Button stopTracking = (Button) findViewById(R.id.btnStopTracking);
final Button startActivity = (Button) findViewById(R.id.btnStartActivity);
HttpAuthorizer authorizer = new HttpAuthorizer("http://example.com");
PusherOptions options = new PusherOptions().setAuthorizer(authorizer).setEncrypted(true);
mPusher = new Pusher("API_KEY", options);
mPusher.connect(new ConnectionEventListener() {
@Override
public void onConnectionStateChange(ConnectionStateChange change) {
Log.d(TAG, "State changed to " + change.getCurrentState() +
" from " + change.getPreviousState());
}
@Override
public void onError(String message, String code, Exception e) {
Log.d(TAG, "There was a problem connecting! " + e.toString());
}
}, ConnectionState.ALL);
mChannel = mPusher.subscribePrivate("private-location", new PrivateChannelEventListener() {
@Override
public void onAuthenticationFailure(String message, Exception e) {
Log.e(TAG, "Error " + message);
}
@Override
public void onSubscriptionSucceeded(String channelName) {
Log.d(TAG, "Subscribed to " + channelName);
isSubscribed = true;
}
@Override
public void onEvent(String channelName, String eventName, String data) {
}
});
// Build the Google Api Client and set the API for LocationServices
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
// Create the LocationRequest object
mLocationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(20 * 1000) // 3 seconds, in MS
.setFastestInterval(1000); // 1 second, in MS
startTracking.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mGoogleApiClient.isConnected())
{
mGoogleApiClient.connect();
}
}
});
stopTracking.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mGoogleApiClient.isConnected())
{
mGoogleApiClient.disconnect();
}
}
});
startActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), Activity2.class);
startActivity(intent);
}
});
}
@Override
protected void onResume()
{
super.onResume();
if (!mGoogleApiClient.isConnected())
{
mGoogleApiClient.connect();
}
}
@Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Location services connected");
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
}
private void handleNewLocation(Location location) {
Log.d(TAG, location.toString());
double currentLatitude = location.getLatitude();
double currentLongitude = location.getLongitude();
LatLng latLng = new LatLng(currentLatitude, currentLongitude);
int speed = (int) (location.getSpeed() * 2.2369);
if (isSubscribed) {
JSONObject json = new JSONObject();
try {
json.put("lat", currentLatitude);
json.put("long", currentLongitude);
json.put("time", location.getTime());
json.put("accuracy", location.getAccuracy());
json.put("speed", speed);
json.put("latLng", latLng);
mChannel.trigger("client-location-changed", json.toString());
} catch (JSONException e) {
Log.e(TAG, "Problem adding JSON");
}
}
}
@Override
public void onConnectionSuspended(int i) {
Log.i(TAG, "Location services suspended. Please reconnect");
}
@Override
public void onLocationChanged(Location location) {
handleNewLocation(location);
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
if (connectionResult.hasResolution()) {
try {
// Start an Activity that tries to resolve the error
connectionResult.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
} else {
Log.i(TAG, "Location services connection failed with code " + connectionResult.getErrorCode());
}
}
}
回答1:
One option is to ditch the LocationManager API and shift to the FusedLocationProviderAPI.
The FusedLocationProviderAPI allows you to request intermittent Location Updates while making location requests in the most efficient manager.
Some code that could help "point" you in the right direction would look like this:
public class GPSPlotter implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {
public static final String UPLOAD_ACTION = "upload";
public static final String BACKGROUND_ACTION = "background";
private static final String TAG = "GPSPlotter: ";
private static final int DEFAULT_INTENT_INTERVAL = 60;
private static final int ALARM_REGISTER_BUFFER = 60000;
/**
* Static fields used in both Background and Foreground Location Updates.
*/
private static GPSPlotter gpsPlotterInstance;
private ServiceType mCurrentServiceType;
private GoogleApiClient mGoogleApiClient;
private MyAccount mAccount;
private static Location mCurrentLocation;
private static CoordinateStorageDatabaseHelper mDbHelper;
private static AlarmManager mAlarmManager;
private static String mUserID;
private static Context mContext;
private int mIntentInterval;
private GPSPlotter(Context theContext) {
initializeInstance();
initializeFields(theContext);
buildApiClient();
connectClient();
}
/**
* Returns an instance of the GPS Plotter.
*/
public static GPSPlotter getInstance(Context theContext) {
if (gpsPlotterInstance == null)
return new GPSPlotter(theContext);
else
return gpsPlotterInstance;
}
/**
* Private method to initialize the fields of the GPS Plotter class.
*
* @param theContext is the application context.
*/
private void initializeFields(Context theContext) {
mGoogleApiClient = null;
mCurrentLocation = null;
mDbHelper = new CoordinateStorageDatabaseHelper(theContext);
mUserID = LocalStorage.getUserID(theContext);
mContext = theContext;
mAccount = null;
mIntentInterval = DEFAULT_INTENT_INTERVAL;
mCurrentServiceType = ServiceType.BACKGROUND;
}
/**
* Private method to initialize an instance of the GPS Plotter class.
*/
private void initializeInstance() {
gpsPlotterInstance = this;
}
/***********************************GOOGLE API CLIENT METHODS*********************************/
/**
* Private helper method to initialize the Google Api Client with the
* LocationServices Api and Build it for use.
*/
private void initializeGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(mContext)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
/**
* Private helper method to determine whether or not GooglePlayServices
* are installed on the local system.
*
* @return services are installed.
*/
private boolean googlePlayServicesInstalled() {
int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(mContext);
return result == ConnectionResult.SUCCESS;
}
/**
* Private method to build the Api Client for use with the LocationServices API.
*/
private synchronized void buildApiClient() {
Log.w(TAG, "Building Google Api Client...");
initializeGoogleApiClient();
}
/**
* Private method used to connect the ApiClient to the Api hosted by Google for
* Accessing Locations.
*/
private void connectClient() {
mGoogleApiClient.connect();
}
/***********************************UPLOAD PROCESSES AND INTENTS********************************/
/**
* Private method to create a pending intent for issuing alarm manager requests.
*
* @param theIntent is the original intent.
* @return thePendingIntent
*/
private PendingIntent buildUploadPendingIntent(Intent theIntent) {
return PendingIntent.getBroadcast(mContext, 0, theIntent, 0);
}
/**
* Private method to create an intent for issuing alarm manager requests.
*
* @return theIntent
*/
private Intent buildUploadIntent() {
Intent theIntent = new Intent(mContext, BackgroundLocationReceiver.class);
theIntent.setAction(UPLOAD_ACTION);
return theIntent;
}
/**
* Private method to register an instance of an AlarmManager that will issue uploads to the
* WebService intermittently. Default duration is one hour. Akarm manager waits one minute
* from the current time before issuing the request to the background services for firing
* points to the database.
*/
private void registerAlarmManager() {
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
(System.currentTimeMillis() + ALARM_REGISTER_BUFFER),
AlarmManager.INTERVAL_HOUR, buildUploadPendingIntent(buildUploadIntent()));
}
/**
* Private method used to cancel the alarm manager in the case that a background point service
* or a foreground point service are disabled.
*/
private void unregisterAlarmManager() {
if (mAlarmManager != null)
mAlarmManager.cancel(buildUploadPendingIntent(buildUploadIntent()));
}
/*****************************************LOCATION SERVICE REQUESTS****************************/
/**
* User passes in a requested interval polling time in seconds as an
* integer.
*
* @param theAccount is a reference to the parent activity used for updating views.
*/
public void beginManagedLocationRequests(MyAccount theAccount) {
if (mAccount == null)
mAccount = theAccount;
startBackgroundUpdates();
}
/**
* Public method to end the managed Location Requests.
*/
public void endManagedLocationRequests() {
endBackgroundUpdates();
}
/**
* This method handles the switch in polling rates by stopping and then starting once more the
* background udpates, which in turn sets the interval in another method in the call stack.
* @param theInterval the desired interval polling rate
*/
public void changeRequestIntervals(int theInterval) {
mIntentInterval = theInterval;
if (LocalStorage.getRequestingBackgroundStatus(mContext)) {
endBackgroundUpdates();
startBackgroundUpdates();
}
}
/**
* Private helper method to build an Intent that will be couple with a pending intent uses
* for issuing background Location requests.
*
* @return theIntent
*/
private Intent buildBackgroundRequestIntent() {
Intent intent = new Intent(mContext, BackgroundLocationReceiver.class);
intent.setAction(BACKGROUND_ACTION);
intent.putExtra(User.USER_ID, mUserID);
return intent;
}
/**
* Private helper method used to generate a PendingIntent for use when the User requests background service
* within the FusedLocationApi until the Interval is changed.
*
* @return pendingIntent
*/
private PendingIntent buildRequestPendingIntent(Intent theIntent) {
Log.w(TAG, "building pending intent");
return PendingIntent.getBroadcast(mContext, 0, theIntent, 0);
}
/**
* Private method to start the Location Updates using the FusedLocation API in the background.
*/
private void startBackgroundUpdates() {
Log.w(TAG, "Starting background updates");
if (googlePlayServicesInstalled()) {
LocalStorage.putBackgroundRequestStatus(true, mContext);
LocalStorage.putLocationRequestStatus(true, mContext);
registerAlarmManager();
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, buildLocationRequest(), buildRequestPendingIntent(buildBackgroundRequestIntent()));
}
}
/**
* Private method to end background updates.
*/
private void endBackgroundUpdates() {
Log.w(TAG, "Ending background updates");
LocalStorage.putBackgroundRequestStatus(false, mContext);
LocalStorage.putLocationRequestStatus(false, mContext);
unregisterAlarmManager();
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, buildRequestPendingIntent(buildBackgroundRequestIntent()));
}
/**
* Private helper method used to generate a LocationRequest which will be used to handle all location updates
* within the FusedLocationApi until the Interval is changed.
*
* @return locationRequest
*/
private LocationRequest buildLocationRequest() {
int dateConversion = 1000;
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setInterval(mIntentInterval * dateConversion);
locationRequest.setFastestInterval((mIntentInterval / 2) * dateConversion);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
Log.w(TAG, "Building location request");
return locationRequest;
}
}
And then you would have a Background Service that you would register in the above GPS Plotter:
public class BackgroundService extends IntentService {
/**
* Private static final String to represent a TAG for this class.
*/
private static final String TAG = BackgroundService.class.getName();
public BackgroundService() {
super("BackgroundService");
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
Log.w(TAG, "Intent is not null...");
GPSPlotter plotter = GPSPlotter.getInstance(getApplicationContext());
int counter = 0;
while (!plotter.hasApiClientConnectivity()) {
if (counter == 0) {
Log.w(TAG, "Plotter does not have api connectivity.");
counter++;
}
}
Log.w(TAG, "Plotter is connected-" + Boolean.toString(plotter.hasApiClientConnectivity()));
plotter.beginManagedLocationRequests(null);
}
}
}
来源:https://stackoverflow.com/questions/31840260/android-get-location-and-send-data-to-remote-server-only-while-condition-is-tr