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.
Related
How to run this in background , I mean even I move to other app or go to home screen of my android or close the screen , the button will still clicking itself
please help me
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
button1.performClick();
}
}, 5000);
Things to know
I will try to elaborate as much as I can in a layman terms so that you have a better grasp the Idea of Threads and async tasks
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//business logic
}
}, 5000);
is an Blocking method, which runs on the UI thread (I am supposing you are new to programming/android)[please read about Threads to understand what I am saying in deapth],
which means, in short, your application is executing some logic on the thread ("A worker" which is responsible for the rendering the UI on-screen),
By using Threads you can achieve efficiency in your application by dividing multiple tasks to multiple workers "Threads" but you can't run your application in the background.
How to make your application work in the background?
Google introduced some background limitations in Android Oreo. so to keep your application alive you need
foreground service by showing an ongoing notification.
1. The way you should implement service is like
public class YourService extends Service {
private static final int NOTIF_ID = 1;
private static final String NOTIF_CHANNEL_ID = "Channel_Id";
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId){
// do your jobs here
startForeground();
return super.onStartCommand(intent, flags, startId);
}
private void startForeground() {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
startForeground(NOTIF_ID, new NotificationCompat.Builder(this,
NOTIF_CHANNEL_ID) // don't forget create a notification channel first
.setOngoing(true)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(getString(R.string.app_name))
.setContentText("Service is running background")
.setContentIntent(pendingIntent)
.build());
}
}
2. Also you need to start the service
public class App extends Application {
#Override
public void onCreate() {
super.onCreate();
startService(new Intent(this, YourService.class));
}
}
3. Add your service in the "application" tag of your AndroidManifest.xml
<service android:name=".YourService"/>
4. And also this permission request in the "manifest" tag (if API level 28 or higher)
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
In this way, you can keep your service in the background. I suggest you read articles and see GitHub repositories, and also practice practice practice a lot to be good at Android :)
From the stackoverflow and many blogs, i surely understand that foreground service never run without notification in API>25. But still i confuse that Is notification mandory while app is running on screen or visible.
For eg. no need of notification when user stand within app. So is this possible to remove notification while app running ?
In service class
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
......
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
.setContentText(text)
.setAutoCancel(true);
Notification notification = builder.build();
startForeground(1, notification);
}
return START_NOT_STICKY;
}
In activity
Intent myService = new Intent(this, MyService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(myService);
} else {
startService(myService);
}
It's not possible to remove the notification while the foreground service is running, but it is possible to change your foreground service back into a "regular" service. This removes the need for a notification. In fact, the function to use,
stopForeground(boolean removeNotification)
...includes a removeNotification parameter just for that purpose. You service can switch from being "foreground" to "regular" on demand, by alternating calls to startForeground() and stopForeground().
In case it's not clear, you'd probably want to call stopForeground() whenever you have at least one Activity in a "started" state. This is something you'd have to track manually. Then, when the number of "started" activities reaches 0, you'd call startForeground().
EDIT
One approach is to use a bound service. Then, it's easy to call stopForeground() on it when you want.
Assume you have a single Activity. You can bind it to the service (see this doc or use one of these examples). Then your onServiceConnected() function could look like this (adapted from the Google example):
//MyActivity.java:
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mService.stopForeground(true); //This makes the notification go away
bound = true;
}
...
#Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MyService.class), this, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (bound) {
Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
.setContentText(text)
.setAutoCancel(true);
Notification notification = builder.build();
mService.startForeground(1, notification); //This brings the notification back! Service is already running, and continues to run.
unbindService(this);
bound = false;
}
}
No, it is mandatory even your app is running in foreground your foreground service need a notification.
You won't able to hide it.
Why :
You can use any other background task handler like intent service, job sclr but things is designed defferent for foreground service your user understand that event i will close this one of it's progress is going to keep running but things is defferent with background service your know it will do something in background but when system decide it's best time to do it not when your app want (as like in foreground service).
One more case ex :
Suppose your app in foreground battery level is lower than expected by user or system your foreground service will execute instantly no matter what so it's important for your user to know this it's running and take my resources (battery, data, etc)
Hopefully you got my mean 🙂
i have a class to get notification from my server I don't use firebase cloud messaging so I am getting data with the intentservice class. However I want to check the notification in per 60 seconds but timer is not work in
protected void onHandleIntent(Intent intent) {
it starts normally when I start the app but after that it doesn't check in time.
is there any way to check the notification every 60 seconds at the background ?
note: i don't use GCM firebase and I will never use it
IntentService starts the service when you send it an intent, runs whatever you define for that intent, then ends the service. If you are intending for the timer to be run then perhaps do it outside of the service. Perhaps you could use a Handler and post a delayed runnable firing the intent, although I must strongly advise against this in the long term since doing 60 second checks using the phone's radio will drain its battery fast. GCM's Firebase is meant to handle that issue since it "batches" the network requests, and if you don't want to do that then perhaps use a JobScheduler. Android manages the phone's radio pretty nicely and efficiently so unless 60 seconds is absolutely necessary, then it's not really advisable to do this; users could complain of battery drain.
On that handler thing:
private static Handler handler = new Handler(Looper.getMainLooper);
In your activity, then
private Runnable runnable = new Runnable() {
#Override
public void run() {
try{
//Start your intent service here.
} finally {
handler.postDelayed(runnable, 60000);
}
}
};
The try finally block is if your process somehow may trigger an error. If not necessary then you can remove it.
Then in your Activity's onCreate() or wherever you need to start the repeated trigger:
runnable.run();
You can use Alarm manager
Intent alarmIntent = new Intent(context, MyService.class);
PendingIntent pendingIntent = PendingIntent.getService(context, 0, alarmIntent, 0);
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
manager.setRepeating(AlarmManager.RTC_WAKEUP, 60000, 60000, pendingIntent);
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.
I have a button that when pressed, starts a service. This service starts logging information from the device. However, when I press the run, the screen temporarily freezes for about 8 seconds whilst it logs, and then once it is finished the screen unfreezes, a toast message shows (that's meant to show as soon as you press the button) and then I can do whatever. If i go on the application screen again, when the application is logging (note: the service is always running so that doesnt freeze it, just the logging) then the activity is frozen again and doesnt let me do anything until the logging is complete.
I have tried asynctasks and running on a new runnable/new thread but none of these have seemed to work for me. Whether i am implementing them correctly or not I'm unsure, but I'd like to fix the problem.
Here is my onStartCommand in the service class:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// THIS WOULD BE THE METHOD THAT RUNS WHATEVER YOU WANT TO DO EVERY SO OFTEN SO FOR ME THIS IS GETTING ALL DATA ETC
getProcessInfo();
// LOG DELAY = SECONDS BETWEEN RUNNING SERVICE/METHOD. SET AT THE TOP
myPendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Alarmmgr.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+LOG_DELAY*1000, myPendingIntent);
Intent notificationIntent = new Intent(this, LogOrCheck.class);
PendingIntent contentIntent = PendingIntent.getActivity(this,
101, notificationIntent,
PendingIntent.FLAG_NO_CREATE);
NotificationManager nm = (NotificationManager) this
.getSystemService(Context.NOTIFICATION_SERVICE);
Resources res = this.getResources();
Notification.Builder builder = new Notification.Builder(this);
builder.setContentIntent(contentIntent)
.setSmallIcon(R.drawable.arrow_up_float)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.arrow_down_float))
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.setContentTitle("Androigenius")
.setContentText("Service running OK");
Notification n = builder.getNotification();
startForeground(100, n);
return Service.START_STICKY;
}
And here is where I call startService:
but1.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (isClicked) {
Context context = getApplicationContext();
Toast toast = Toast.makeText(context, "Please press 'MINIMIZE' at the top to continue logging.", Toast.LENGTH_LONG);
toast.setGravity(Gravity.TOP|Gravity.RIGHT, 10, 55);
toast.show();
System.out.println("Start logging");
but1.setText("Stop Logging");
but1.setTextColor(Color.RED);
// START SERVICE, DONE. (STOP IS BELOW).
startService(myIntent);
isClicked = false;
}
else if (!isClicked) {
System.out.println("Stop Logging");
but1.setText("Start Logging");
// STOP SERVICE, DONE.
stopService(myIntent);
but1.setTextColor(getResources().getColor(color.cornblue));
isClicked = true;
}
Services run on the UI thread which is causing the 'freeze'. Using an IntentService will create a service that automatically runs in the background, not on the UI thread. Here is some details of the IntentService class:
IntentService Class
By default, the Service also run in the main thread of your applicaiton, in which your UI operations occur. Therefore, if you perform a long task in your onStartCommand() method, it will block the main thread.
To avoid the problem, you have to migration the logging task into a separate thread. You may use AsycTask class to do so. Please refer to http://developer.android.com/reference/android/os/AsyncTask.html for how to use the class.