Hi i need to call a method every 4 seconds, even when the device is sleeping, i use alarm manager with service Start_stick, the service name is TransactionService. the code works well when the device is active and the method is called every exact 4 second, but when the screen is locked and device sleep the calling becomes inaccurate. so the method is now called every 2 seconds, sometimes every 1 sec,5 ....
this is how i run the thread to call method every 4 seconds
AlarmManager mgr = (AlarmManager) getApplicationContext().getSystemService(
Context.ALARM_SERVICE);
Intent notificationIntent = new Intent(getApplicationContext(),
TransactionService.class);
PendingIntent pendingIntent = PendingIntent.getService(
getApplicationContext(), 0, notificationIntent, 0);
mgr.setRepeating(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis(), 4000, pendingIntent);
this is the log of calling the method when device is active and screen is on
12-30 13:23:00.565 17397-17479/com.ids.simcardrefill D/url: calling
12-30 13:23:04.565 17397-17537/com.ids.simcardrefill D/url:calling
12-30 13:23:08.565 17397-17411/com.ids.simcardrefill D/url:calling
12-30 13:23:12.565 17397-17655/com.ids.simcardrefill D/url:calling
and this is how the method is calling when device is sleeping
12-30 13:09:12.565 17397-17655/com.ids.simcardrefill D/url:calling
12-30 13:09:17.785 17397-17598/com.ids.simcardrefill D/url:calling
12-30 13:09:20.565 17397-17479/com.ids.simcardrefill D/url:calling
12-30 13:09:25.775 17397-17537/com.ids.simcardrefill D/url:calling
12-30 13:09:28.565 17397-17411/com.ids.simcardrefill D/url:calling
here the difference between calling is inaccurate: 2 seconds, 5 seconds, 3 seconds
this is how the service look like :
public int onStartCommand(Intent intent, int flags, int startId) {
mshared = PreferenceManager
.getDefaultSharedPreferences(getApplicationContext());
edit = mshared.edit();
hostname = mshared.getString(
getApplicationContext().getString(R.string.hostname), "0");
contin = true;
cost
= mshared.getString(getString(R.string.test), "0.09");
if (contin) {
getTransactions get = new getTransactions(getApplicationContext());
get.execute(hostname);
}
return START_STICKY;
}
`
any solution ??
You should crate a service for working in background: https://developer.android.com/guide/components/services.html
You should use Handler in order to implement every 4 second functionality.
Handler handler = new Handler();
Runnable test = new Runnable() {
#Override
public void run() {
//do work
handler.post(test, 4000); //wait 4 sec and run again
}
};
public void stopTest() {
handler.removeCallbacks(test);
}
public void startTest() {
handler.post(test,0); //wait 0 ms and run
}
EDIT: i have tried the code below and it works for me
MyService.java
public class MyService extends Service {
Handler handler;
Runnable test;
public MyService() {
handler = new Handler();
test = new Runnable() {
#Override
public void run() {
Log.d("foo", "bar");
handler.postDelayed(test, 100); //100 ms you should do it 4000
}
};
handler.postDelayed(test, 0);
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
AndroidManifest.xml
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
//some code
startService(new Intent(this, MyService.class));
}
And remember if you want start-stop functionality take loot at my first example.
The correct way to do this is using a Handler (as already mentioned in the other answer), but I will take the liberty to add a few points to it.
The problem
I had a similar situation, where the AlarmManager was firing erratically. Digging deeper into the issue led to me to understand that since the AlarmManager operation wakes up the CPU by holding a CPU wake-lock and is intensive on the battery (given that the device is inactive), the OS tries to batch different alarms from different apps and fires all pending alarms when the device wakes up. This leads to the erratic behaviour of the AlarmManager. The documentation also specifies that we shouldn't be using this to trigger events at exact timestamps. There are Android APIs which are supposed to work for exact intervals, eg AlarmManager.setExact(), but the OS optimises itself to ignore the exactness if the interval duration is less than a minute. [not documented, but speaking from my personal experiences]
The fix
I fixed the issue using a Handler only, as shared in the other answer. But there is a small caveat. In the edge case that the Handler is killed (due to any reason), it won't trigger itself and your polling would stop.
The caveat
The fallback is to keep a AlarmManager as well, running every minute to trigger the Handler back again in case it was stopped prematurely by the OS. So, you have a Handler running every n seconds. Store the timestamp of the last time the Handler was called in SharedPreferences. Have a backup AlarmManager running every x minutes (ideally x = 5*n, so that you don't miss more than 5 polling calls). The AlarmManager checks when the Handler last ran. If it's within the margin, the AlarmManager does nothing and reschedules itself after x minutes. If it's been more than x minutes, the Handler must have been killed by the OS and the AlarmManager starts the Handler back again.
Adding some code to give you context as well.
public class PollingAlarmReceiver extends WakefulBroadcastReceiver {
final long POLLING_FREQUENCY_MARGIN = 5 * 1000; //margin kept in case the System delays any threads
Context mContext = ServicesApp.getContext();
/*
Splash/BootReceiver starts the Alarm and the Handler for polling.
The Handler starts the polling service and schedules the next run after an delay of the polling interval.
Before starting the service, the Handler also checks when the service was last run and whether it is time for the next call or not (with a margin of 5 seconds [POLLING_FREQUENCY_MARGIN]).
The Handler should cover all the cases and run smoothly. In case it fails, the Alarm acts as a failsafe.
The Alarm runs at an interval of 1 minute checking when the Handler was last called.
If it is past the time of the next scheduled call (with a margin of 5 seconds [POLLING_FREQUENCY_MARGIN]), the Alarm starts the runnable and makes the Handler queue the next run.
*/
#Override
public void onReceive(Context context, Intent intent) {
if (mContext == null)
mContext = ServicesApp.getContext();
if (mContext == null)
mContext = context.getApplicationContext();
if (mContext != null) {
if (getLastPolledTimestamp(mContext) > 0 && (System.currentTimeMillis() > (POLLING_FREQUENCY_MARGIN + getPollingInterval(mContext) + getLastPolledTimestamp(mContext)))) {
startPollingHandler();
}
}
}
Runnable mPoller = new Runnable() {
#Override
public void run() {
if (mContext == null)
mContext = ServicesApp.getContext();
if (mContext != null) {
try {
if ((System.currentTimeMillis() >= (getPollingInterval(mContext)) - POLLING_FREQUENCY_MARGIN + getLastPolledTimestamp(mContext))) {
if (!isServiceRunning(PollingService.class, mContext)) {
mContext.getSharedPreferences(CommonLib.APP_SETTINGS, 0).edit().putLong(LAST_POLLED_TIMESTAMP, System.currentTimeMillis()).commit();
Intent service = new Intent(mContext, PollingService.class);
startWakefulService(mContext, service);
}
}
} finally {
ServicesApp.getHandler().postDelayed(mPoller, getPollingInterval(mContext));
}
}
}
};
public void startAlarmToCheckForHandler() {
if (mContext == null)
mContext = ServicesApp.getContext();
if (mContext != null) {
AlarmManager alarmMgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(mContext, PollingAlarmReceiver.class);
PendingIntent alarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
alarmMgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), 60 * 1000, alarmIntent);
}
}
public void startPollingHandler() {
mPoller.run();
}
public void cancelAlarm() {
if (mContext == null)
mContext = ServicesApp.getContext();
if (mContext != null) {
AlarmManager alarmMgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(mContext, PollingAlarmReceiver.class);
PendingIntent alarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
alarmMgr.cancel(alarmIntent);
}
}
}
P.S. : I have this code running on production for thousand of devices whose main functionality rely on the exactness of the polling and it seems to be working great for me.
Related
Google has its clock app, which includes its stopwatch. I'm currently trying to create in my app a (count-up) timer, or you can call it a stopwatch, that will be able to run in the background, and when it runs in the background I want it to also show a notification, that displays the time it counts and a "Stop" button (all of this happens in google clock app (see here)). For the timer in my app, I'm using a Handler that posts a Runnable, which is posting itself. I'm writing my app in Java.
the code defining the 'timer' (Handler and Runnable):
Handler timerHandler = new Handler();
Runnable timerRunnable = new Runnable() {
#Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
seconds = (millis / 1000) + PrefUtil.getTimerSecondsPassed();
timerHandler.postDelayed(this, 500);
}
};
my onPause function:
#Override
public void onPause() {
super.onPause();
if (timerState == TimerState.Running) {
timerHandler.removeCallbacks(timerRunnable);
//TODO: start background timer, show notification
}
PrefUtil.setTimerSecondsPassed(seconds);
PrefUtil.setTimerState(timerState);
}
How can I implement the background service and the notification in my app?
Edit
I've managed to succeed in creating a foreground service that runs my timer, but I have two problems:
When I run the app after something like 5 minutes, the notification shows up in a 10-second delay.
the notification stops updating after around 30 seconds from the time it starts/resumes (The timer keeps running in the background, but the notification won't keep updating with the timer).
Here's my Services code:
public class TimerService extends Service {
Long startTime = 0L, seconds = 0L;
boolean notificationJustStarted = true;
Handler timerHandler = new Handler();
Runnable timerRunnable;
NotificationCompat.Builder timerNotificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
public static final String TIMER_BROADCAST_ID = "TimerBroadcast";
Intent timerBroadcastIntent = new Intent(TIMER_BROADCAST_ID);
#Override
public void onCreate() {
Log.d(TAG, "onCreate: started service");
startForeground(1, new NotificationCompat.Builder(this, CHANNEL_ID).setSmallIcon(R.drawable.timer).setContentTitle("Goal In Progress").build());
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String goalName = intent.getStringExtra(PublicMethods.getAppContext().getString(R.string.timer_notification_service_current_goal_extra_name));
startTime = System.currentTimeMillis();
notificationJustStarted = true;
timerRunnable = new Runnable() {
#Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
seconds = (millis / 1000) + PrefUtil.getTimerSecondsPassed();
updateNotification(goalName, seconds);
timerHandler.postDelayed(this, 500);
}
};
timerHandler.postDelayed(timerRunnable, 0);
return START_STICKY;
}
public void updateNotification(String goalName, Long seconds) {
try {
if (notificationJustStarted) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
timerNotificationBuilder.setContentTitle("Goal In Progress")
.setOngoing(true)
.setSmallIcon(R.drawable.timer)
.setContentIntent(pendingIntent)
.setOnlyAlertOnce(true)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_MAX);
notificationJustStarted = false;
}
timerNotificationBuilder.setContentText(goalName + " is in progress\nthis session's length: " + seconds);
startForeground(1, timerNotificationBuilder.build());
} catch (Exception e) {
Log.d(TAG, "updateNotification: Couldn't display a notification, due to:");
e.printStackTrace();
}
}
#Override
public void onDestroy() {
timerHandler.removeCallbacks(timerRunnable);
PrefUtil.setTimerSecondsPassed(seconds);
super.onDestroy();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
And here is how I start it in my fragment:
private void startTimerService() {
Intent serviceIntent = new Intent(getContext(), TimerService.class);
serviceIntent.putExtra(getString(R.string.timer_notification_service_current_goal_extra_name), "*Current Goal Name Here*");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Objects.requireNonNull(getContext()).startForegroundService(serviceIntent);
}
}
UPDATE
When I run the app on google pixel emulator, I don't face any of the issues listed
There are 2 issues. I will try to solve both of them.
First issue
When I run the app after something like 5 minutes, the notification shows up in a 10-second delay.
For this, you need to update the notification with its code. Now, because it takes time to show up, show it in the activity where you start the service and then, pass the notification id to the service using its constructor. Using that id, update it in the service.
Let's hope that solves the first issue.
Second issue
the notification stops updating after around 30 seconds from the time it starts/resumes (The timer keeps running in the background, but the notification won't keep updating with the timer).
To solve that, you can clear the previous notification after 10 seconds by it's id. Then you can make a new random key for the notification( I'd prefer new Random().nextInt()) and then show it. But then you or anyone would say that there is so much sound when a notification comes. Just disable it this way when creating a channel:
notificationChannel.setSound(null, null);
NOTE: You might want to reinstall your app for it to work
If that seems complicated, see this:
Runnable running -> When 10 seconds done from previous notification display -> Clear the notification -> Make a new notification id -> show notification with that id -> Repeat
EDIT
This is the working code for me:
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import java.util.concurrent.TimeUnit;
public class TimerService extends Service {
Long startTime = 0L, seconds = 0L;
boolean notificationJustStarted = true;
Handler timerHandler = new Handler();
Runnable timerRunnable;
private final String CHANNEL_ID = "Channel_id";
NotificationManager mNotificationManager;
NotificationCompat.Builder timerNotificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID).setContentTitle(CHANNEL_ID);
#SuppressLint("InlinedApi")
#Override
public void onCreate() {
super.onCreate();
Toast.makeText(this, "created", Toast.LENGTH_SHORT).show();
String TAG = "Timer Service";
Log.d(TAG, "onCreate: started service");
startForeground(1, new NotificationCompat.Builder(TimerService.this, createChannel()).setContentTitle("Goal In Progress").setPriority(NotificationManager.IMPORTANCE_MAX).build());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String goalName = "Sample Goal";
Toast.makeText(this, "started", Toast.LENGTH_SHORT).show();
startTime = System.currentTimeMillis();
notificationJustStarted = true;
timerRunnable = new Runnable() {
#Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
seconds = (millis / 1000) + PrefUtil.getTimerSecondsPassed(TimerService.this);
updateNotification(goalName, seconds);
Log.d("timerCount", seconds + "");
timerHandler.postDelayed(this, 1000);
}
};
timerHandler.postDelayed(timerRunnable, 0);
return Service.START_STICKY;
}
#SuppressLint("NewApi")
public void updateNotification(String goalName, long seconds) {
if (notificationJustStarted) {
Intent notificationIntent = new Intent(this, MainActivity.class);
#SuppressLint("InlinedApi") PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
timerNotificationBuilder.setContentTitle("Goal In Progress")
.setOngoing(true)
.setContentIntent(pendingIntent)
.setOnlyAlertOnce(true)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setSmallIcon(R.drawable.ic_launcher_foreground);
notificationJustStarted = false;
}
long minutes = TimeUnit.SECONDS.toMinutes(seconds);
String time = minutes + ":" + (seconds - TimeUnit.MINUTES.toSeconds(minutes));
timerNotificationBuilder.setContentText(goalName + " is in progress\nthis session's length: " + time);
mNotificationManager.notify(1, timerNotificationBuilder.build());
startForeground(1, timerNotificationBuilder.build());
}
#Override
public void onDestroy() {
timerHandler.removeCallbacks(timerRunnable);
PrefUtil.setTimerSecondsPassed(this, seconds);
super.onDestroy();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#NonNull
#TargetApi(26)
private synchronized String createChannel() {
mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
String name = "STOPWATCH";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, importance);
mChannel.setName("Notifications");
if (mNotificationManager != null) {
mNotificationManager.createNotificationChannel(mChannel);
} else {
stopSelf();
}
return CHANNEL_ID;
}
}
You can also view my repo on this here. It is a complete stop watch app
I've found the reason why my notification stops updating after 30 seconds! Apparently,(according to this thread) on some devices running Android versions higher than 9 there are background restrictions.
These restrictions are the ones stopping my notifications from updating after 30 seconds from the moment the app gets closed, or in other words - from the moment they're becoming background activities (even though they are called through startForeground()).
There is no way around this setting. You cannot programmatically disable it. Your only option is to programmatically check if it's enabled using ActivityManager.isBackgroundRestricted() and display a pop-up informing your users on how to disable this setting
Says the user from the accepted answer in the thread.
And so, the issue of the notification not updating as expected is solved. The issue of the delay to show the first notification though remains unsolved, and there's another issue - every time the notification gets updated, the whole notification panel freezes for a second fraction.
I am stuck in a problem in which I need to compute some concurrent calculation in the background and notify the user when each execution is completed.
I read a lot of articles. In which I found Service Class, Intent Service, and other ones. But I found crash above 24 android os versions.
Nowadays which method is best to execute the background service in android and why?
For the above problem you can use Work Manager. For better understanding please review the following link
https://developer.android.com/topic/libraries/architecture/workmanager
Best Method to execute the Background service is to use the system AlarmManager class and invoke alarm after every XXX seconds but it will drain the bettery but solution definately work for you.
step to follow ,
create alarm
public static void setUpalarm(Context context) {
Intent intent = new Intent(context, RestartServiceFromTimer.class);
final PendingIntent pIntent = PendingIntent.getBroadcast(context , 0,
intent,0);
// Setup periodic alarm every every half hour from this point onwards
long firstMillis = System.currentTimeMillis(); // alarm is set right away
AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// First parameter is the type: ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC_WAKEUP
// Interval can be INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_DAY
long delay = 5 * 1000 * 60; // time sets to 5 minute change accordingly
long time = System.currentTimeMillis() + delay;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
alarm.set(AlarmManager.RTC_WAKEUP, time, pIntent);
else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
alarm.setExact(AlarmManager.RTC_WAKEUP, time, pIntent);
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pIntent);
}
2 create Broadcast receiver and restart service again also schedule alarm again
public class RestartServiceFromTimer extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("Task Trigger","Task is triggered");
if(!isMyServiceRunning(DetectIncomonCallService.class,context.getApplicationContext()))
{ Intent myserviceIntent = new Intent(context.getApplicationContext(),Service.class);
//start your background service here
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O)
{
ContextCompat.startForegroundService(context.getApplicationContext(),myserviceIntent);//this is for forground service
}
else
{
context.startService(myserviceIntent);
}
}
MainActivity.setUpalarm(context.getApplicationContext());
}
private boolean isMyServiceRunning(Class<?> serviceClass,Context context) {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
}
3 call method from main activity like this
setUpalarm(MainActivity.this)
You can use Foreground Service for android OREO and higher
versions and for below OREO Android version, you can use background STICKY, NON_STICKEY services. From the service, you can broadcast your action with value when your calculation is completed.
My goal is to have a service polling a Server from the background and notifying the user he gets interesting data. Basically just like WhatsApp does (WhatsApp can be closed and the phone can be sleeping and you are still notified whenever you get a message).
I know about the Service class as well as the IntentService class and that they can do background operations and extend above the lifecycle of an activity.
To make myself familiar with them I wrote a test project with a server which basically just accepts socket connections and prints its input into the console.
Client-side, though, I just cannot get the service to stick around. After 5 seconds of the app being closed the service stops (onDestroy is not even called).
I've tried returning START_STICKY in the onStartCommand() method. I've also tried different approaches polling the data, such as using google's Volley library to fire requests every 5 seconds, as well as an ongoing, lasting Socket connection sending packets of data to the Server every 5 seconds. None of those attempts have been working, though, and the service always is killed. The sticky mode will . I've read some things about AlarmManager to restart the killed process, but some people said that this approach would be bad practice since the AlarmManager can be quite unreliable.
Is there something I am missing or doing wrong? Here is my client-side code:
This is the service's onStartCommand:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
#Override
public void run() {
Log.i(TAG, "Service has started");
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("chId", "chName", NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(true);
channel.enableVibration(true);
notificationManager.createNotificationChannel(channel);
}
showNotification("Service has started");
long start = System.currentTimeMillis();
long now;
BufferedReader input = null;
PrintWriter output = null;
try {
Socket connection = new Socket("192.168.178.21", 6789);
input = new BufferedReader(new InputStreamReader(connection.getInputStream()));
output = new PrintWriter(connection.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
while (true) {
now = System.currentTimeMillis();
if ((now - start) >= 5000) {
output.write("Hi" + System.lineSeparator());
output.flush();
start = now;
}
}
}
}).start();
return START_STICKY;
}
and its onDestroy method
#Override
public void onDestroy() {
showNotification("on Destroy");
super.onDestroy();
}
The showNotification method just pushes a new notifcation forward:
private void showNotification(String text) {
NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext(), "chId")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setTicker("Ticker")
.setWhen(System.currentTimeMillis())
.setContentTitle("Titel")
.setLights(Color.BLUE, 3000, 3000)
.setContentText(text);
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
notification.setContentIntent(pendingIntent);
notificationManager.notify((int) System.currentTimeMillis(), notification.build());
}
I have been reading about this topic a lot so I hope this is not a duplicate question. Thank you guys for your help!
Recent changes in Androids background task running behaviour makes it very difficult to keep Services alive and continue work in applications when the phone is locked. My Service is only working properly when the screen is on or the phone gets charged. When the phone is locked, the Service shuts down almost immediately or runs way too slow to be useful in any way.
I tried to use "START_STICKY" flag and a startForeground() Notification to keep the Service alive but this doens't help at all. I'm using a Handler that calls dowork() every 5 seconds, which then checks if theres something to do.
I want to perform a simple task on a certain time event: wake up every half/quarter or full hour, do some quick work without CPU limitation, then shut down until next time. The phone should wake up reliable and accurate on time and get "whitelisted" to use some CPU power for around half a minute. I don't do any intense work, that could affect user performance.
public class MyService extends Service {
public MyService() {
super();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("MyService")
.setContentText("Service is running")
.setPriority(IMPORTANCE_HIGH)
.setContentIntent(pendingIntent).build();
startForeground(1, notification);
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
dowork();
handler.postDelayed(this, 5000);
}
}, 1500);
return START_STICKY;
}
For this question i want to refer to Alarm Manager Example
. This one is doing it's job pretty well, i finally got it working that way.
I have activity which needs to be active all the time. I have thread which sleep 10 sec, and monitors values taken from database, compare them and start method. I'm wondering if user go back to other applications and activities, does my activity and thread still work, or they are handled by activity manager and go to pause-stop-destroy?? How to stay them a live??
Thank you.
here is code for that thread:
new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
while(true){
try {
Thread.sleep(10000);
myHendler.post(new Runnable() {
public void run() {
// TODO Auto-generated method stub
final Calendar cal = Calendar.getInstance();
int godina2 = cal.get(Calendar.YEAR);
int mesec2 = cal.get(Calendar.MONTH);
int dan2 = cal.get(Calendar.DAY_OF_MONTH);
int sati2 = cal.get(Calendar.HOUR_OF_DAY);
int minuti2 = cal.get(Calendar.MINUTE);
trenutniDatum = new StringBuilder().append(dan2).append("-").append(mesec2 +1).append("-").append(godina2);
trenutnoVreme = prepraviVreme(sati2) + ":" + prepraviVreme(minuti2);
for(int i = 0; i < primljenoIzBazeDatum.length; i++){
String bazaBroj = "";
String bazaText = "";
if(primljenoIzBazeDatum[i].toString().equals(trenutniDatum.toString()) && primljenoIzBazeVreme[i].toString().equals(trenutnoVreme)){
int bazaId = Integer.parseInt(primljenoIzBazeId[i]);
bazaBroj = primljenoIzBazeBroj[i].toString();
bazaText = primljenoIzBazeText[i].toString();
String datumPromena = "*" + primljenoIzBazeDatum[i].toString() + "* SENT *";
datumVreme.open();
datumVreme.updateData(bazaId, datumPromena);
datumVreme.close();
sendPoruka(bazaBroj, bazaText);
}
} // end for
} // end run
});
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}).start();
Based on my understanding of what you want to do, here is what I would do :
First, create a BroadcastReceiver
public class Poller extends BroadcastReceiver {
private final String TAG = "Poller";
#Override
public void onReceive( Context context, Intent intent ) {
Log.i(TAG, "Poller broadcastintent received");
Intent myIntent = new Intent( context, PollerService.class );
context.startService( myIntent );
}
then , create the service that is called and then shuts itself down
public class PollerService extends Service {
final String TAG = "PollerService";
#Override
public void onStart(Intent intent, int startId) {
Log.i(TAG, "Service onStart()");
pollingTask.execute();
}
AsyncTask<Void, Void, Void> pollingTask = new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... param) {
// Do what you want in the background
}
#Override
protected void onPostExecute(Void result) {
stopSelf();
}
};
}
then, set an AlarmManager to wake the service every minute
AlarmManager am = ( AlarmManager ) getSystemService( Context.ALARM_SERVICE );
Intent alarmIntent = new Intent( "CHECK_DATABASE" );
PendingIntent pi = PendingIntent.getBroadcast(context, 0 , alarmIntent, 0 );
int type = AlarmManager.ELAPSED_REALTIME_WAKEUP;
long interval = POLLING_INTERVAL_IN_MILLISECONDS;
long triggerTime = SystemClock.elapsedRealtime() + interval;
// For short intervals setInexact repeating is same as exactRepeating, use at least fifteen minutes to make it more efficient
am.setInexactRepeating( type, triggerTime, interval, pi );
Log.i(TAG, "Set inexact alarm through AlarmManager");
}
setup the receiver in Android manifest
<receiver android:name="Poller">
<intent-filter>
<action android:name="CHECK_DATABASE"/>
</intent-filter>
</receiver>
finally, unset the AlarmManager to stop polling once your required SMS is received
AlarmManager am = ( AlarmManager ) getSystemService( Context.ALARM_SERVICE );
Intent intent = new Intent( "CHECK_DATABASE" );
PendingIntent pi = PendingIntent.getBroadcast( context, 0 , intent, 0 );
am.cancel(pi);
I do think that Peter is right though and this will kill you battery unless you'll only be checking until you get the required info and then don't poll and that's a short time.
Also, if you can get the exact time when you want to send the SMS with a single call from the database you can just set up the AlarmManger to wake up the service at that time, perform the action and be done with it. That would be the best approach (I can't quite make out if that is the case from you code but it does seems to be from you comments).
No, no application code on Android is not guaranteed to run all the time. Android OS can kill off aplications and services any time it feels it needs to.
Your best bet to periodically execute code would be to use AlarmManager, which makes your code execute periodically. Also a proper flag must be set to execute your code when device is asleep.
Note, since your period is very short (10s), it would keep CPU running all the time, draining the batterry very quickly.
If it has to be active all the time you have to use a Service: http://developer.android.com/guide/topics/fundamentals/services.html
I'm wondering if user go back to other applications and activities,
does my activity and thread still work, or they are handled by
activity manager and go to pause-stop-destroy?? How to stay them a
live??
They won't be kept "alive". If the system needs the resources your activity is destroyed. If you want to keep things running in the background even after your app is finished you have to use a Service.
In Java language you can scheduling your programs by traditional way:
java.util.Timer
java.util.TimerTask
for more information you can see:
http://enos.itcollege.ee/~jpoial/docs/tutorial/essential/threads/timer.html
http://www.ibm.com/developerworks/java/library/j-schedule/index.html
but better practice is using a scheduling framework such as Quartz, you can see http://www.quartz-scheduler.org/.
Spring framework also integration with Quartz framework.