I have a quite strange issue with a cwac-wakeful library and wakelocks.
In my app, I'm using cwac-wakeful to periodically download a data from a web server and this works very well. But when somewhere in the app I acquire a wakelock (even when the set alarms were cancelled), cwac-wakeful starts to behave a little odd - it starts a job at full hours exactly every 5 minutes (14:00, 14:05...), regardless of set repeating interval.
Releasing the wakelock gives no effect and the only method to fix this is to completely restart the application.
I know that cwac-wakeful takes advantage of wakelocks while performing a job, so maybe the problem is here.
Here is a class that implements the AlarmListener.
public class ServiceWaker implements WakefulIntentService.AlarmListener
{
private static final Logger log = Logger.getLogger(ServiceWaker.class);
public ServiceWaker()
{
log.info("New ServiceWaker");
}
#Override
public void scheduleAlarms(AlarmManager alarmManager, PendingIntent pendingIntent, Context context)
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
long syncFrequency = Long.parseLong(prefs.getString("sync_frequency", "1"));
syncFrequency = syncFrequency * 60 * 1000;
log.info("Alarm scheduled with a repeat interval:" + syncFrequency);
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime()+60000, syncFrequency, pendingIntent);
}
#Override
public void sendWakefulWork(Context context)
{
log.info("Sent wakeful work");
WakefulIntentService.sendWakefulWork(context, CheckServiceWakeful.class);
}
#Override
public long getMaxAge()
{
return AlarmManager.INTERVAL_FIFTEEN_MINUTES*2;
}
}
Details:
Target SDK: 17
Minimum SDK: 12
Compile SDK: 17
BuildTools version: 19.0.3
Late answer but I forgot to post it after I found out the solution.
The problem was an energy manager in my mobile. I have 3 battery plans available:
Energy saving - runs only essential services
Smart - prolongs the battery life by doing some optimization with wake locks, I have had this plan set to active
Normal - everything runs normally
"Smart" plan was responsible for this issue. As I have read somewhere, when set to active, the system tries to batch wake locks and network tasks, so the mobile is not continuously being waken. Thus my task was being re-scheduled with a longer period, in order to run it with another tasks every - for example - 15 minutes.
Related
maybe this is yet another question about WorkManager, but I really can't find a solution...
I'm trying, as the title suggests, to run a periodic work every 15 minutes. Actually in the worker I'm polling some data every minute. After every poll, every almost 1 second I check if the worker is being stopped and if so return, otherwise keep waiting until 1 minute is reached and poll data again.
According to the documentation this should work and indeed it is, until I kill the app from the recent app screen.
Here is the code:
package com.qsea.app.cordova;
import android.content.Context;
import android.os.SystemClock;
import android.util.Log;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class ServerListenerWorker extends Worker {
public static final String TAG = "ServerListenerWorker";
public ServerListenerWorker(
Context appContext,
WorkerParameters workerParams
) {
super(appContext, workerParams);
}
#Override
public Result doWork() {
Log.d(TAG, "Doing work");
final long startTime = SystemClock.elapsedRealtime();
final int maxDelta = 840000; // 14 minutes
while (true) {
// I did this to stop this worker until 15 minutes
// and then let the next worker run
if (SystemClock.elapsedRealtime() - startTime >= maxDelta) {
break;
}
// Here I'm polling data, if the polling results in a failure
// I return Result.retry()
// It avoid waiting if it remains only 1 minute until the max time
if (SystemClock.elapsedRealtime() - startTime >= (maxDelta - 60000)) {
break;
}
for (int i = 0; i < 60; i++) {
SystemClock.sleep(950);
// Here it checks if it is stopped
if (isStopped()) {
Log.d(TAG, "Detected stop"); // this is actually never reached
break;
}
}
}
return Result.success();
}
}
And I do as following to start the work
Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();
PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(ServerListenerWorker.class, 15, TimeUnit.MINUTES)
.addTag(serverListenerWorkerUID)
.setConstraints(constraints)
.build();
WorkManager.getInstance(cordova.getActivity()).enqueueUniquePeriodicWork(serverListenerWorkerUID, ExistingPeriodicWorkPolicy.KEEP, periodicWorkRequest);
I check the correct working of the worker by viewing the log of my server (If I receive the request it is working fine) and it seems to work ok until I close the app. Then it stops running the actual worker and never run a worker again until I reopen the app, where the work seems to be enqueued and resumes right after the app opening.
Am I doing something wrong in the initialization?
I have also <service android:name=".ServerListenerWorker" android:permission="android.permission.BIND_JOB_SERVICE" /> in my AndroidManifest.xml
Is this expected behavior?
I read that chinese ROM have additional restriction to background services and I have a HUAWEI, which seems to be the worst in this. So could it be the device? And if so, how do Facebook, WhatsApp, Instagram and others manage this to work even in these devices?
Let's split this in two different problems.
What are you doing in your workers is battery heavy and would be better if you use a foreground service that notifies the user that your application is running continuously in the background. You can also use WorkManager with the newly introduced support for long-running workers (that under the hood uses a foreground service). More on this in the documentation.
If have a problem with a specific OEMs, please open an issue on the Android issuetracker as this maybe a CDD violation. Google can contact the OEM and request that they fix the ROM. This is going to take time, in the meanwhile, you can take a look at sites like don't kill my app to understand what are the constraints on a specific device and use a library like autostarter to help the user to navigate to the right setting.
BTW, you don't need to be list your workers in the AndroidManifest.xml file
For a few days now im struggling with the following: I want to count movement patterns using the gravity sensor on an Android device while the screen is off. I am using a bound service that I start in the foreground (with a notification for Android 8 to keep it running) and everything works fine when the screen is on. Even if the App is not running in the foreground everything works fine.
However, as soon as the display is turned off, very strange things start to happen: The sensor data is still processed and movement counted to some extend but the results are very bad and inaccurate. Also, if the screen is turned on again, the app behaves very strange. Sometimes the app works as expected but sometimes it seems that old sensor events are processed delayed and counted later. The whole algorithm is not running smoothly from this time on. What is also interesting: If the device is plugged in and I observe everything with the console in Android Studio everything works just perfect, even if the screen is off. However, if the device is unplugged, the results become wrong again.
I tried a lot of things: Running everything on the main thread, on another thread, using an IntentService, setting the App on the whitelist for Doze, not sending data to the MainActivity while the screen is turned off, and followed this guide (I am developing on an Galaxy J5) - but nothing worked. It seems that either the SensorFusion algorithms of the Android system shut down in Standby even if there is a registered listener or that Samsung has some battery optimization running in the background and limits CPU operations. Is there something else that could cause this behavior? So running fine if the app is active, if it is in the background, but not when the device is in sleep?
Also maybe interesting to mention: I am developing a plugin for Cordova.
This is my code
Main.class
public class SensorPlugin extends CordovaPlugin implements ServiceClass.Delegate {
public void initialize() {
...
Intent serviceIntent = new Intent(applicationContext, ServiceClass.class);
applicationContext.bindService(serviceIntent, serviceConnection,
Context.BIND_AUTO_CREATE);
}
public void start() {
serviceClass.startMeasuring();
}
#Override
public void updateMovementCount(int count) {
...
};
}
Service.class
public class ServiceClass extends Service implements OtherClass.Delegate {
private volatile boolean isMeasuring;
private volatile double gravityX;
private volatile double gravityY;
private volatile double gravityZ;
public IBinder onBind(Intent intent) {
sensorManager = (SensorManager) getApplicationContext().
getSystemService(Context.SENSOR_SERVICE);
startForeground(1, buildNotification());
return mBinder;
}
public void startMeasuring() {
assert sensorManager != null;
Sensor gravity = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
sensorManager.registerListener(this, gravity,
SensorManager.SENSOR_DELAY_GAME);
isTracking = true;
SensorThread sensorThread = new SensorThread();
sensorThread();
}
#Override
public void onSensorChanged(SensorEvent event) {
gravityX = event.values[0];
gravityY = event.values[1];
gravityZ = event.values[2];
}
// This is an interface method from another class that I wrote
// (see below). For which I set this class as delegate and as soon
// as the other class finds a pattern in the data it calls this method
#Override
public void movementPatternDidChange(int count) {
// Usually I send this count to the main class with
// the same delegation pattern
delegate.updateMovementCount(count);
}
class SensorProcessingThread extends Thread {
Handler handler = new Handler();
// I use another runnable here, as I only want to process 10
// sensor events per second. The sensor manager usually returns
// way more which I don't need.
private Runnable sensorProcessingRunnable = new Runnable() {
public void run() {
otherClass.processMotionData(gravityX, gravityY, gravityZ);
if (isMeasuring) {
handler.postDelayed(this, 100);
}
}
};
#Override
public void run() {
if (!isMeasuring) {
return;
}
handler.postDelayed(sensorProcessingRunnable, 100);
}
}
}
Edit
I tested a lot in the meantime. I am now using the Apache Commons Primitives Collections for a better performance (instead of the large FastUtil which also caused this error in the past).
I also tested the app on two devices, a rather old LG G2 and the Galaxy J5. The problem is the same on both. So probably not manufacturer specific. The Android Studio Profiler reports a CPU usage of average 1-3% on both devices, so I assume an overload is likely not the cause. I also tested a TimerTask and Timer instead of a Runnable and Handler, which did not work as well.
What is also very interesting: I tried to debug the App over Wifi as explained here, and the App works absolutely fine, even if the device sleeps and the screen is turned off. It is so difficult to debug this issue, because the App behaves fine as soon as I am debugging it even without a cable attached. I have no idea what I could do else.
For everyone with a similar problem: I finally found the solution.
I think it is not well explained in the Android documentation, and not very intuitive either, but it is still necessary to set a partial wake lock to prevent the CPU from sleeping. This explains how to set a wake lock. A foreground service alone is not enough to keep a time-critical process running.
I have an app that should show a notification every 2 hours and should stop if user has already acted upon the notif. Since background services are history now, I thought of using WorkManager ("android.arch.work:work-runtime:1.0.0-beta01") for the same.
My problem is that although the work manager is successfully showing the notifications when app is running, but it won't show notification consistently in the following cases(I reduced the time span from 2 hours to 2 minutes to check the consistency):
when app is killed from the background.
device is in screen off.
state device is in unplugged state(i.e not charging).
By consistency , i mean that the notifications show at least once in the given time span. for 2 minutes time span, the freq of notifications went from once every 4 minutes to completely not show any notification at all. for 2 hours timespan( the timespan that i actually want), its been 4 hours and i haven't got a single notification. Here is the Code i am using for calling WorkManger:
public class CurrentStreakActivity extends AppCompatActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
setDailyNotifier();
...
}
private void setDailyNotifier() {
Constraints.Builder constraintsBuilder = new Constraints.Builder();
constraintsBuilder.setRequiresBatteryNotLow(false);
constraintsBuilder.setRequiredNetworkType(NetworkType.NOT_REQUIRED);
constraintsBuilder.setRequiresCharging(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
constraintsBuilder.setRequiresDeviceIdle(false);
}
Constraints constraints =constraintsBuilder.build();
PeriodicWorkRequest.Builder builder = new PeriodicWorkRequest
.Builder(PeriodicNotifyWorker.class, 2, TimeUnit.HOURS);
builder.setConstraints(constraints);
WorkRequest request = builder.build();
WorkManager.getInstance().enqueue(request);
}
....
}
Here is the worker class(i can post showNotif(..) and setNotificationChannel(...) too if they might be erroronous):
public class PeriodicNotifyWorker extends Worker {
private static final String TAG = "PeriodicNotifyWorker";
public PeriodicNotifyWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
Log.e(TAG, "PeriodicNotifyWorker: constructor called" );
}
#NonNull
#Override
public Result doWork() {
// Log.e(TAG, "doWork: called" );
SharedPreferences sp =
getApplicationContext().getSharedPreferences(Statics.SP_FILENAME, Context.MODE_PRIVATE);
String lastcheckin = sp.getString(Statics.LAST_CHECKIN_DATE_str, Statics.getToday());
// Log.e(TAG, "doWork: checking shared preferences for last checkin:"+lastcheckin );
if (Statics.compareDateStrings(lastcheckin, Statics.getToday()) == -1) {
Log.e(TAG, "doWork: last checkin is smaller than today's date, so calling creating notification" );
return createNotificationWithButtons(sp);
}
else {
Log.e(TAG, "doWork: last checkin is bigger than today's date, so no need for notif" );
return Result.success();
}
}
private Result createNotificationWithButtons(SharedPreferences sp) {
NotificationManager manager =
(NotificationManager) getApplicationContext().getSystemService((NOTIFICATION_SERVICE));
String channel_ID = "100DaysOfCode_ID";
if (manager != null) {
setNotificationChannel(manager,channel_ID);
showNotif(manager, channel_ID, sp);
return Result.success();
}
else {
return Result.failure();
}
I am using a xiaomi miA2 androidOne device with Android Pie(SDK 28). There are a few other things that are troubling me:
What can i possibly do to know if my WorkManager is running? Other that just wait for 2 hours and hope for a notification. I actually tried something like that, keeping my phone connected to pc and checking android studio's logcat every now and then. It DOES run all the logs when the worker is actually called, but i don't think that's a correct way to test it, or is it?
In the above Code, the setDailyNotifier() is called from the onCreate() every time the app is opened. Isn't it Wrong? shouldn't there be some unique id for every WorkRequest and a check function like WorkManger.isRequestRunning(request.getID) which could let us check if a worker is already on the given task??If this was a case of AsyncTask, then boy we would have a mess.
I have also checked #commonsware's answer here about wakelock when screen is off, but i remember that work manager does use alarm manager in the inside when available. So what am I missing here?
Few comments:
WorkManager has a minimum periodic interval of 15minutes and does not guarantee to execute your task at a precise time. You can read more about this on this blog.
All the usual background limitation you've on newer Android releases are still relevant when you use WorkManager to schedule your tasks. WorkManager guarantees that the task are executed even if the app is killed or the device is restated, but it cannot guarantee the exact execution.
There's one note about the tasks being rescheduled when your app is killed. Some OEM have done modification to the OS and the Launcher app that prevents WorkManager to be able to accomplish these functionality.
Here's the issuetracker discussion:
Yes, it's true even when the phone is a Chinese phone.
The only issue that we have come across is the case where some Chinese OEMs treat swipe to dismiss from Recents as a force stop. When that happens, WorkManager will reschedule all pending jobs, next time the app starts up. Given that this is a CDD violation, there is not much more that WorkManager can do given its a client library.
To add to this, if a device manufacturer has decided to modify stock Android to force-stop the app, WorkManager will stop working (as will JobScheduler, alarms, broadcast receivers, etc.). There is no way to work around this. Some device manufacturers do this, unfortunately, so in those cases WorkManager will stop working until the next time the app is launched.
As of now , i have this app installed for last 8 days and i can confirm that the code is correct and app is working fine. as said by pfmaggi , the minimum time interval for work manager to schedule the work is 15 minutes, so there is a less chance that the WorkManager would have worked as expected in my testing conditions( of 2 minutes ) . Here are some of my other observations:
Like I said in the question that i was unable to recieve a notification for 4 hours even though i have passed the repeat interval as 2 hours. This was because of Flex Time. I passed in the flex time of 15 minutes and now it shows notifications between correct time interval. so i will be marking pfmaggi's answer as correct.
The problem of repeated work request can be solved by replacing WorkManager.getInstance().enqueue(request) with WorkManager.getInstance().enqueueUniqueWork(request,..)
I was still unable to find a way to test the work manager in the way i have described.
I'm trying to make a worker-run every 15 minutes using the new WorkManager API, 1.0.0-alpha06.
If I'm not wrong, using Work manager with PeriodicWorkRequest should make the worker outlive task kills and phone reboots, but when I swipe the task from the Recent Apps the scheduled worker is lost (I've waited for around 45 minutes to see any logs of the worker scheduled for 15 minutes interval).
these are my files:
MyExampleWorker.java:
public class MyExampleWorker extends Worker{
public static final String TAG = "MY_EXAMPLE_WORKER";
#NonNull
#Override
public Result doWork() {
Log.i(TAG, "Starting background worker");
// Getting configuration data
long param1 = getInputData().getLong("param1", 60000);
String param2 = getInputData().getString("param2");
String param3 = getInputData().getString("param3");
PackageManager pckMgr = mContext.getPackageManager();
...
..
.
return Result.SUCCESS;
}
}
Main.java:
this method fires as soon as the app is launched
#ReactMethod
public void execute() {
Log.i(TAG, "inside execute, setting up periodic worker in workManager");
Data inputData = new Data.Builder()
.putLong("param1", 60000)
.putString("param2", "something")
.putString("param3", "something else")
.build();
PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest
.Builder(MyExampleWorker.class, PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, TimeUnit.MILLISECONDS)
.setInputData(inputData)
.addTag(MyExampleWorker.TAG)
.build();
WorkManager.getInstance().enqueueUniquePeriodicWork(MyExampleWorker.TAG, ExistingPeriodicWorkPolicy.KEEP, periodicWorkRequest);
}
UPDATE:
Not only that, but if I open the app once again I see the log for "inside execute, setting up a periodic worker in workManager" but seems the worker is not scheduled, it has been over an hour and no logs for the worker are present in logcat.
Am I missing something here?
Any help is GREATLY appreciated!!
Diego
This is the problem caused by Battery Optimization and/or Doze mode, occurs especially when you are using Chinese Rom like Oxygen OS, MIUI etc.
Test your app on Stock Rom's, this will work perfectly fine. There would be no issues at all. This is because these Chinese ROM enable their custom power saving techniques that prevent any background services from running.
Two things you can do:
Use the Ignore Battery Optimization and whitelist your app. When you enable this, a user will be asked like this,
You can ask programmatically
Intent intent = new Intent();
String packageName = context.getPackageName();
PowerManager pm = (PowerManager)
context.getSystemService(Context.POWER_SERVICE);
if (pm.isIgnoringBatteryOptimizations(packageName))
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
else {
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
}
context.startActivity(intent);
And in your manifest
<uses-permission
android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
This way you can whitelist your app and work manager will work, but unfortunately, Google will remove your app from their play store if you request the above permission. No one knows why!!!
Read this article on Google's Anti Trust Issues
The Second method is you can ask the users to whitelist their app by not showing the above dialog.
You can do this
startActivityForResult(new Intent(android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS), 0);
Part of my question, how I can set up a job with less then 15 minutes interval in "Nougat", was answerd by "blizzard" in his answer here:
Job Scheduler not running on Android N
He explained the problem and suggested to use the following workaround:
JobInfo jobInfo;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
jobInfo = new JobInfo.Builder(JOB_ID, serviceName)
.setMinimumLatency(REFRESH_INTERVAL)
.setExtras(bundle).build();
} else {
jobInfo = new JobInfo.Builder(JOB_ID, serviceName)
.setPeriodic(REFRESH_INTERVAL)
.setExtras(bundle).build();
}
However, using the suggested
.setMinimumLatency(REFRESH_INTERVAL)
just starts the job once;
but how do I get it periodic with a period of around 30 seconds on an android nougat device (not using handler or alarm manager)?
If someone is still trying to overcome the situation,
Here is a workaround for >= Android N (If you want to set the periodic job lower than 15 minutes)
Check that only setMinimumLatency is used. Also, If you are running a task that takes a long time, the next job will be scheduled at, Current JOB Finish time + PROVIDED_TIME_INTERVAL
.SetPeriodic(long millis) works well for API Level below Android N
#Override
public boolean onStartJob(final JobParameters jobParameters) {
Log.d(TAG,"Running service now..");
//Small or Long Running task with callback
//Reschedule the Service before calling job finished
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
scheduleRefresh();
//Call Job Finished
jobFinished(jobParameters, false );
return true;
}
#Override
public boolean onStopJob(JobParameters jobParameters) {
return false;
}
private void scheduleRefresh() {
JobScheduler mJobScheduler = (JobScheduler)getApplicationContext()
.getSystemService(JOB_SCHEDULER_SERVICE);
JobInfo.Builder mJobBuilder =
new JobInfo.Builder(YOUR_JOB_ID,
new ComponentName(getPackageName(),
GetSessionService.class.getName()));
/* For Android N and Upper Versions */
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mJobBuilder
.setMinimumLatency(60*1000) //YOUR_TIME_INTERVAL
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
}
UPDATE:
If you are considering your repeating job to run while in Doze Mode and thinking about JobScheduler, FYI: JobSchedulers are not allowed to run in Doze mode.
I have not discussed about the Dozing because we were talking about JobScheduler. Thanks, #Elletlar, for pointing out that some may think that it will run even when the app is in doze mode which is not the case.
For doze mode, AlarmManager still gives the best solution. You can use setExactAndAllowWhileIdle() if you want to run your periodic job at exact time period or use setAndAllowWhileIdle() if you're flexible.
You can also user setAlarmClock() as device always comes out from doze mode for alarm clock and returns to doze mode again. Another way is to use FCM.
Reference: Doze Restrictions
https://developer.android.com/training/monitoring-device-state/doze-standby
I struggled with same thing when I wanted to setup Job for refresh small part of data. I found out that solution for this problem may be setting up Job one more time with the same ID after i calledjobFinished(JobParameters, boolean). I think it should work every time on main thread.
My function to setup Job looks like this:
JobInfo generateRefreshTokenJobInfo(long periodTime){
JobInfo.Builder jobBuilder = new JobInfo.Builder(1L, new ComponentName(mContext, JobService.class));
jobBuilder.setMinimumLatency(periodTime);
jobBuilder.setOverrideDeadline((long)(periodTime * 1.05));
jobBuilder.setRequiresDeviceIdle(false);
return jobBuilder.build();
}
When I finish my work after first call of Job i call in main thread
jobFinished(mJobParameters, true);
registerRefreshJob(5*60*1000L);
This will reschedule my Job one more time for the same amount of time on the same id. When device is in idle state you still have to take under consideration lack of wake locks in doze so your job may not be started so often as you wish. It is mentioned in https://developer.android.com/about/versions/nougat/android-7.0-changes.html
If the device is stationary for a certain time after entering Doze, the system applies the rest of the Doze restrictions to PowerManager.WakeLock, AlarmManager alarms, GPS, and Wi-Fi scans. Regardless of whether some or all Doze restrictions are being applied, the system wakes the device for brief maintenance windows, during which applications are allowed network access and can execute any deferred jobs/syncs.