I have a location tracking app. I have a Foreground service that when the app goes into the background, it continues to get the location. That part works fine. If I output the location I can see the different points and correct timestamps.
While in the background I need to POST that data to an API endpoint. My GPSHeartbeat class is a singleton and it exposes a function to let me update the Singletons location property.
While in the foreground, everything works fine. When in the background, the location IS updated, but the singleton has the last location from BEFORE it went into the background.
My APICommunicator is firing in the background on its interval like it should, it just doesn't have the correct Location.
Here is the broadcast receiver that is responsible for listening to the Foreground services location change.
This works fine in the background and in the foreground. It is successfully getting the updated location.
private void onNewLocation(Location location)
{
Log.i(TAG, "onNewLocationRec'd: " + location);
mLocation = location;
// Notify anyone listening for broadcasts about the new location.
Intent intent = new Intent(ACTION_BROADCAST);
intent.putExtra(EXTRA_LOCATION, location);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
// Update notification content if running as a foreground service.
if (serviceIsRunningInForeground(this)) {
mNotificationManager.notify(NOTIFICATION_ID, getNotification());
}
}
The BroadcastReceiver is an inner class of an Activity called HomeActivity. This gets the CORRECT location from the service. If I output the log, it is the same as what the Service broadcast.
public class HomeActivity extends AppCompatActivity
{
private GPSHeartbeat mGPSHeartbeat;
private GPSReceiver myReceiver;
private LocationUpdatesService mService = null;
private boolean mBound = false;
private final ServiceConnection mServiceConnection = new ServiceConnection()
{
#Override
public void onServiceConnected(ComponentName name, IBinder service)
{
LocationUpdatesService.LocalBinder binder = (LocationUpdatesService.LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName name)
{
mService = null;
mBound = false;
}
};
private void GPSBeginRequestingUpdates()
{
//Wait 5 seconds to spin up
(new Handler()).postDelayed(this::StartGPSUpdates, 5000);
}
private void StartGPSUpdates()
{
mService.requestLocationUpdates();
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
myReceiver = new GPSReceiver();
setContentView(R.layout.activity_home);
mGPSHeartbeat = GPSHeartbeat.instance(getApplicationContext()).setInterval(6);
}
#Override
protected void onStart()
{
super.onStart();
bindService(new Intent(getApplicationContext(), LocationUpdatesService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop()
{
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
super.onStop();
}
#Override
protected void onResume()
{
super.onResume();
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(myReceiver, new IntentFilter(LocationUpdatesService.ACTION_BROADCAST));
GPSBeginRequestingUpdates();
}
#Override
protected void onPause()
{
LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(myReceiver);
super.onPause();
}
private class GPSReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
Location location = intent.getParcelableExtra(LocationUpdatesService.EXTRA_LOCATION);
if (location != null) {
Log.i(TAG, "\nonReceived New Location: " + GPSUtils.getLocationText(location));
GPSHeartbeat.instance(context.getApplicationContext()).SetLocation(location);
}
}
}
}
The Singleton. The SetLocation() does receive the correct location. It is only during my POST request that the APICommunicator is using the GPSHeartbeat's old location. Even though it was just updated.
How do I make sure I update to the correct location?
public class GPSHeartbeat extends Service {
private static String TAG = "GPSHeartbeat";
private static volatile GPSHeartbeat _instance;
private final WeakReference<Context> mContextRef;
private Boolean isRunning = false;
private int mInterval;
private Location mLocation;
private Handler mHandler;
private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
private Future mLongRunningTaskFuture;
private Runnable mStatusChecker = new Runnable()
{
#Override
public void run()
{
try {
tick(); //this function can change value of mInterval.
}
finally {
if (isRunning()) {
// 100% guarantee that this always happens, even if your update method throws an exception
mHandler.postDelayed(mStatusChecker, mInterval);
}
}
}
};
private GPSHeartbeat(Context context)
{
mContextRef = new WeakReference<>(context.getApplicationContext());
}
public static GPSHeartbeat instance(Context context)
{
if (_instance == null) {
_instance = new GPSHeartbeat(context);
} else {
if (!context.equals(_instance.mContextRef.get())) {
_instance = null;
_instance = new GPSHeartbeat(context);
}
}
return _instance;
}
public void SetLocation(Location loc)
{
Log.i(TAG, "setLocation(): " + loc);
this.mLocation = loc;
}
public GPSHeartbeat setInterval(int interval)
{
this.mInterval = interval * 1000;
return this;
}
public void start()
{
if (isRunning()) return;
mHandler = new Handler();
mLongRunningTaskFuture = mExecutorService.submit(mStatusChecker);
mStatusChecker.run();
isRunning = true;
}
public void stop()
{
if (mHandler != null) {
mHandler.removeCallbacks(mStatusChecker);
}
if (mLongRunningTaskFuture != null) {
//kill the task:
try {
mLongRunningTaskFuture.cancel(true);
mLongRunningTaskFuture = null;
mHandler = null;
}
catch (Exception e) {
Log.e(TAG, "Unable to cancel task: " + e.getLocalizedMessage());
}
}
isRunning = false;
}
public Location currentLocation()
{
return mLocation;
}
public boolean isRunning()
{
return isRunning;
}
private void tick()
{
// Fire off the APICommuncator.Post() method
}
#Nullable
#Override
public IBinder onBind(Intent intent)
{
return null;
}
}
The APICommuncator
public class APICommuncator
{
private static String TAG = "APICommuncator";
private static volatile APICommuncator _instance;
private final WeakReference<Context> mContextRef;
private GPSHeartbeat _gpsHeartbeat;
private APICommuncator(Context context)
{
mContextRef = new WeakReference<>(context.getApplicationContext());
_gpsHeartbeat = GPSHeartbeat.instance(context.getApplicationContext());
}
public static APICommuncator i(Context context)
{
if (_instance == null) {
_instance = new APICommuncator(context);
} else {
if (!context.equals(_instance.mContextRef.get())) {
_instance = null;
_instance = new APICommuncator(context);
}
}
return _instance;
}
public void Post(){
// Do the background thing and grab
// getLocationNode() which gets the OLD location before it went to the background.
}
private JSONObject getLocationNode()
{
Location location = _gpsHeartbeat.currentLocation();
if (location == null) {
return null;
}
JSONObject node = null;
try {
node = new JSONObject();
node.put("Latitude", String.valueOf(location.getLatitude()));
node.put("Longitude", String.valueOf(location.getLongitude()));
node.put("HAccuracy", String.valueOf(location.getAccuracy()));
node.put("VAccuracy", String.valueOf(location.getAccuracy()));
node.put("Altitude", String.valueOf(location.getAltitude()));
node.put("Speed", String.valueOf(location.getSpeed() * 2.237));
node.put("Heading", String.valueOf(location.getBearing()));
node.put("Timestamp", String.valueOf((location.getTime() / 1000)));
}
catch (JSONException | NullPointerException e) {
e.printStackTrace();
}
return node;
}
}
In the Manifest:
<service
android:name=".gpsheartbeat.GPSHeartbeat"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
<service
android:name=".gpsheartbeat.LocationUpdatesService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="location" />
Actually I don't see that you are using foreground service. Not foreground service would be killed very soon after the application goes background. Plus communication with API should be in the scope of foreground service because activity could be killed by the system.
Related
I'm creating a cordova plugin that should return the device's position using mapbox. The issue is that LocationEngineResult.getLastLocation() is always returning null.
I use locationEngine to retrieve the location with locationCallback.
Permissions are also checked.
***
LocationEngine locationEngine;
AltGeolocationLocationCallback callback = new AltGeolocationLocationCallback(this);
***
#SuppressLint("MissingPermission")
private void initLocationEngine() {
locationEngine = LocationEngineProvider.getBestLocationEngine(this.cordova.getActivity().getApplicationContext());
LocationEngineRequest request = new LocationEngineRequest.Builder(DEFAULT_INTERVAL_IN_MILLISECONDS)
.setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
.setMaxWaitTime(DEFAULT_MAX_WAIT_TIME).build();
locationEngine.requestLocationUpdates(request, callback, Looper.getMainLooper());
locationEngine.getLastLocation(callback);
}
private static class AltGeolocationLocationCallback extends Activity
implements LocationEngineCallback<LocationEngineResult> {
private final WeakReference<AltGeolocation> activityWeakReference;
private AltGeolocationLocationCallback contexto = this;
AltGeolocationLocationCallback(AltGeolocation activity) {
this.activityWeakReference = new WeakReference<>(activity);
}
#Override
public void onSuccess(LocationEngineResult result) {
AltGeolocation activity = activityWeakReference.get();
if (activity != null) {
Location location = result.getLastLocation();
Log.d(LOG_TAG, "result: " + location );
}
}
#Override
public void onFailure(#NonNull Exception exception) {
AltGeolocation activity = activityWeakReference.get();
}
#Override
public void onPointerCaptureChanged(boolean hasCapture) {
}
}
}
How can I run service in background in oreo? This service class is working good in all Android versions below Oreo, and I declared this service in manifest. In my activity class I launch with startservice(getApplicationContext,ExoService.class).
public class ExoService extends Service {
private static Context context;
private static ItemRadio station;
private static ExoService service;
public static SimpleExoPlayer exoPlayer;
private static Uri uri;
private WifiManager.WifiLock wifiLock;
static ProgressTask task;
/*binder return null*/
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
task = new ProgressTask();
task.execute();
return START_STICKY;
/*Alrady set Start Sticky*/
}
/*here is initilize service class*/
static public void initialize(Context context, ItemRadio station) {
ExoService.context = context;
ExoService.station = station;
Log.e("inwhich", "");
}
/*this is my service instance*/
static public ExoService getInstance() {
if (service == null) {
service = new ExoService();
}
return service;
}
/*Oncreate method */
#Override
public void onCreate() {
super.onCreate();
try {
TrackSelector trackSelector = new DefaultTrackSelector();
LoadControl loadControl = new DefaultLoadControl();
exoPlayer = ExoPlayerFactory.newSimpleInstance(this, trackSelector, loadControl);
} catch (NullPointerException e) {
e.printStackTrace();
}
}
/*public void stop() {
if (exoPlayer != null && exoPlayer.getPlayWhenReady()) {
exoPlayer.stop();
exoPlayer.release();
exoPlayer = null;
this.wifiLock.release();
this.stopForeground(true);
}
}*/
public void start() {
if (exoPlayer != null) {
exoPlayer.setPlayWhenReady(true);
}
}
/*after some second ondstroy method call in oreo.*/
public void onDestroy() {
if (exoPlayer != null) {
exoPlayer.stop();
}
Log.e("Destroyed", "Called");
}
/*public void pause() {
if (exoPlayer != null) {
exoPlayer.stop();
// exoPlayer.setPlayWhenReady(false);
}
}*/
public ItemRadio getPlayingRadioStation() {
return station;
}
Async task for decoding songs url:
#SuppressLint("StaticFieldLeak")
private class ProgressTask extends AsyncTask<String, Void, Boolean> {
protected void onPreExecute() {
}
protected Boolean doInBackground(final String... args) {
/*boolean bool = true;*/
try {
uri = Uri.parse(station.getRadiourl());
DefaultBandwidthMeter bandwidthMeterA = new DefaultBandwidthMeter();
DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, getString(R.string.app_name)), bandwidthMeterA);
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
MediaSource audioSource = new ExtractorMediaSource(uri, dataSourceFactory, extractorsFactory, null, null);
/* exoPlayer.addListener(eventListener);
MediaSource videoSource = new HlsMediaSource(uri, dataSourceFactory, 1, null, null);*/
final LoopingMediaSource loopingSource = new LoopingMediaSource(videoSource);
if (station.getRadiourl().endsWith(".m3u8")) {
exoPlayer.prepare(loopingSource);
} else {
exoPlayer.prepare(audioSource);
}
/*exoPlayer.setPlayWhenReady(true);*/
} catch (IllegalArgumentException | IllegalStateException | SecurityException | NullPointerException e1) {
e1.printStackTrace();
}
return true;
}
#SuppressLint("WifiManagerPotentialLeak")
#Override
protected void onPostExecute(final Boolean success) {
try {
if (success) {
wifiLock = ((WifiManager) context.getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "RadiophonyLock");
wifiLock.acquire();
exoPlayer.setPlayWhenReady(true);
} else {
/*Toast.makeText(context, context.getString(R.string.internet_disabled), Toast.LENGTH_SHORT).show();*/
}
} catch (NullPointerException e) {
e.printStackTrace();
}
/* dialog.dismiss();*/
}
}
}`
After some time, the ondestroy method gets called automatically in oreo. How can I handle this?
Since Oreo limits the use of background service you need to start a foreground service. Here's the documentation about restrictions of services in Oreo. A quote of it:
Android 8.0 introduces the new method startForegroundService() to
start a new service in the foreground. After the system has created
the service, the app has five seconds to call the service's
startForeground() method to show the new service's user-visible
notification. If the app does not call startForeground() within the
time limit, the system stops the service and declares the app to be
ANR.
Putting a service in foreground basically involves attaching the service to a notification which is visible to the user:
service.startForeground(NOTIFICATION_ID, notification);
The startForeground service is available since API 5 whereas startForegroundService comes with Oreo, so you need to check the API level of the device and start the service accordingly.
I have a service listening for UDP packets that is bound to my MainActivity (which is the only activity in the app). The service runs on its own thread and I can see the UDP messages as well as the parsed messages in logcat. I created a setParsedMessage() and a public getParsedMessage() in order to get the parsed string and send it to my main activity in order to change a TextView and an ImageView depending on what the parsed message is, however it does not appear to be retrieving the String for some reason. I read about this method on the Developer.Android website, however I've also seen something about using Handler to do this instead. Here is my code:
MainActivity:
public class MainActivity extends Activity {
AlertAssignments mAlertAssignments;
Button startListeningButton;
boolean started;
int counter;
boolean mBound = false;
Context context;
ListenerService mListenerService;
TextView mTextView;
TextView mBlinkView;
ImageView mImageView;
private StartListening _StartListeningTask;
String messageFromService = "";
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//start listener service
Intent listenerServiceIntent = new Intent(MainActivity.this, ListenerService.class);
this.bindService(listenerServiceIntent, mConnection, Context.BIND_AUTO_CREATE);
mImageView = (ImageView) findViewById(R.id.image_view);
mTextView = (TextView) findViewById(R.id.alert_text);
mBlinkView = (TextView) findViewById(R.id.blinking_text);
Animation mAnimation = new AlphaAnimation(0.0f, 1.0f);
mAnimation.setDuration(50);
mAnimation.setStartOffset(20);
mAnimation.setRepeatCount(Animation.INFINITE);
mAnimation.setRepeatMode(Animation.REVERSE);
mBlinkView.startAnimation(mAnimation); //animation value
mAlertAssignments = new AlertAssignments();
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
ListenerService.LocalBinder binder = (ListenerService.LocalBinder) service;
mListenerService = binder.getService();
mBound = true;
if(mBound) {
Log.e("UDP", "Service has been bound successfully");
}
else {
Log.e("UDP", "Service has not been bound");
}
readFromService();
}
#Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
}
};
#Override
protected void onStart() {
super.onStart();
}
#Override
protected void onStop() {
super.onStop();
//unbind from service
if(mBound) {
this.unbindService(mConnection);
mBound = false;
}
}
private void readFromService() {
try {
Integer parsedMessage = Integer.valueOf(mListenerService.getParsedMessage());
mImageView.setImageResource(mAlertAssignments.alarmImages[parsedMessage]);
if(parsedMessage >= 10 && parsedMessage <= 19 && parsedMessage != 0) {
mTextView.setText(mAlertAssignments.alertTextMessages[parsedMessage]);
} else {
mBlinkView.setText(mAlertAssignments.alertTextMessages[parsedMessage]);
}
} catch(NumberFormatException e) {
e.printStackTrace();
}
}
}
I had read that using the public getter like this:
Integer parsedMessage = Integer.valueOf(mListenerService.getParsedMessage());
would allow me to access the string value of mListenerService.getParsedMessage, however I'm guessing that may only work for started services, not bound services.
AlertAssignments is a simple enumeration that uses ordinal arrays to bind images and Strings to values, so mImageView.setImageResource(mAlertAssignments.alarmImages[parsedMessage]) would set the ImageView to an image. Finally, here is the Service:
public class ListenerService extends Service{
public String the_alarm_S;
public String parsedMessage = "";
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
ListenerService getService() {
return ListenerService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
DatagramSocket socket;
Thread UDPBroadcastThread;
void startListenForUDPBroadcast() {
UDPBroadcastThread = new Thread(new Runnable() {
public void run() {
try {
while (shouldRestartSocketListen) {
try {
socket = new DatagramSocket(12001);
socket.setReuseAddress(true);
String message = "";
byte[] recvBuf = new byte[1024];
DatagramPacket packet = new DatagramPacket(recvBuf, 1024);
Log.e("UDP", "Waiting for UDP broadcast");
try {
socket.receive(packet);
Log.e("UDP", "Received Packet");
} catch (IOException e) {
e.printStackTrace();
}
message = new String(packet.getData());
Log.e("UDP", "Got UDB broadcast message: " + message);
setParsedMessage(message);
if(socket != null) {
socket.close();
}
} catch (SocketException e) {
e.printStackTrace();
}
}
//if (!shouldListenForUDPBroadcast) throw new ThreadDeath();
} catch (Exception e) {
Log.i("UDP", "no longer listening for UDP broadcasts cause of error " + e.getMessage());
}
}
});
UDPBroadcastThread.start();
}
private Boolean shouldRestartSocketListen = true;
private void setParsedMessage(String messageContents) {
the_alarm_S = messageContents;
String parseMessage[] = the_alarm_S.split("!!!");
Log.e("UDP", "Parsed message with value " + parseMessage[1]);
parsedMessage = parseMessage[1];
}
public String getParsedMessage() {
return parsedMessage;
}
private void stopListen() {
shouldRestartSocketListen = false;
if(socket != null) {
socket.close();
}
}
#Override
public void onCreate() {
startListenForUDPBroadcast();
}
#Override
public void onDestroy() {
stopListen();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
shouldRestartSocketListen = true;
startListenForUDPBroadcast();
Log.i("UDP", "Service started");
return START_STICKY;
}
}
Can someone give me the simplest method of getting the String from the service to the main activity, or if I already have it, where I am going wrong in using it? I would like to avoid having to rewrite my Service as an IntentService unless it's absolutely necessary to do so since this is a relatively simple object to pass to MainActivity
Thanks
You could try subscribing to the service. What I mean is pass some interface that the service calls to notify the activity about changes, here's an example I just tested:
A Subscriber interface
public interface ServiceSubscriber {
void messageCallback(String message);
}
Subscribe to the service using the Subscriber
public class TestService extends Service {
ArrayList<ServiceSubscriber> subscribers = new ArrayList<>();
private TestBinder testBinder = new TestBinder();
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(){
#Override
public void run() {
while(true){
//this is where you are receiving UDP packets
doStuff();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
return super.onStartCommand(intent, flags, startId);
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return testBinder;
}
private void doStuff() {
System.out.println("Service is doing stuff!");
//loop through your subscribers and notify them of your changes
//a loop here isn't very costly, if there aren't many subscribers
for (ServiceSubscriber subscriber : subscribers) {
subscriber.messageCallback("I'm doing stuff");
}
}
public class TestBinder extends Binder {
public TestService getService() {
return TestService.this;
}
}
public void subscribeToMessages(ServiceSubscriber subscriber) {
subscribers.add(subscriber);
}
public void unSubscribeToMessages(ServiceSubscriber subscriber) {
subscribers.remove(subscriber);
}
}
Now for the usual Binding Activity, where you define what you need to do with the Message Callback:
public class MainActivity extends AppCompatActivity {
private TestService testService;
private Subscriber subscriber;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
protected void onStart() {
super.onStart();
bindService(new Intent(this, TestService.class),serviceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
testService = ((TestService.TestBinder)service).getService();
subscriber = new ServiceSubscriber() {
#Override
public void messageCallback(String message) {
//I'm just printing out the message received
//Be careful if you need to do UI stuff to use a
//Handler
System.out.println(message);
}
}
testService.subscribeToMessages(subscriber );
}
#Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
Of course don't forget to unsubscribe on destroy.
Updating UI often doesn't break your app if you do it by using a handler
//activity fields
Handler handler
//in activity constructor
handler = new Handler();
//update UI by calling
handler.post(new Runnable(){
#Override
public void run(){
//update the UI here
}
EDIT: I forgot to keep a reference of the subscriber, to unsubscribe later. Changed from anonymous instance to a field.
Make below method to your sevice class:
private void sendMessage() {
Intent intent = new Intent("message");
intent.putExtra("message", your_message);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
And put the below code in your activity class:
#Override
public void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(this)
.registerReceiver(mMessageReceiver,
new IntentFilter("message"));
}
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String yourMessage = intent.getIntExtra("message",-1);
}
};
#Override
protected void onPause() {
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(mMessageReceiver);
super.onPause();
}
Note: -1 is for default value
I am new in using background service and retrofit library, i am getting no error, by debugging my app i come to know that its getting coordinates but not sending to server(in background service)
Any help will appreciated, thanks in advance, happy coding!
GPS Service
public class LocationUpdaterService extends Service
{
public static final int TWO_MINUTES = 120000; // 120 seconds
public static Boolean isRunning = false;
public LocationManager mLocationManager;
public LocationUpdaterListener mLocationListener;
public Location previousBestLocation = null;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mLocationListener = new LocationUpdaterListener();
super.onCreate();
}
Handler mHandler = new Handler();
Runnable mHandlerTask = new Runnable(){
#Override
public void run() {
if (!isRunning) {
startListening();
}
mHandler.postDelayed(mHandlerTask, TWO_MINUTES);
}
};
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
mHandlerTask.run();
return START_STICKY;
}
#Override
public void onDestroy() {
stopListening();
mHandler.removeCallbacks(mHandlerTask);
super.onDestroy();
}
private void startListening() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
if (mLocationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER))
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, mLocationListener);
if (mLocationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER))
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mLocationListener);
}
isRunning = true;
}
private void stopListening() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mLocationManager.removeUpdates(mLocationListener);
}
isRunning = false;
}
public class LocationUpdaterListener implements LocationListener
{
#Override
public void onLocationChanged(Location location) {
if (isBetterLocation(location, previousBestLocation)) {
previousBestLocation = location;
try {
// Script to post location data to server..
Call<Update> loginCall;
String deviceKey;
deviceKey = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
loginCall = MyApplication.getInstance().getAPI().update(deviceKey,String.valueOf(location.getLatitude()),String.valueOf(location.getLongitude()));
loginCall.enqueue(new Callback<Update>() {
#Override
public void onResponse(Call<Update> call, Response<Update> response) {
if(response.getClass() != null)
{
}
}
#Override
public void onFailure(Call<Update> call, Throwable t) {
}
});
}
catch (Exception e) {
e.printStackTrace();
}
finally {
stopListening();
}
}
}
#Override
public void onProviderDisabled(String provider) {
stopListening();
}
#Override
public void onProviderEnabled(String provider) { }
#Override
public void onStatusChanged(String provider, int status, Bundle extras) { }
}
protected boolean isBetterLocation(Location location, Location currentBestLocation) {
if (currentBestLocation == null) {
// A new location is always better than no location
return true;
}
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return true;
// If the new location is more than two minutes older, it must be worse
} else if (isSignificantlyOlder) {
return false;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(), currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and accuracy
if (isMoreAccurate) {
return true;
} else if (isNewer && !isLessAccurate) {
return true;
} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
return true;
}
return false;
}
/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) {
return provider2 == null;
}
return provider1.equals(provider2);
}
My Application
import android.app.Application;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class MyApplication extends Application {
private API api;
private OkHttpClient client;
private static MyApplication sInstance;
#Override
public void onCreate() {
super.onCreate();
sInstance = this;
configureAPI();
}
private void configureAPI() {
client = new OkHttpClient.Builder()
.connectTimeout(80, TimeUnit.SECONDS)
.writeTimeout(300, TimeUnit.SECONDS)
.readTimeout(80, TimeUnit.SECONDS)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Server.API_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
api = retrofit.create(API.class);
}
public API getAPI() {
return api;
}
public static MyApplication getInstance() {
return sInstance;
}
}
API
public interface API {
#FormUrlEncoded
#POST("updateLocation")
Call<Update> update(#Query("token") String token, #Query("lat") String latitude, #Field("long") String longitude);
}
Server
public class Server {
public static final String API_URL = "http://192.168.146.2:8090/";
public static final String REG_API_URL = "http://192.168.120.2:8090/";
public static final String SndMsg_API_URL = "http://192.168.120.2:8090/";
}
MainActivity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent serviceIntent = new Intent(getApplicationContext(), LocationUpdaterService.class);
startService(serviceIntent);
}
}
Your code looks pretty good. There are few things those might be causing problems. please check those.
First of all make sure compiler is going inside the "OnLocationChanged()" method.
Second thing make sure your web-service call method is of "Update" type. Because you are using "Update". It can be "Post".
Third thing is print the response in "OnFailure()" method, maybe it is going to failure.
I hope you will find the problem by checking these scenarios.
I am trying to create a service that will show a toast every second with the most recent running application. Every time I start the service, I get a NullPointerException. What can I do to avoid this?
public class CheckRunningActivity extends Service {
private static final String TAG = "CheckRunningActivity";
boolean checkApps;
private Timer mTimer = null;
private Handler mHandler = new Handler();
private ActivityManager am;
public static final long NOTIFY_INTERVAL = 1000; // 1 second
#Override
public void onDestroy() {
mTimer.cancel();
}
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
#Override
public void onCreate() {
Log.d(TAG, "I created it");
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
// cancel if already existed
if(mTimer != null) {
mTimer.cancel();
} else {
// recreate new
mTimer = new Timer();
}
// schedule task
mTimer.scheduleAtFixedRate(new TimeDisplayTimerTask(), 0, NOTIFY_INTERVAL);
}
class TimeDisplayTimerTask extends TimerTask {
#Override
public void run() {
// run on another thread
mHandler.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Its running");
String packageName = am.getRunningTasks(1).get(0).topActivity
.getPackageName();
Toast.makeText(getBaseContext(), packageName, Toast.LENGTH_LONG).show();
Log.d(TAG, "Make Toast");
}
});
}
}
}
you can't directly call toast from service . you need a handler for this. see answer here.
after looking at the code again, I realized that I had not properly initialized the Activity Manager.
Here is the corrected code...
public class CheckRunningActivity extends Service {
private static final String TAG = "CheckRunningActivity";
boolean checkApps;
private Timer mTimer = null;
private Handler mHandler = new Handler();
private ActivityManager am;
public static final long NOTIFY_INTERVAL = 1000; // 1 second
#Override
public void onDestroy() {
mTimer.cancel();
}
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
#Override
public void onCreate() {
Log.d(TAG, "I created it");
// cancel if already existed
if(mTimer != null) {
mTimer.cancel();
} else {
// recreate new
mTimer = new Timer();
}
// schedule task
mTimer.scheduleAtFixedRate(new TimeDisplayTimerTask(), 0, NOTIFY_INTERVAL);
}
class TimeDisplayTimerTask extends TimerTask {
#Override
public void run() {
// run on another thread
mHandler.post(new Runnable() {
#Override
public void run() {
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
Log.d(TAG, "Its running");
String packageName = am.getRunningTasks(1).get(0).topActivity.getPackageName();
Toast.makeText(getBaseContext(), packageName, Toast.LENGTH_LONG).show();
Log.d(TAG, "Make Toast");
}
});
}
}
}