I have curious problem, in my application, the GPS-positioning takes quite a long time (as it tends to do) and therefore I wanted to run it as its own thread while the user is making choices in other views.
My structure is that I have a "main" view that is always active in the background, then the user is shown a series of views in which he or she makes a few choices. In the end, the user is presented with a result based on the choices made and the current position. All of this works, but I wanted to move the GPS-bit to its own thread so that the user wouldn't have to wait for it to finish.
All of the user's choices and the GPS coordinates is stored in a singleton called CurrentLogEntry. All communication between the different parts of the program is performed through it.
I created a handleMessage(Message msg) override in the main view and then I implemented this function in Main.java:
void startGPSThread() {
Thread t = new Thread() {
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
boolean isDebug = CurrentLogEntry.getInstance().isDebug();
// Define a listener that responds to location updates
LocationListener locationListener = new LocationListener() {
public void onStatusChanged(String provider, int status, Bundle extras) {
/* This is called when the GPS status changes */
String tag = "onStatusChanged, ";
switch (status) {
case LocationProvider.OUT_OF_SERVICE:
Log.w(tag, "Status Changed: Out of Service");
break;
case LocationProvider.TEMPORARILY_UNAVAILABLE:
Log.w(tag, "Status Changed: Temporarily Unavailable");
break;
case LocationProvider.AVAILABLE:
break;
}
}
public void onProviderEnabled(String provider) {
}
public void onProviderDisabled(String provider) {
// This is called if the GPS is disabled in settings.
// Bring up the GPS settings
Intent intent = new Intent(
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
}
public void onLocationChanged(Location location) {
// Once a location has been received, ignore all other position
// updates.
locationManager.removeUpdates(locationListener);
locationManager.removeUpdates(this);
// Make sure that the received location is a valid one,
// otherwise show a warning toast and hit "back".
if (location == null) {
String warningString = "Location was unititialized!";
if (isDebug) {
Toast.makeText(getApplicationContext(),
warningString,
Toast.LENGTH_LONG).show();
}
KeyEvent kev = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
onKeyDown(KeyEvent.KEYCODE_BACK, kev);
}
CurrentLogEntry.getInstance().setUserLatitude(location.getLatitude());
CurrentLogEntry.getInstance().setUserLongitude(location.getLongitude());
//Send update to the main thread
int result = 0;
if (location.getLatitude() == 0 || location.getLongitude() == 0) {
result = -1;
}
messageHandler.sendMessage(Message.obtain(messageHandler, result));
}
};
#Override
public void run() {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 0, 0, locationListener);
// Wait until a position has bee acquired.
while(!CurrentLogEntry.getInstance().isReadyToCalculate()){
try {
wait(250);
} catch (InterruptedException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
};
t.start();
}
This function is called in Main.onCreate().
Unfortunately, this doesn't work at all. The program crashes as soon as it reaches locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener); and that has me completely stumped.
Can anyone enlighten me as to why this won't run as a background thread? Also, do I really need the wait-polling at the end to ensure that the thread stays alive until it receives its data? The GPS bit worked just fine before I wanted to put it in a thread of its own, so what in the world am I doing wrong?
From what i understand you have to make the Thread a Looper thread in order to run Location Updates in the background...however i am still trying to figure this out myself...when i figure out how to do this i will post it.
Probably have the same error that i received, which is can't create handler in thread where Looper.prepare() has not been called
Hope you get it working bud
Check the this thread and specifically for this answer. They deal with the same subject, location, and the same problem, running it from a Thread loop.
Related
I have following requirement in my app.
1. I select some files & click Upload button. On clicking Upload button, the app exits. (I am enqueueing the request & finishing the activity).
2. Once the app exits, these files need to get synced to server in background.
3. Also after certain time duration (I have set 16min interval), in background, I need to check if there is unsynced data, sync it in background.
4. If user is offline, the unsynced data should get synced in background once network connectivity is established.
I have used WorkManager's Periodic Work Request to achieve this.
But on testing it on my Asus Zenfone3, I observed that :
1. If my device goes into sleep mode, the doWork() does not get execute after that.
If I turn off mobile data & then turn it on, the doWork() does not get executed immediately.
I have set 1 constraint : .setRequiredNetworkType(NetworkType.CONNECTED). It gets executed only after its interval is completed. I need immediate sync on network.
Also sometimes, clicking on upload button does not immd call doWork().
Is WorkManager (2.3.2) the right approach I am following. Any guideline to achieve above req. will be appreciated.
WorkManager is the preferred solution on Android for deferrable tasks. If you want an immediate action, you may want to look into implementing the logic you want in a Foreground Service.
Regarding what happens when your device goes into doze mode, WorkManager tries to minimize battery consumption and respect it. Again, if you need to have your tasks to be executed even when the device is supposed to go into doze mode, WorkManager is not the right solution. But you need to have a good reason to justify this behavior as it is going to have a negative impact on battery life.
To be able to understand what is happening with the Asus Zenfone3, it would be helpful to see how you enqueue the WorkRequest and WorkManager's logs.
If you read android documentation work manager is not a good way to schedule recurring tasks and when the device goes to sleep mode the thread work manager is working on is also put to sleep to conserve battery try using handler instead.
https://developer.android.com/reference/android/os/Handler
in the code i have posted i have used handler and firebase jobdispatcher to log user location on fixed interval of 30 seconds and it works even when device is in sleep mode
//file with handler and firebase job dispatcher
public class GetterService extends JobService {
private HandlerThread handlerThread = new HandlerThread("HandlerThread");
private Handler threadHandler;
int delay = 30*1000;
Runnable runnable;
LocationManager locationManager;
#Override
public boolean onStartJob(#NonNull com.firebase.jobdispatcher.JobParameters job) {
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
handlerThread.start();
threadHandler = new Handler();
threadHandler.postDelayed(runnable= () -> {
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
Location repeatedLocations = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
Log.i("location logger", "getting Longitude as "+ repeatedLocations.getLongitude() +" getting Latitide as "+repeatedLocations.getLatitude());
threadHandler.postDelayed(runnable,delay);
},delay);
return false;
}
#Override
public boolean onStopJob(#NonNull com.firebase.jobdispatcher.JobParameters job) {
return false;
}
}
//file with location getter
public class LocationGetter extends AppCompatActivity implements LocationListener {
LocationManager locationManager;
#BindView(R.id.latitudeVal)
TextView latitudeVal;
#BindView(R.id.longitudeVal)
TextView longitudeVal;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 2);
} else {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1, 1, this);
}
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher
(new GooglePlayDriver(getApplicationContext()));
Job notificationJob = dispatcher.newJobBuilder()
.setService(GetterService.class)
.setRecurring(true).setTag("jid")
.setLifetime(Lifetime.UNTIL_NEXT_BOOT)
.setTrigger(Trigger.executionWindow(1, 10000))
.setReplaceCurrent(false)
.build();
dispatcher.mustSchedule(notificationJob);
}
#OnClick(R.id.ping)
public void onPingClick() {
}
#Override
public void onLocationChanged(Location location) {
latitudeVal.setText("" + location.getLatitude() + "");
longitudeVal.setText("" + location.getLongitude() + "");
Log.i("dfjh", "" + location.getLongitude() + " " + location.getLatitude());
}
#Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
#Override
public void onProviderEnabled(String s) {
}
#Override
public void onProviderDisabled(String s) {
}
#Override
protected void onDestroy() {
super.onDestroy();
}
}
I have an AlarmManager that triggers every 30 minutes to an IntentService, and this intent service is to get the user's location each time. I have 2 ways of getting location: first it checks the getLastKnownLocation(), and if it was within the last 2 minutes it uses that, this part works perfectly.
The second way is if the last location was old or returns null, in which I want to get the new location once. For some reason this never calls onLocationChanged(). This results in my IntentService not returning coordinates much of the time, it only returns them if the getLastKnownLocation() was recent.
Here is my code for how it is set up, why is it that if I want to get a new location, it is never called? Check the comments in the code to see what is called and what is never called.
LocationListener locationListener;
LocationManager locationManager;
private final double MIN_COORD_DIFF = 0.0006;
public CoordinateAlarmReceiver(){
super("CoordinateAlarmReceiver");
}
#Override
protected void onHandleIntent(Intent intent) {
//THIS IS CALLED CORRECTLY
MyLog.i("coordinate alarm received");
locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
Location lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
//if last location was in past 2 minutes, use that
if(lastLocation != null && lastLocation.getTime() > Calendar.getInstance().getTimeInMillis() - 2 * 60 * 1000) {
//THIS IS CALLED CORRECTLY
storeLocation(lastLocation);
MyLog.i("Last location was recent, using that");
}
else { //otherwise get new location
//THIS IS CALLED CORRECTLY
MyLog.i("Last location was old, getting new location");
locationListener = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
//THIS IS NEVER CALLED
MyLog.i("Got new coordinates");
storeLocation(location);
locationManager.removeUpdates(this);
}
#Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
#Override
public void onProviderEnabled(String s) {
}
#Override
public void onProviderDisabled(String s) {
}
};
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);
}
}
You should use a Service instead of a IntentService. The IntentService finish when complete the task however, the service is running and can listen the location change events. Try to use a service.
Hope it helps you!!
*Please have a look at the below written source code lines and suggest:-*
In the below mentioned code lines I am trying to request for GPS and Network location in every 60 seconds.
if (gps_enabled)
{
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 60000, 0, locationListenerGps);
}
if (network_enabled)
{
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 60000, 0,
locationListenerNetwork);
}
Below mentioned is the code for location change listeners:-
LocationListener locationListenerGps = new LocationListener()
{
public void onLocationChanged(Location location)
{
mCurrentGpsLocation = location;
String userCurrentGpsLocation = findUserAddress(mCurrentGpsLocation, mPreviousGpsLocation);
mPreviousGpsLocation = location;
for(int i = 0, size = mLocationUpdateListeners.size(); i<size; i++)
{
LocationUpdateListener listener = mLocationUpdateListeners.get(i);
listener.recieveGpsNotification(userCurrentGpsLocation);
}
}
public void onProviderDisabled(String provider) {
}
public void onProviderEnabled(String provider) {
}
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
LocationListener locationListenerNetwork = new LocationListener()
{
public void onLocationChanged(Location location)
{
mCurrentNetworkLocation = location;
String userCurrentNetworkLocation =findUserAddress(mCurrentNetworkLocation, mPreviousNetworkLocation);
mPreviousNetworkLocation = location;
for(int i = 0, size = mLocationUpdateListeners.size(); i<size; i++)
{
LocationUpdateListener listener = mLocationUpdateListeners.get(i);
listener.recieveNetworkNotification(userCurrentNetworkLocation);
}
}
public void onProviderDisabled(String provider) {
}
public void onProviderEnabled(String provider) {
}
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
And on receiving location change event for GPS and Network, I simply call my listeners recieveGpsNotification() and recieveNetworkNotification() methods respectively.
Logic of recieveGpsNotification() are as follows :-
public void recieveGpsNotification(String gpsLocation)
{
sendEmail();
}
**Problems :-
1) I expect call to sendMail() should come after every 60 sec but I am receiving notifications very frequently and after 4-5 notifications my application crashes. Please help if you see any error in implementation logic.
2) Will i also receive notifications for GPS even when my activity is in pause state or in stopped state, because i want to receive notification even when my activity is in background or it is stopped?
Additional Query :-
Whenever android framework provides us with GPS location updates, every time notification come through a different thread or is it the thread that request for notifications?
Thanks in Advance.
**
Try adding a min distance to each update. Also consider grouping the updates together for your sendEmail() method
Move your Location Listener logic into a Service that runs in the background. This will keep the updates coming. However, be aware that GPS updates are awful for battery life and users will not be happy to see GPS trying to get a lock all the time.
I believe those updates happen on the thread in which they are declared. So you should move your findUserAddress() method and other complex operations to a different thread.
Also, You should only use one method to get GPS updates. The normal use case is to try GPS Adapter, if not enabled or preset, fall through to use the Network listener. Since you have both you will be getting both updates on different intervals.
Details:
I have a service that needs to do the following:
- listen constantly for GPS locations, with 50 meters as parameter (works fine) and send it to a server
- each 60 seconds, if no GPS location was read, start listening for a Network location and send it to the server
This may sound weird, but this is the project requirement. So the user is constantly being tracked using GPS. When he stops, or GPS is not locked, or is inside a building, every 60 seconds start a quick Network location read, and send this location to the server. As the service uses the same thread as the main app, each server update is done in its own thread. And another important thing: each location read should be sent one after another, so for instance if the user is driving, and multiple reads are done, each should be send to the server, after the previous one has been sent. That's why I decided to use ScheduledExecutorService as I can submit threads and they will be execute one after another.
Here is the code:
private ScheduledExecutorService scheduleTaskExecutor;
Handler locationHandler = new Handler();
private Location lastNetworkLocation;
#Override
public void onStartCommand() {
scheduleTaskExecutor = Executors.newSingleThreadScheduledExecutor();
//prepare to execute Network reading every 60 seconds
scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
initNetworkReadings();
//usually a network location read is done almost instantly
//however after 5 seconds I check if a location has been read by the Network listener inside the locationRunnable
locationHandler.postDelayed(locationRunnable, 5000);
}
}
}, 60, 60, TimeUnit.SECONDS);
locationRunnable = new Runnable() {
#Override
public void run() {
if (lastNetworkLocation !=null){
//send location to the server if valid
}
lastNetworkLocation = null;
}
}
}
private void initNetworkReadings() {
locationManager.removeUpdates(locationListenerNetwork);
try {
isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
} catch (Exception ex) {
}
if (isGpsEnabled) {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork);
}
}
LocationListener locationListenerNetwork = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
lastNetworkLocation = location;
// stop listening for Network locations
locationManager.removeUpdates(locationListenerNetwork);
}
...
}
Each time I read a GPS location I add it to the threads queue as:
scheduleTaskExecutor.submit(new Runnable() {
#Override
public void run() {
updateLocationOnServer(readLocation);
}
});
The problem I have is that the Network location listener never gets onLocationChanged() called,when I use it like in the code above, in the Runnable. BUT if I add on service start, the initNetworkReadings(), I get onLocationChanged() fired right away. So I believe it has something to do with being used in scheduleAtFixedRate.
What do you think it could be the problem ? Anything bad in the way I thought of the workflow ?
What if you try to set up your repitition with a Handler and a Runnable instead of scheduleAtFixedRate()?
Handler h = new Handler();
Runnable run = new Runnable(){
//Do your repeititive work here!
h.postDelayed(run, 60 * 1000);
};
h.post(run);
Same problem,
I have a background service that detect locations.
If network is avaible, it uses networks, otherwise it uses gps.
It works well with many smartphones
( nexus s 4.1, galaxy nexus 4.2, galaxy note) , but with Galaxy s3 jb (4.1) network location never rises any location.
Same code with gps locations, works just fine.
In my code i'm using an IntentService to listen to location updates (either GPS or network updates) and this IntentService is triggered when an event is received, so it is started with startService() from any activity.
public class AddLocationService extends IntentService implements LocationListener {
/*My code here*/
}
#Override
protected void onHandleIntent(Intent intent) {
if(getOldLoc() == null)
{
//Get a new location
this.locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, TIME_INTERVAL_GPS, 0, this);
this.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, TIME_INTERVAL_GPS, 0, this);
Log.d(AddLocationService.TAG, "Network listener started");
this.time_start_listening = System.currentTimeMillis();
mTimerThread mTimerRunnable = new mTimerThread();
this.timerThread = new Thread(mTimerRunnable);
this.timerThread.start();
}
else
/*REUSE OLD LOCATION*/
}
Now my problem is : When two events start this IntentService and the second starts it while the first one is still requesting for updates, I will like the second one to wait until first one is fully finished (location found OR timer thread finishes).
However whenever the IntentService is executed a second time (first instance still running), it prints me the log and does as it was executing in parallel.
However I thought that the main goal of IntentService was that it is something sequential so a second intent would have to wait until first one is done...
Did I missunderstood something ?
It appears that your onHandleIntent method is not blocking the thread it is executing on, so it will return quickly and allow the second intent to be processed. Not only that, but any callbacks from the LocationManager to that thread are unlikely to be processed as the background thread is likely to be killed when onHandleIntent is finished.
If you really want to use IntentService to manage your intent queue then you will need to do your location handling on its own thread, and join the IntentService thread to the location thread whilst it is waiting for the location callback.
Heres a bit of code that demonstrates the idea:
public class TestService extends IntentService {
private static final String TAG = "TestService";
private Location mLocation = null;
public TestService() {
super(TAG);
}
#Override
public void onHandleIntent(Intent intent) {
Log.d(TAG, "onHandleIntent");
if (mLocation == null) {
Log.d(TAG, "launching location thread");
LocationManager locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
LocationThread thread = new LocationThread(locationManager);
thread.start();
try {
thread.join(10000);
} catch (InterruptedException e) {
Log.d(TAG, "timeout");
return;
}
Log.d(TAG, "join finished, loc="+mLocation.toString());
} else {
Log.d(TAG, "using existing loc="+mLocation.toString());
}
}
private class LocationThread extends Thread implements LocationListener {
private LocationManager locationManager = null;
public LocationThread(LocationManager locationManager) {
super("UploaderService-Uploader");
this.locationManager = locationManager;
}
#Override
public void run() {
Log.d(TAG, "Thread.run");
Looper.prepare();
this.locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
this.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
Looper.loop();
}
#Override
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub
Log.d(TAG, "onLocationChanged("+location.toString()+")");
mLocation = location;
Looper.myLooper().quit();
}
#Override
public void onProviderDisabled(String arg0) {
}
#Override
public void onProviderEnabled(String arg0) {
}
#Override
public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
}
}
}
Of interest in there is the Looper that runs a message loop on the thread (to allow handling of the callbacks).
Given the effort required to do this with IntentService it might be worthwhile investigating deriving from Service instead and managing your own intent queue.
onHandleIntent is in it's own thread already. You don't (shouldn't) create on in there. It's all handled by IntentService for you.
Thanks a million, that is exactly what I needed to handle the location requests.
Thank you for explanations and making it clear for me, I wasn't very familiar with all the looper concept, now I understand it better !
In case someone need the same kind of thing, don't forget to stop the thread looper if your location thread is not stopping naturally (end of time on join(millis)), by adding this in onHandleIntent() :
if(thread.isAlive())
{
thread.onThreadStop();
try{
thread.interrupt();
}catch (Exception e) {
Log.d(TAG, "Exception on interrupt: " + e.getMessage());
}
}
after thread.join(yourTime), so for example if you didn't find any location update you still stop the thread after a certain time. And on method onThreadStop() :
/*We remove location updates here and stop the looper*/
public void onThreadStop()
{
this.locationManager1.removeUpdates(this);
handleLocationChange(AddLocationService.this.currentBestLocation);
Looper.myLooper().quit();
}
However I thought I saw my two intents being treated the first time I ran this code, but now only the first one is treated when I have multiple intents while still requesting location updates.
My method onHandleIntent() seems to execute correctly, stops the thread after the time specified and even displays the very last Log (last statement of the method) but the second intent is not executed...
Would you have any idea why ?