I'm building an app to control led bulbs (Mi-Light, limitlessLed and the likes). The purpose of the app is to have effects that the user can select and let run for an indefinite amount of time. For example, a "candle light" effect might change the bulbs color between shades of yellow and red, more or less randomly, until the user decides to stop.
Quick background info: the way these bulbs are controlled is through UDP packages which are sent over the WiFi network. Consequently, I need the app to keep on sending such UDP packages even when the device is sleeping.
After researching a little, I ended up making use of a wakelock in order to let the device broadcast UDP packages through the WiFi network even when sleeping (please do tell me in case there is a better approach I didn't find out about).
Everything works fine for some minutes (maybe 10?), until the device seemingly goes into some sort of deep sleep mode and stops sending packages over the network.
How can I prevent this from happening? And more generally speaking, what is a good approach I should take in order to accomplish what described above?
Just for reference, here's a sample snippet of code which just rotates through an array of 7 colours:
[...]
private static boolean animationRunning = false;
private Timer timer = new Timer();
private PowerManager mgr = (PowerManager)getReactApplicationContext().getSystemService(Context.POWER_SERVICE);
private PowerManager.WakeLock wakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock");
[...]
public void startAnimation(final int step) {
animationRunning = true;
wakeLock.acquire();
final int[] steps = { 0, 66, 99, 122, 166, 199, 255 };
resetTimer();
timer.scheduleAtFixedRate(new TimerTask(){
int curstep = 0;
@Override
public void run(){
byte[] Bytes = {64, (byte)steps[curstep], 85};
try {sendUdp(Bytes);} catch(IOException e) {};
curstep = curstep + 1;
if(curstep == steps.length) {
curstep = 1;
}
}
},0,1000);
}
I ended up implementing a foreground service (startForeground()) as explained here
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
_acquireWakelock();
_showNotification();
[do your thing]
return START_REDELIVER_INTENT;
}
private void _showNotification() {
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setAction("com.lightcontrollerrn2.action.main");
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
Bitmap icon = BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher);
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("Light Controller")
.setTicker("Light Controller")
.setContentText("Light effects are running")
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(Bitmap.createScaledBitmap(icon, 128, 128, false))
.setContentIntent(pendingIntent)
.setOngoing(true)
.build();
startForeground(1337,
notification);
}
A couple things I found out along the way which might help others as well:
The foreground service still requires a wakelock in order to keep on running when the device is idle;
An additional reason you might run into similar issues is the new Doze mode introduced in Android 6.0 (https://developer.android.com/training/monitoring-device-state/doze-standby.html). In my case though, it seems the service keeps on running long enough without the need for me to tackle the device going into doze mode (I tested with roughly one hour and I'm happy with it). EDIT: for the sake of completion, I tackled this one as well. Here's how you do it (within the Service itself):
@Override
public void onCreate() {
super.onCreate();
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
String packageName = getPackageName();
if (Build.VERSION.SDK_INT >= 23 && !pm.isIgnoringBatteryOptimizations(packageName)) {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
startActivity(intent);
}
}