Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Network call on alarm times out

I have an app that uses AlarmManager to schedule a repeating alarm every X amount of time. When my receiver receives the Intent, it has to make a http request.

The alarm itself works fine and triggers when it should. The network call, however, starts timing out when the phone is not in use. To be more specific:

When I schedule it to fire every minute (bad practise, I know, but just to illustrate), the first 5-8 minutes the request succeeds. After that, I get a java.net.SocketTimeoutException: connect timed out. Sometimes it does succeed, but mostly this happens.

I tried setting the connect/read/write timeouts to a minute, but then I get this exception instead of the one above: java.net.ConnectException: Failed to connect to myapp.example.com/123.45.67.89:80.

My code:

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // Consider mApi and myBody to be initialised and valid
        mApi.myPostRequest(myBody).enqueue(new Callback<Void> {

            @Override
            public void onResponse(Call<Void> call, Response<Void> response) {
                //Does not get here
            }

            @Override
            public void onFailure(Call<Void> call, Throwable t) {
                t.printStackTrace();
            }
        }
    }
}

Things I've tried:

  • as stated before, increasing timeouts
  • acquiring a WakeLock in onReceive and releasing it when the call is done (added the permission)

Other information:

  • The alarm is set using alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), interval, pendingIntent); from my Activity.
  • I'm using Retrofit (2.1.0) for the network communication, but you could probably have guessed that from my code ;)

Any ideas on how to get the network call working when the phone is sleeping?

like image 702
jelsief Avatar asked Jun 19 '17 20:06

jelsief


4 Answers

You Should use JobService here, it has many constraint to handle different scenarios and also your Job is guaranteed to be executed by the system.

The problem here is of doze mode, and using JobService this can be resolved easily.

Implementation is also easy all you need to do is create a JobService and inside it's onStartJob() start your network thread and then just dispatch your job.

For more detail

https://developer.android.com/reference/android/app/job/JobService.html

like image 114
Ankit Khare Avatar answered Nov 13 '22 12:11

Ankit Khare


From https://developer.android.com/reference/android/content/BroadcastReceiver.html

As a general rule, broadcast receivers are allowed to run for up to 10 seconds before they system will consider them non-responsive and ANR the app. Since these usually execute on the app's main thread, they are already bound by the ~5 second time limit of various operations that can happen there (not to mention just avoiding UI jank), so the receive limit is generally not of concern. However, once you use {@goAsync }, though able to be off the main thread, the broadcast execution limit still applies, and that includes the time spent between calling this method and ultimately PendingResult.finish().

Further reading says

If you are taking advantage of this method to have more time to execute, it is useful to know that the available time can be longer in certain situations. In particular, if the broadcast you are receiving is not a foreground broadcast (that is, the sender has not used FLAG_RECEIVER_FOREGROUND), then more time is allowed for the receivers to run, allowing them to execute for 30 seconds or even a bit more.

(long work should be punted to another system facility such as JobScheduler, Service, or see especially JobIntentService),

You can try using @goAsync. Or you can switch your logic to JobIntentService

I haven't tested any of these.

like image 26
Nilesh Deokar Avatar answered Nov 13 '22 12:11

Nilesh Deokar


You have a basic mistake in your code - you cannot make requests (or any long running operations) in your broadcast receiver - it dies after ~10 sec so that might be the reason for some of your failures.

You should move the request logic to a service (IntentService) which you'll start from your broadcast receiver and make the request there.

That should work just fine.

like image 26
MarkySmarky Avatar answered Nov 13 '22 10:11

MarkySmarky


From developer docs : https://developer.android.com/reference/android/app/AlarmManager.html

The Alarm Manager holds a CPU wake lock as long as the alarm receiver's onReceive() method is executing. This guarantees that the phone will not sleep until you have finished handling the broadcast. Once onReceive() returns, the Alarm Manager releases this wake lock. This means that the phone will in some cases sleep as soon as your onReceive() method completes

In your code onReceive will return before mApi.myPostRequest(myBody).enqueue ...task will be executed, then this task probably will be never executed due the CPU will stop as soon onReceive returns.

You said you tested acquiring a WakeLock, but new Android 6.0 Doze mode ignores wakelocks

It seems that OnReceive will have to wait for the task to end

Some ideas:

Checking for some terminate flag in a loop with thread.sleep ?

If the task uses a Thread object then using thread.join() ?

like image 1
from56 Avatar answered Nov 13 '22 12:11

from56