Hello everyone i'm trying to implement a periodic task on Android but i'm stuck on some devices.
I need to run a task in background every 15 or 30 minutes. This works well on Android pre 8.0. But on 8+, it works only when app is in background or foreground. When app is swiped out of recent , scheduled task are killed on real devices (Ulefone note 7(Android 8.1), Tecno LC7(Android 10), itel A56 (Abdroid 9)) but works well on emulators(Android 10). I've tried several ways:
1.Workmanager (works only when app is in background or foreground)
build.gradle
implementation "androidx.work:work-runtime:2.4.0"
MainActivity
PeriodicWorkRequest periodicSyncDataWork =
new PeriodicWorkRequest.Builder(NotificationWorker.class, 15,TimeUnit.MINUTES)
.addTag("TAG_SYNC_DATA")
.setBackoffCriteria(BackoffPolicy.LINEAR,PeriodicWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)
.build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"NOTIFICATION_WORKER",
ExistingPeriodicWorkPolicy.REPLACE, //Existing Periodic Work policy
periodicSyncDataWork //work request
);
NotificationWorker
public class NotificationWorker extends Worker {
public NotificationWorker(#NonNull Context context, #NonNull WorkerParameters workerParams)
{
super(context, workerParams);
}
#NonNull
#Override
public Result doWork() {
Log.d("MYWORKER", "LLLLLLLLLLL");
//My code here
return Result.success();
}
}
2.JobScheduler (works only when app is in background or foreground)
ComponentName serviceComponent = new ComponentName(context, NotifJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(1880, serviceComponent);
builder.setPersisted(true);
builder.setPeriodic(16*60*1000, 20*60 *1000);
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
jobScheduler.schedule(builder.build());
3.Alarm Manager (Doesn't fire the BroadcastReceiver)
The main code
Intent intent = new Intent(getApplicationContext(), MyIntentService.class);
final PendingIntent pIntent = PendingIntent.getBroadcast(this, 100,intent, PendingIntent.FLAG_UPDATE_CURRENT);
long firstMillis = System.currentTimeMillis();
AlarmManager alarm = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, firstMillis, AlarmManager.INTERVAL_FIFTEEN_MINUTES, pIntent);
The BroadcastReceiver
public class NotificationBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent in = new Intent(context, MyIntentService.class);
context.startService(in);
}
}
The IntentService
public class MyIntentService extends IntentService {
public MyIntentService(String name) {
super(name);
}
#Override
protected void onHandleIntent(#Nullable Intent intent) {
Log.d("NotifIntentService", "Starting");
//My task here
}
}
I can't figure out what i'm doing wrong here. Please help
I have been stuck on the same issue for days and eventually found out that there is no proper way to do it yet but to ask users to give some permissions (auto start, batter saver optimizations etc..)
you can find more information here: https://dontkillmyapp.com
This question is asked in many different ways with no good answers, but here you can probably find some answers which it's abstract will be what I just told you.
Work Manager on chinese ROMs like Xiaomi and oppo, when under battery optimization, increase the scheduled delay of work by several hours
Related
I'm working on an android app which requires a background task to be performed every hour(Job Scheduler or Service). Task gets executed when the app is running but as soon as I kill the app from foreground, service not work. Is there another way to achieve this?
1. Service
public class NotificationService extends JobService {
private void PrintLog()
{
Log.d("DGVCL", "PrintLog()");
}
#Override
public boolean onStartJob(JobParameters jobParameters) {
Log.d("DGVCL", "onStartJob()");
PrintLog();
jobFinished(jobParameters, false);
return true;
}
#Override
public boolean onStopJob(JobParameters jobParameters) {
Log.d("DGVCL", "onStopJob()");
return true;
}
}
2. Main Activity
JobScheduler jobScheduler = (JobScheduler)getApplicationContext().getSystemService(JOB_SCHEDULER_SERVICE);
ComponentName componentName = new ComponentName(this, NotificationService.class);
JobInfo jobInfo = new JobInfo.Builder(1, componentName)
.setPeriodic(Global.NOTIFICATION_TIME_PERIOD)
.setBackoffCriteria(Global.NOTIFICATION_TIME_PERIOD, JobInfo.BACKOFF_POLICY_LINEAR)
.setPersisted(true).build();
jobScheduler.schedule(jobInfo);
3. manifest
<service android:name="com.hopesndreams.hiren.hd.service.NotificationService" android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
Use WorkManager it is build on top of JobScheduler and it is specifically build to take on all background services both foreground and background functionalities. https://developer.android.com/topic/libraries/architecture/workmanager
* Using AlarmManager*
Step 1:Create a Service
Do your Logic here in the service
public class AService extends Service {
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//do something
}
#Override
public void onCreate() {
//do somrthing
}
}
Step 2: Create a BroadCast receiver
Start your service with this.
public class AReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
try {
Intent intent = new Intent(context, AService.class);
context.startService(intent);
} catch (Exception e) {
}
}
}
in MainActivity
Intent liveIntent = new Intent(getApplicationContext(), AReceiver.class);
PendingIntent recurring = PendingIntent.getBroadcast(getApplicationContext(), 0, liveIntent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Calendar updateTime = Calendar.getInstance();
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, updateTime.getTimeInMillis(), 16 * 60 * 1000, recurring);
//wakeup and starts service in every 16 minutes.
This is the method working for me. Works fine even if you close the app. Works in Xiaomi devices.
Don't forget to add the service inside the manifest
Indeed, WorkManager is the way to go.
You can read up more on other work primitives to suit your task here, but the below implementation uses Worker for threading in WorkManager, which performs work synchronously on a background thread.
public class BackgroundWorker extends Worker {
public BackgroundWorker
(#NonNull Context context,
#NonNull WorkerParameters params) {
super(context, params);
}
#NonNull
#Override
public Worker.Result doWork() {
yourBackgroundTask(); // yourBackgroundTask() implementation
return Result.success();
}
public static void schedulePeriodicWork(Data data) {
// When multiple constraints are specified like below,
// your task will run only when all the constraints are met.
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.setRequiresCharging(true)
.build();
PeriodicWorkRequest taskWork = new PeriodicWorkRequest.Builder(BackgroundWorker.class, 60,
TimeUnit.MINUTES)
.setConstraints(constraints)
.setInputData(data)
.build();
WorkManager.getInstance().enqueue(taskWork);
}
}
Later in your MainActivity file, inside onCreate():
Data data = workData();
BackgroundWorker.schedulePeriodicWork(data);
Then outside the onCreate() method,
private Data workData() {
return new Data.Builder() // to build Data objects
.build();
}
One small thing to note, is that though we set the above task to execute every 60 minutes, each iteration may not be executed at the same time interval.
According to Android documentation, WorkManager is meant for deferrable work, and some drift must be tolerated. However, you can check your log console for the update, "WM-WorkerWrapper: Worker result SUCCESS for Work".
Hope this is helpful.
I'm doing an Android app that requires sending its location frequently, every 1 minute or 2 minutes at the most. For this, I use a JobSchedulerService. I've already managed to make it run more than once every 15 minutes on devices with Android N version by replacing the .setPeriodic() with a .setMinimumLatency(). The fact is that at the beginning it is executed periodically in the established time, but after a while it runs every 7 or 9 minutes approximately.
I have already included the application in the battery saving white list, but didn't work. Is there any way to execute it or a similar service every minute with no restrictions? Doesn't matter how much battery the app spends.
EDIT:
This is what I've tried:
ReceiverService:
public class ReceiverService extends WakefulBroadcastReceiver {
#Override
public void onReceive(Context ctx, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
if (!isMyServiceRunning(ServiceBackground.class, ctx))
startWakefulService(ctx, new Intent(ctx, ServiceBackground.class));
new ServiceAlarmManager(ctx).register();
}
}
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())) {
Log.i("Service already","running");
return true;
}
}
Log.i("Service not","running");
return false;
}
}
The ServiceAlarmManager is exactly the same as #madking said.
You can put your code that sends location in a Service and implement an AlarmManager that periodically checks if your Service is running and restarts it if the Service has been killed by OS. You'll have to implement the AlarmManager using a WakefulBroadcastReceiver.
ReceiverService.java
public class ReceiverService extends WakefulBroadcastReceiver {
#Override
public void onReceive(Context ctx, Intent intent) {
if (!YourService.isRunning()) {
startWakefulService(ctx, new Intent(ctx, YourService.class));
}
new ServiceAlarmManager(ctx).register();
}
}
ServiceAlarmManager.java
public class ServiceAlarmManager {
private Context ctx;
private static final int TIME_INTERVAL = 300 * 1000;
public ServiceAlarmManager(Context context) {
ctx = context;
}
public void register() {
Intent serviceRestarter = new Intent();
serviceRestarter.setAction("someString");
PendingIntent pendingIntentServiceRestarter = PendingIntent.getBroadcast(ctx, 0, serviceRestarter, 0);
AlarmManager alarmManager = (AlarmManager) ctx.getSystemService(ctx.ALARM_SERVICE);
Date now = new Date();
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, now.getTime() + TIME_INTERVAL, pendingIntentServiceRestarter);
}
}
Also register your BroadcastReceiver in your Manifest.xml file
<receiver android:name=".ReceiverService">
<intent-filter>
<action android:name="someString" />
</intent-filter>
</receiver>
The register() method does two things.
1- Issues a broadcast which is caught by WakefulBroadcastReceiver and restarts the Service if required
2- Sets the next alarm to be invoked to check if the Service has been killed.
This way the service keeps running even if the OS kills it and you'll be able to send location updates periodically.
Note: Though this practice is not recommended as your application will use more battery but you don't seem to care about it as I did not either as some business requirements don't leave us a choice.
I tried this and it works: in the onCreate() of your activity you schedule an Alarm for every minute (setAlarm). Everytime the alarm is triggered, WakefulBroadcastReceiver is called, and that's where we launch our service(s):
private static long INTERVAL_ALARM = 1 * 60 * 1000;
public static void setAlarm(Context context) {
long current_time = Calendar.getInstance().getTimeInMillis();
Intent myAlarm = new Intent(context.getApplicationContext(), AlarmReceiver.class);
PendingIntent recurringAlarm = PendingIntent.getBroadcast(context.getApplicationContext(), 0, myAlarm, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarms = (AlarmManager) context.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
alarms.setRepeating(AlarmManager.RTC_WAKEUP, current_time, INTERVAL_ALARM, recurringAlarm);
}
And in the receiver:
public class AlarmReceiver extends WakefulBroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent myService = new Intent(context, MyService.class);
context.startService(myService);
}
}
In your service, you should stopSeflf() in the end of your treatment.
Don't forget to register your BroadcastReceiver in your Manifest.xml file
NB: WakefulBroadcastReceiver is deprecated in API level 26.1.0. JobSchedulerService does the work
I'm developing an app that need to do some check in the server every certain amount of time. The check consist in verify if there are some notification to display. To reach that goal I implemented Service, Alarm Manager and Broadcast Reciever. This is the code that I'm using so far:
public class MainActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
setRecurringAlarm(this);
}
/**
*
* #param context
*/
private void setRecurringAlarm(Context context) {
Calendar updateTime = Calendar.getInstance();
Intent downloader = new Intent(context, MyStartServiceReceiver.class);
downloader.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, downloader, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, updateTime.getTimeInMillis(), 60000, pendingIntent);
}
...
}
Receiver class
public class MyStartServiceReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent dailyUpdater = new Intent(context, MyService.class);
context.startService(dailyUpdater);
Log.e("AlarmReceiver", "Called context.startService from AlarmReceiver.onReceive");
}
}
Service class
public class MyService extends IntentService {
public MyService() {
super("MyServiceName");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.e("MyService", "Service running!");
// TODO Do the hard work here
this.sendNotification(this);
}
private void sendNotification(Context context) {
// TODO Manage notifications here
}
}
Manifest.xml
<!--SERVICE AND BROADCAST RECEIVER-->
<service
android:name=".MyService"
android:exported="false"/>
<receiver
android:name=".MyStartServiceReceiver"
android:process=":remote"/>
The code works fine, the task in the service will be excecuted periodically. The problem is that the service is destroyed when the app is forced to close. I need to keep alive the service, capable to execute the task, even if the user has closed the app, so the user can be updated via notifications. Thank you for your time!
You can't. If the app is forced closed, that means either its crashed (in which case the service has to be stopped as it may no longer work correctly) or the user force closed it in which case the user wants the app to stop- which means the user doesn't want the service to run. Allowing a service to be automatically restarted even if the user stops it would be basically writing malware into the OS.
In fact, Android went the exact opposite (and correct) way- if the user force stops an app, NOTHING of the app can run until the user runs it again by hand.
You may go through this. I hope this will solve your problem. If you want to keep awake your service it is practically not possible to restart the app which is forced close. So if you disable force stop your problem may be solved.
I have an android service to fetch data from the web that runs every fifteen minutes
public class SparkService extends Service {
Handler handler;
public SparkService() {
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("Starting Sevice", "Starting Service Successfully.");
if (handler == null) {
handler = new Handler(new Handler.Callback() {
#Override
public boolean handleMessage(Message msg) {
fetchDataFromServer();
handler.removeMessages(120);
handler.sendEmptyMessageDelayed(120, 15 * 60 * 1000);
return true;
}
});
}
handler.sendEmptyMessageDelayed(120, 15 * 60 * 1000);
return Service.START_STICKY;
}
}
I have found the service to be unreliable at times and seems like it's not being called if the app is inactive for a certain period of time. I want to replace the service with an AlarmManager service instead. My app is currently in production. Can I just delete the SparkService class and add another Alarm service class without affecting existing users who update the app? Or would I have to stop this SparkService in my app update so the app can function properly?
Your app is your entry point. So if it's killed that means all services related to its process will also be killed, like if you kill the svchost.exe process in Windows all sub processes like Windows update service will be stopped too and will not be running again until you launch the update manager.
The same goes for your app: the only way that a Service won't be stopped by killing your app (and I'm not sure about that but it can be) is if the Service is created with its own process using a special tag in the Manifest.
I think in your case you didn't set that tag so the Service will be only scheduled once your app is launched after the update and in that case the Service will behave according to the new code.
To answer your first question even if you delete the service from your update users with the old version will not be affected until they update there version with the new one
Now for using Alarm manger to trigger update from your backend as you said it's a good practice as the alarm manager have different set that you can use depending or your need below a short example how to use it
// Get alarm manager instance
AlarmManager alarmManager = (AlarmManager)getApplicationContext().getSystemService(Context.ALARM_SERVICE);
Calendar calendar;
Intent intent;
PendingIntent pendingIntent;
// Schedule
intent = new Intent(getApplicationContext(), YourCustomBroadcastReceiver.class);
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, 0);
calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.add(Calendar.SECOND, 1); // first time
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(),
60*5*1000,//Each five minutes
pendingIntent
);
And in your broadcast receiver
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class YourBroadcastReceiver extends BroadcastReceiver{
publicYourBroadcastReceiver() {}
#Override
public void onReceive(Context context, Intent intent) {
Intent serviceIntent = new Intent(context.getApplicationContext(),YourService.class);
context.startService(serviceIntent);
}
}
And here for more details about alarm manager
http://developer.android.com/training/scheduling/alarms.html
I would like to know if there is an Android counterpart for iOS BackgroundFetch feature.
I would like my Android app using Cordova to wake up every 15 minutes or so and check for updates and perform some other miscellaneous tasks.
In iOS, I was able to do this by using cordova-background-fetch plugin.
Since there is no Android version of that plugin, I am happy to write it myself; but I would first like to know how I would go about implementing such feature in Android. Any suggestions?
In Android you can set AlarmManger to wakes up every X milliseconds and run a PendingIntent.
This code looks something like this.
AlarmManager mgr=(AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent i=new Intent(context, OnAlarmReceiver.class);
PendingIntent pi=PendingIntent.getBroadcast(context, 0, i, 0);
mgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime()+60000,
PERIOD,
pi);
Android's default IntentService that runs in the background has some limitations.
You can also take a look at external libary WakefulIntentService (https://github.com/commonsguy/cwac-wakeful). I use that along with AlarmManager to run background tasks.
Updated:
OnAlarmReceiver class
public class OnAlarmReceiver extends BroadcastReceiver {
public static String TAG = "OnAlarmReceiver";
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Waking up alarm");
WakefulIntentService.sendWakefulWork(context, YourService.class); // do work in the service class
}
}
YourService class
public class YourService extends WakefulIntentService {
public static String TAG = "YourService";
public YourService() {
super("YourService");
}
#Override
protected void doWakefulWork(Intent intent) {
Log.d(TAG, "Waking up service");
// do your background task here
}
}
You can use AlarmManager. Besides that you can also use AccountManager.