0

Je construis une application qui doit envoyer l'emplacement de l'utilisateur à un serveur distant (Pusher dans ce cas). L'objectif est de mettre à jour leur emplacement sur une carte en temps quasi réel, mais seulement quand ils sont sur un travail, sinon l'application n'aura pas besoin de suivre leur emplacement.android - Obtenir l'emplacement et envoyer des données au serveur distant seulement lorsque la condition est vraie

J'ai besoin des mises à jour de localisation pour rester actives si elles quittent l'activité où elles ont accepté le travail (et ont donc été placées sur la carte) et si elles quittent complètement l'application. Une fois qu'ils ont atteint leur destination, je souhaite arrêter ce suivi en arrière-plan.

J'ai regardé le composant Service d'Android mais je ne suis pas sûr si c'est ce dont j'ai besoin pour cela. Les mises à jour doivent se produire en arrière-plan indéfiniment, mais uniquement lorsque l'utilisateur est affecté à un Job (les mises à jour démarrent lorsqu'elles acceptent un travail, et se terminent lorsqu'elles atteignent leur destination).

Un service lié serait-il le meilleur pour cela? Si oui, un certain code en ce qui concerne ce problème serait GRANDEMENT apprécié, autant de ce que je peux trouver est générique et fait des choses comme retourner des nombres entiers aléatoires.

Voici le code que je veux exécuter en arrière-plan:

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"); 
      } 
     } 

    } 
} 

MISE À JOUR

C'est ce que je suis venu avec après le passage à Google Play API de localisation de service. J'ai testé en laissant cette activité (et l'application en général) et tout continue à fonctionner correctement, prouvant les mises à jour de localisation jusqu'à ce que je clique sur le bouton pour les faire arrêter.

commentaires seraient appréciés sur ce 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()); 
     } 
    } 
} 
+0

commencer une nouvelle activité qui peut fonctionner dans le sol en arrière quand un nouvel emploi est attribué et mis ce Coode dans cette activité –

+0

"Une nouvelle activité qui peut fonctionner en arrière-plan" - ne serait-ce pas un "Service"? – NightMICU

+0

ouais mais les services qui durent longtemps peuvent être tués vous devez faire attention à de tels résultats –

Répondre

1

Une option consiste à abandonner l'API LocationManager et passer à la FusedLocationProviderAPI. L'utilitaire FusedLocationProviderAPI vous permet de demander des mises à jour intermittentes de position tout en effectuant des demandes d'emplacement dans le gestionnaire le plus efficace.

code qui pourrait vous aider à « point » dans la bonne direction ressemblerait à ceci:

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; 
} 
} 

Et vous auriez alors un service de base que vous enregistrerait dans le Traceur GPS ci-dessus:

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); 
    } 
} 

}

+0

Wow, c'est assez complexe. Dans votre cas, vous stockez l'emplacement de l'utilisateur toutes les heures - j'espère pouvoir le faire toutes les trois secondes environ. L'heure à laquelle les mises à jour d'emplacement sont envoyées ne dépassera probablement pas 20 minutes, donc je ne suis pas très inquiet de la mort d'un 'Service'. Dans votre cas, je vois un 'AlarmManager',' IntentService', et 'BroadcastReceiver' - y a-t-il un moyen de simplifier cela, donc je travaille avec moins de points d'entrée? Merci! Et en passant, j'utilise actuellement Google Play Services. – NightMICU

+0

Il convient également de noter que je n'aurai jamais besoin de récupérer des données de tout ce qui manipule le code en arrière-plan. Mais j'ai besoin de transmettre initialement des données, telles qu'un ID de travail, un nom, etc. – NightMICU

+0

L'AlarmManager est uniquement utilisé pour télécharger des points par lots dans la base de données. En ce qui concerne les intervalles d'interrogation de lieu, vous le faites dans PendingIntent. – andrewdleach