Android JobIntentService seems to be restarted - android

I have a broadcast receiver that listens for power connection events. Whenever the device is connected to power, I attempt to transfer files from the APP to a Server in a machine running Ubuntu. The files are transferred over Bluetooth. Since the transfer of files is important, if for any reason the transfer has an error, or the connection is not successful in a first attempt, I retry it up to 6 times allowing 3 minutes between attempt.
In the beginning, I was using an asynctask which was simply maintained alive as long as we still have retries available and the file transfer has not been successfully done. I read, that having an asynctask in a broadcast receiver is not a good practice which makes total sense, especially since I'm forcing the task to run for long periods of time. Therefore, I decided to change to a JobIntentService such that every time a power connection event was captured by the receiver, I would issue the job that will transfer files to my computer. Within the job, right after the file transfer is finished or failed, I would set an alarm that will send a pending intent to the broadcast and call the job again.
I was running this and I have noticed that (as different from before) I've gotten too many "Connection reset by peer" errors during the transfer, which makes me wonder if the Job is being stopped before its completed or something like that?. Those errors used not to happen in my previous implementation. Then, I also noticed that for some reason the OS seems to have launched the JobIntentService again by itself (there was no event that launched it) which caused inconsistencies on my code and caused me to lose some files (I'm not supposed to allow multiple instances of this job running at the same time)
My question is, why do you think the service was restarted? is it possible for the JobIntentService to be finished and restarted by the OS during the BT transfer? The files are heavy so they take several minutes to transfer from the app to the machine. I was thinking of trying a foreground service instead of the JobIntent and having a notification for the service or going back to my previous implementation.
Any suggestions?
This is how I call the Intent Job.
FileTransferJob.isJobAlreadyRunning = true;
Intent intent = new Intent(context, FileTransferJob.class);
intent.putExtra(TRANSFER_DATA_RETRIES, retries);
FileTransferJob.enqueueWork(context,intent);
This is the JobIntentService class
public class FileTransferJob extends JobIntentService {
/**
* Unique job ID for this service.
*/
public static boolean isJobAlreadyRunning = false; //This flag will remain true as soon as this JOB is called and as long as retries are still available
public static final int JOB_ID = 1000;
public static int MAX_NUM_OF_RETRIES = 6;//How many times are we going to retry to send the data
private int MINUTES_TO_WAIT = 3; //The minutes we wait between each attempt
public String TAG = "FileTransferJob";
/**
* Convenience method for enqueuing work in to this service.
*/
public static void enqueueWork(Context context, Intent work) {
enqueueWork(context, FileTransferJob.class, JOB_ID, work);
}
#Override
protected void onHandleWork(Intent intent) {
int retriesRemaining = intent.getIntExtra(TRANSFER_DATA_RETRIES,1); //Get the number of retries we have. Default to 1 (this one)
Log.d(TAG, "onHandleWork: About to attempt transfer with remaining retries " + String.valueOf(retriesRemaining));
try {
BluetoothFileTransfer btio = new BluetoothFileTransfer();
Log.d(TAG, "onHandleWork: About to send data over Bluetooth");
btio.sendData(FileTransferJob.this.getApplicationContext());
FileTransferJob.isJobAlreadyRunning = false; //Success, then this is no longer running
Log.d(TAG, "onHandleWork: The data has been sent over Bluetooth");
}catch (Exception e){
Log.d(TAG, "onHandleWork: There was a problem with the BT transfer: " + e.getMessage());
retriesRemaining--; //We reduce the number of retries we have
//If no more retries available, simply do nothing
if (retriesRemaining > 0) {
Log.d(TAG, "onHandleWork: Setting up alarm. Retries ramaining: " + String.valueOf(retriesRemaining));
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent alarmIntent = new Intent(this.getApplicationContext(), DataCollectReceiver.class);
alarmIntent.setAction(TRANSFER_DATA);
alarmIntent.putExtra(TRANSFER_DATA_RETRIES, retriesRemaining);
PendingIntent alarmPendingIntent = PendingIntent.getBroadcast( this.getApplicationContext(), PENDING_INTENT_CODE_FILE_TRANSFER_JOB, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
int totalTime = MINUTES_TO_WAIT*60*1000;
if(alarmManager != null){
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + totalTime,
alarmPendingIntent);
Log.d(TAG, "onHandleWork: Alarm is set, waiting " + String.valueOf(totalTime) + " minutes for next attempt...");
}else{
Log.d(TAG, "onHandleWork: Alarm could not be set. Alarm manager is NULL");
}
}else{
Log.d(TAG, "onHandleWork: There are no more retries");
FileTransferJob.isJobAlreadyRunning = false;
}
}
}
#Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: The file transfer JOB has finished");
}
}
The logcat. The highlighted section shows what I believe is the OS creating a new instance of the JobService and running it.

Let me try to answer it as i have noticed this behavior. The JobIntentService/JobService/Worker will run only for 10 mins after that they will be stopped and you can get a call back on onStopJob/onStopCurrentWork in case of JobService/JobIntentService and OnStopped in case of Worker.
Though the android document has explained this behavior for Worker only but JobService/JobIntentServie both behaves the same way
A Worker is given a maximum of ten minutes to finish its execution and return a ListenableWorker.Result. After this time has expired, the Worker will be signalled to stop.
Hence i can assume that your task is not finished within 10 mins and Android is destroying the JobIntentService.
Now the thing is that All of these Jobservice/JobIntentService/Worker are started again (If stopped prematurely) after the exponential backoff time i.e. 30secs , 1 min, 2 mins,4 mins...
Although the weird part is that the old thread which died after running 10 mins started as explained but as the call back comes again on HandleWork it starts another thread again which duplicates the work done by the thread and that is why i think you see inconsistencies.
The suggestion is that you break your work in such a way that can be finished withing the 10 mins window. Or We can wait for Google team to fix this.

Related

Intent Service not working in doze mode

One of my peer developer has written an intent service that makes an API call and then sleeps for 2 mins. After waking up, it sends again.
Below is the code:
public class GpsTrackingService extends IntentService {
....
#Override
protected void onHandleIntent(Intent intent) {
do{
try{
//make API call here
//then go to sleep for 2 mins
TimeUnit.SECONDS.sleep(120);
} catch(InterruptedException ex){
ex.printStackTrace();
}
} while (preferences.shouldSendGps()); //till the user can send gps.
}
....
}
Manifest
<service android:name=".commons.GpsTrackingService" />
This is working fine when the phone is active. However, whenever the phone goes into doze mode it fails to wake.
Will using alarm manager with WAKE permission solve this?
I have just got the code base and need to fix this within today. It'll be great if someone can help.
As the documentation says:
In Doze mode, the system attempts to conserve battery by restricting
apps' access to network and CPU-intensive services. It also prevents
apps from accessing the network and defers their jobs, syncs, and
standard alarms.
Periodically, the system exits Doze for a brief time to let apps
complete their deferred activities. During this maintenance window,
the system runs all pending syncs, jobs, and alarms, and lets apps
access the network.
In few words, while in Doze mode the system suspends network accesses, ignores Wake Locks, stops acquiring data from sensors, defers AlarmManager jobs to the next Doze maintenance window (which are progressively less frequently called), also WiFi scans, JobScheduler jobs and Sync adapters do not run.
Neither setAndAllowWhileIdle() nor setExactAndAllowWhileIdle() can fire alarms more than once per 9 (?) minutes, per app.
And it seems that the Foreground Services are also involved into this "Doze Drama", at least in MarshMellow (M).
To survive in this situation, tons of applications need to be at least rewiewed. Can you imagine a simple mp3 player which stops playing music when the device enters in Doze Mode?
Doze mode starts automatically, when the device is unplugged from the power supply and left on the table for about 1 hour or so, or even earlier when the user clicks the power button to power down the screen, but I think this could depend by the device manufacturer too.
I tried a lot of countermeasures, some of them really hilarious.
At the end of my tests I reached a possible solution:
One possible (and maybe the only) way to have your app running even when the host device is in Doze mode, is basically to have a ForegroundService (even a fake one, doing no jobs at all) running in another process with an acquired partial WakeLock.
What you need to do is basically the following (you could create a simple project to test it):
1 - In your new project, create a new class which extends Application (myApp), or use the
main activity of the new project.
2 - In myApp onCreate() start a Service (myAntiDozeService)
3 - In myAntiDozeService onStartCommand(), create the Notification
needed to start the service as a foreground service, start the
service with startForeground(id, notification) and acquire the
partial WakeLock.
REMEMBER! This will work, but it is just a starting point, because you have to be careful with the "Side Effects" this approach will generate:
1 - Battery drain: The CPU will work for your app forever if you
don't use some strategy and leave the WakeLock always active.
2 - One notification will be always shown, even in the lockscreen,
and this notification cannot be removed by simply swiping it out, it
will be always there until you'll stop the foreground service.
OK, let's do it.
myApp.java
public class myApp extends Application {
private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";
#Override
public void onCreate() {
super.onCreate();
// start foreground service
startForeService();
}
private void stopForeService() {
Intent service = new Intent(this, myAntiDozeService.class);
service.setAction(STOPFOREGROUND_ACTION);
stopService(service);
}
private void startForeService(){
Intent service = new Intent(this, myAntiDozeService.class);
service.setAction(STARTFOREGROUND_ACTION);
startService(service);
}
#Override
public void onTerminate() {
stopForeService();
super.onTerminate();
}
}
myAntiDozeService.java
public class myAntiDozeService extends Service {
private static final String TAG = myAntiDozeService.class.getName();
private static boolean is_service_running = false;
private Context mContext;
private PowerManager.WakeLock mWakeLock;
private static final int NOTIFICATION_ID = 12345678;
private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";
#Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!is_service_running && STARTFOREGROUND_ACTION.equals(intent.getAction())) {
Log.i(TAG, "Received Start Foreground Intent ");
showNotification();
is_service_running = true;
acquireWakeLock();
} else if (is_service_running && STOPFOREGROUND_ACTION.equals(intent.getAction())) {
Log.i(TAG, "Received Stop Foreground Intent");
is_service_running = false;
stopForeground(true);
stopSelf();
}
return START_STICKY;
}
#Override
public void onDestroy() {
releaseWakeLock();
super.onDestroy();
}
private void showNotification(){
Intent notificationIntent = new Intent(mContext, ActivityMain.class);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(mContext)
.setContentTitle("myApp")
.setTicker("myApp")
.setContentText("Application is running")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
// starts this service as foreground
startForeground(NOTIFICATION_ID, notification);
}
public void acquireWakeLock() {
final PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
releaseWakeLock();
//Acquire new wake lock
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG+"PARTIAL_WAKE_LOCK");
mWakeLock.acquire();
}
public void releaseWakeLock() {
if (mWakeLock != null && mWakeLock.isHeld()) {
mWakeLock.release();
mWakeLock = null;
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
AndroidManifest.xml changes.
In the AndroidManifest.xml add this permission:
<uses-permission android:name="android.permission.WAKE_LOCK" />
Don't forget to add the name of your app in the <application> tag:
<application
....
android:name=".myApp"
....
And finally add your foreground service running into another process:
<service
android:name=".myAntiDozeService"
android:process=":MyAntiDozeProcessName">
</service>
A couple of notes.
In the previous example, the notification created, when clicked,
opens the ActivityMain activity of your test project.
Intent notificationIntent = new Intent(mContext, ActivityMain.class);
but you can use another kind of intent too.
To test it, you have to add some job to be performed into your
ActivityMain.java, for example some repeating alarm (which was
normally stopped when the device falls in Doze Mode), or a ripetitive
network access, or a timed tone played, or.... whatever you want.
Remember that the job performed by the main activity has to run
forever because to test this AntiDoze you need to wait at least 1
hour to be sure the device enters in Doze Mode.
To enter in Doze mode, the device has to be quiet and unplugged, so
you can't test it while you are debugging. Debug your app first,
check that everything is running then stop it, unplug, restart the
app again and leave the device alone and quiet on your desk.
The adb commands suggested by the documentation to simulate Doze
and StandBy modes could and could not give you the right results
(it depends, I suppose, by the device manufacturer, drivers, bla
bla). Please make your tests in the REAL behaviour.
In my first test, I used an AlarmManager and a tone generator to play a tone every 10 minutes just to understand that my app was still active.
And it is still running from about 18 hours, breaking my ears with a loud tone exactly every 10 minutes. :-)
Happy coding!
One of my peer developer has written an intent service that makes an API call and then sleeps for 2 mins. After waking up, it sends again.
Only have a service running while it is actively delivering value to the user. Sitting around for two minutes, watching the clock tick, is not actively delivering value to the user.
Will using alarm manager with WAKE permission solve this?
That depends on what you mean by "solve this". You can use AlarmManager to request to get control every two minutes so that you can do work. While the device is in Doze mode, you will not actually get control every two minutes, but once per maintenance window.

AlarmManager not waking phone up in Air Mode (onReceive delayed)

Problem is that onReceive method of BroadcastReceiver usually "called" by AlarmManager is delayed until device is awoken by the user.
This has never happened to me, only information I have was is from the report sent by user. In the log I saw that in the first case onReceive method call was delayed by almost 2 hours and in the second one by about 20 minutes. In both situations alarm (and onReceive) has started just after the phone was awoken by the user.
Problem has occured twice in two consecutive days and user states it has never happened before. Only distinctive change in phone's settings was that Air Mode was enabled.
My code:
Alarm is set like:
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
am.set(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
Logger.log("posting alarm " + id + " for " + formatTime(timeInMillis);
Broadcast Receiver's onReceiveMethod:
#Override
public void onReceive(Context context, Intent intent) {
Logger.initialize(context, "AlarmReceiver");
...
}
Logs received from the user:
481. 20/05 13:00:04 v89: posting alarm 4 for 7:0 (in 17:59)
486. 21/05 08:58:00 v89: logger initialized again from AlarmReceiver
536. 21/05 09:04:54 v89: posting alarm 4 for 7:0 (in 21:55)
541. 22/05 07:22:24 v89: logger initialized again from AlarmReceiver
Is it possible for Air Mode to block phone's awakening somehow? Can I prevent it? Or maybe it is something completely different? Any help is welcomed.
Device is Samsung Galaxy SIII (GT-I9305) with Android 4.1.2
Edit:
Just in case that delay could be somehow caused by the Logger, here's its code. mHandler is created with use of HandlerThread, so I believe it can't block onReceive, right?
public synchronized static void initialize(Context context, String src) {
if (mInstance == null) {//wasn't null
...
} else {
Logger.log("logger initialized again from " + src);
}
}
public synchronized static void log(final String text) {
Log.d(TAG, text);
if (mInstance != null && mInstance.mLoggingEnabled) {
mInstance.mHandler.post(new Runnable() {
#Override
public void run() {
//some database operations
}
});
}
}
If you want to schedule your tasks with the specified interval don't use flags AlarmManager.RTC and AlarmManager.RTC_WAKEUP with method alarm.setRepeating(...). Because in this case alarm will be bounded to the device's real time clock. So changing the system time may cause alarm to misbehave. You must use flags AlarmManager.ELAPSED_REALTIME or AlarmManager.ELAPSED_REALTIME_WAKEUP. In this case SystemClock.elapsedRealtime() will serve as a basis for scheduling an alarm.
The code will look like:
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + checkIntervalMillis, checkIntervalMillis, pendingIntent);
If you want your long running task to be executed when device in the sleep mode I recommend to use WakefulIntentService library by CommonsWare: https://github.com/commonsguy/cwac-wakeful

loop in IntentService

I have an infinite loop in my IntentService to update my view once every 30 seconds based on the input from the main activity.
public class IntentServiceTest extends IntentService {
String Tag = "IntentServiceTest";
String ACTION_RCV_MESSAGE = "com.jsouptest8.intent.action.MESSAGE";
public IntentServiceTest(){
super("IntentServiceTest");
Log.d(Tag, "IntentServiceTest constructor");
}
#Override
protected void onHandleIntent(Intent intent) {
// TODO Auto-generated method stub
Log.d(Tag, "in onHandleIntent");
String url = intent.getStringExtra("URL");
Document doc;
int i=0;
try{
while(true){
Log.d(Tag, "entered try block...");
Log.d(Tag, "url = "+url);
doc = Jsoup.connect(url)
.get();
Log.d(Tag, "past Jsoup.connect");
Element data = doc.select("table").get(1).attr("bgcolor", "#f4f36f");
Log.d(Tag, data.toString());
Log.d(Tag, data.text());
Log.d(Tag, "creating intent...");
Intent broadcastIntent = new Intent();
Log.d(Tag, "setting action...");
broadcastIntent.setAction(ACTION_RCV_MESSAGE);
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
broadcastIntent.putExtra("OUTPUT", data.toString());
Log.d(Tag, "sending broadcast: "+(i++));
sendBroadcast(broadcastIntent);
Thread.sleep(30*1000);
}
}
catch(StackOverflowError e){
Log.d(Tag, "in StackOverflowError block...");
Log.d(Tag, "creating intent...");
Intent broadcastIntent = new Intent();
Log.d(Tag, "setting action...");
broadcastIntent.setAction(ACTION_RCV_MESSAGE);
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
broadcastIntent.putExtra("OUTPUT", "系統忙線中, 請稍後再試");
Log.d(Tag, "sending broadcast...");
sendBroadcast(broadcastIntent);
}
catch(Exception e){
Log.d(Tag, "in catch Exception block...");
onHandleIntent(intent);
}
}
}
The problem is, I am stuck in this loop. Even if I kill the main activity and then return to it to enter a new input and the IntentService still returns based on the old input.
I need to know how I can update myself from the URL every 30 second without getting stuck. Thanks!
An IntentService is meant to finish of a task and return. It does this task in a new thread. Do not use while loop in IntentService. Your IntentService will get killed after sometime. I am telling this from personal experience. I tried using a while loop in it. And at the end of the while loop I used sleep(60000) i.e 1 minute. But I found that my IntentService was killed after sometime.
I would recommend you not to use an AlarmManager for 30 seconds, as some have siggested. Because 30 seconds is too short. it will drain the battery. For AlarmManager use a minimum 1 minute with RTC.
If you still want it to be 30 seconds, use a service. In the service use your logic. But do that in a separate thread i.e spawn a new thread in your Service and used while loop there and sleep(). And do not forget to use startForeGround. This reduces the probabilty of android killing your service greatly.
Using a while statement inside an IntentService, or any kind of Service for that matter is a bad idea. It is especially a bad idea inside an IntentService because the IntentService is supposed to finish a task and then get terminated automatically, you are in essence defeating the whole purpose of using an IntentService.
I would recommend to remove the loop in the IntentService and to use an alarm to wake up the IntentService every 30 seconds. That way, your service gets called every 30 seconds for sure and for the time that it is not processing, it can actually go back to sleep. Moreover, to handle cases where a new call to the IntentService is received while the IntentService is servicing an older request, you can add code to the onStartCommand method of your IntentService to see if the call should be enqueued for processing or ignored altogether.
Set an alarm using this method:
public void setRepeating (int type, long triggerAtMillis, long
intervalMillis, PendingIntent operation)
Link: http://goo.gl/E9e6
For a more efficient approach, use setInexactRepeating (but that does not guarantee a 30 second wakeup)
PS. We don't normally override the onStartCommand of an IntentService but it can be done if your app really that functionality.
in this link you'll find a service that updates itself using a timer
Keep Service running
If your comfortable with the while loop just write an if statement that exists the loop
if(thisIsTrue)
{
break; // this will exit the loop!
}
It would be better that you keep the loop or timer or any such running task in the MainActivity itself and execute IntentService everytime. Because IntentService will perform task and finish itself everytime or queue the task to be delivered further.
From the Docs -
IntentService will receive the Intents, launch a worker thread, and
stop the service as appropriate.
It uses work queue processor pattern to maintain the task.

Android - Calling a system service in fixed intervals from another service

As the question implies, I am wondering how I could write a thread that would call a system service and then wait a certain amount of time before calling said system service's function that calls back to onReceive from a registered BroadcastReceiver.
In other words, I am trying to call the Wifi scanning service (registering a BroadcastReceiver with IntentFilters) within my custom service so I can get the current SSID's available. I know what I will end up doing with the received data, which is not relevant to this question. However, I will need to wait a certain amount of time before calling startScan again within onReceive, and that is where I am trying to determine the best course of action.
I managed to try calling the wifi scanner in this fashion within my thread:
private Object _lock = new Object();
private final long SLEEP_TIME = 15000; //Scan every 15 secs
private final long WIFI_SCAN_TIMEOUT = 60000; //One minute timeout for getting called back, otherwise, initiate a new scan.
#Override
public void run() {
while(running){
//Start a new scan;
wifiSearchComplete = false;
_wifiMan.startScan();
while(!wifiSearchComplete){
synchronized(_lock){
try{
_lock.wait(WIFI_SCAN_TIMEOUT);
} catch (InterruptedException ie){
Log.d(TAG, TAG+".run() caught " + ie.getMessage() +" when trying to sleep for " + (WIFI_SCAN_TIMEOUT/1000) +"secs.");
Thread.currentThread().interrupt();
}
}
if(!wifiSearchComplete){
synchronized(_lock){
//Try scanning again since we didn't get called back at all;
_lock.notify();
}
}
}
}
}
public boolean isRunning(){
return running;
}
public void stop(){
synchronized(_lock){
running = false;
//unregister receivers and cleanup
_lock.notify();
}
}
#Override
public void onReceive(Context context, Intent intent) {
synchronized(_lock){
wifiSearchComplete = true;
//iterate through our SSID's
try{
_lock.wait(SLEEP_TIME);
} catch (InterruptedException ie){
Log.d(TAG, TAG+".onReceive() caught " + ie.getMessage() +" when trying to sleep for " + (SLEEP_TIME/1000) +"secs.");
Thread.currentThread().interrupt();
}
_lock.notify();
}
}
However, even though it waits every 15 seconds before it scans again, when trying to exit my test activity (calling onDestroy) it blocks the main thread for the sleep time, before it unbinds the service. In other words, is this the appropriate way of trying to accomplish what I want to do without blocking, or do I have to simply create a BroadcastReceiver and call Thread.sleep at the end of onReceive before calling starting a new scan?
You should implement an IntentService. In your implementation override onHandleIntent() to do you WiFi scan.
Next, use the AlarmManager to schedule sending an Intent to your IntentService at some interval. Make up your own action name: "diago.intent.scan_wifi" or something like that. If you use one of the "inexact repeating intervals" (such as 15min) then the Android OS will schedule all the other services at the same time to minimize the number of times the phone must wake from sleep mode.
Finally, Implement a BroadcastReceiver to respond to ACTION_BOOT_COMPLETED and call your code to schedule the AlarmManager. This will start your service on boot.
This way when the AlarmManager sends the intent, your service will be loaded, execute the scan and then exit.

AlarmManager and BroadcastReceiver instead of Service - is that bad ? (Timeout)

BACKGROUND INFO:
I need to update some data from the web, about every hour or so, even when my app is closed. The update of the data itself takes about 40 seconds to 1 minute. It is then saved as a Serializable to a file. This file is read when my app starts.
THIS IS THE APPROACH I TOOK FOR THE MOMENT (not using a Service)
use the AlarmManager and BroadcastReceiver like this :
private void set_REFRESH_DATA_Alarm(){
mContext = Main.this;
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
broadcast_intent = new Intent(mContext,
RepeatingAlarmReceiver_REFRESH_DATA.class);
pendingIntent = PendingIntent.getBroadcast(mContext, 0, broadcast_intent, 0);
// do a REFRESH every hour, starting for the first time in 30 minutes from now ...
Calendar now = Calendar.getInstance();
long triggerAtTime = now.getTimeInMillis()+ (1 * 30 * 60 * 1000); // starts in 30 minutes
long repeat_alarm_every = (1 * 60 * 60 * 1000); // repeat every 60 minutes
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtTime,
repeat_alarm_every, pendingIntent);
}
My RepeatingAlarmReceiver_REFRESH_DATA.class takes care of updating the Data from the Web:
public class RepeatingAlarmReceiver_REFRESH_DATA extends BroadcastReceiver {
public static Context mContext;
ConnectivityManager mConnectivity;
#Override
public void onReceive(Context context, Intent intent) {
mContext = context;
// if Network connection is OK (Wifi or Mobile) then Load data ...
mConnectivity = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
Log.i("Hub",
"mConnectivity.getNetworkInfo(0)="
+ mConnectivity.getNetworkInfo(0));
Log.i("Hub",
"mConnectivity.getNetworkInfo(1)="
+ mConnectivity.getNetworkInfo(1));
if ((mConnectivity.getNetworkInfo(0).getState() == NetworkInfo.State.CONNECTED)
|| (mConnectivity.getNetworkInfo(1).getState() == NetworkInfo.State.CONNECTED)) {
Log.i("Hub", "Connectivity OK ...");
Refresh_HIST_DATA();
} else {
// else Show Dialog "No network connection" ...
Log.i("Hub",
"No network connection for the moment... will try again later!");
}
}
// =========================================================================
private void Refresh_HIST_DATA() {
Log.i("Hub", "Refresh_HIST_DATA()... Starting ...");
// etc...
}
}
In the Manifest I have :
<receiver android:name="com.cousinHub.myapp.RepeatingAlarmReceiver_REFRESH_DATA" android:process=":remote" />
PROBLEM :
The alarm gets fired on time and the update starts but then after about 10 seconds it stops (Timeout):
06-25 11:55:05.278:
WARN/ActivityManager(76): Timeout of
broadcast BroadcastRecord{44bb4348
null} -
receiver=android.os.BinderProxy#44bcc670
06-25 11:55:05.278:
WARN/ActivityManager(76): Receiver
during timeout: ResolveInfo{44bb42c0
com.cousinHub.myapp.RepeatingAlarmReceiver_REFRESH_DATA
p=0 o=0 m=0x0}
06-25 11:55:05.278: INFO/Process(76):
Sending signal. PID: 819 SIG: 9
06-25 11:55:05.298:
INFO/ActivityManager(76): Process
com.cousinHub.myapp:remote (pid 819)
has died.
ps: strangely enough, this "Timeout" does not happen after about 10 seconds on my HTC Hero (still on Android 1.5 - API Level 4) but well on my Nexus One (2.1-update1)
Questions :
Why this timeout ? Any easy way to avoid this ?
Did I set up my BroadcastReceiver correctly in the manifest ? Do I need to add something (to avoid this timeout) ?
Should I absolutely go for a Service for this kind of "Refresh from Web" functionality ? (considering this article : http://www.androidguys.com/2009/09/09/diamonds-are-forever-services-are-not/)
If YES (I should switch to a service): Any good snippets of code/tutorial for this ...
As allways, thanks for your help.
H.
Why this timeout ?
You are running on the main application thread. You cannot run on the main application thread for more than a few seconds. Also, while doing this, you are harming the performance of the device (because you are running with foreground priority), such as causing frame-rate loss in games or videos.
Any easy way to avoid this ?
Don't do significant work (>100ms) on the main application thread. Have your BroadcastReceiver delegate to an IntentService, perhaps a WakefulIntentService.
Did I set up my BroadcastReceiver
correctly in the manifest ?
Please please please please please get rid of the android:process=:remote. You do not need it, it is not helping you, and it is degrading performance of the device even further.
Should I absolutely go for a Service
for this kind of "Refresh from Web"
functionality ? (considering this
article :
http://www.androidguys.com/2009/09/09/diamonds-are-forever-services-are-not/)
If YES (I should switch to a service):
Any good snippets of code/tutorial for
this ...
IMHO, yes. Then again, I wrote that blog post. For an example, see the WakefulIntentService project.
For information, I've tried with a new thread and it works when on Wifi (takes about 1'30" to update the data when phone is asleep, it doesn't get 'killed' !
//let's try with a new separate thread ?
new Thread(new Runnable() {
public void run() {
Refresh_HIST_DATA();
}
}).start();
but NOT when on Mobile (GPRS), as it gets killed after about 10 secs!
It's half-a-solution for the moment and I will try CommonsWare's solution for a cleaner/more sustainable approach...
Let's see if the new thread solution works allways fine or was just luck (I've tested only during a couple hours) ...
If anyone else has another suggestion, please do post it.
Instead of thread. You can start a AsyncTask from your broadcast receiver onRecive() method. This will not block the UI thread. I myself have done same in my projects which is of same nature i.e. It has to post data every 1 hour.
public void onReceive(Context context, Intent intent) {
// start your Asynctask from here. which will post data in doInBackground() method
}

Categories

Resources