Run an Android Service in the background reliably every N seconds

How to make an android service to run in the background reliably every N seconds even when the device is idle.

Sun 02 Aug 2020
  
er

In the context of an experiment I am running, it was required to run a background service where it is supposed to run every 15 seconds and not to stop even if the device is idle. Although these requirement may not seem much to someone with some knowledge on Android development it turned out that this is not as trivial as initially thought! Upon googling about it you will find many different ways that you could run a service in the background every some seconds, like set a repeating alarm ! There are basically two problems that discovered with these ways, first is that they are not always exact. Take for example the repeating alarm, it turned out that after some time it no longer respect the 15 seconds but runs at an arbitrary amount of time decided by the android system. The second problem was that the background service needs to run even if the phone is in an idle state or if the application gets killed.

So as you can imagine in this article I am presenting you the solution that worked reliably according to the requirements we set.

Before moving on I would like to emphasize that this solution by NO means should be included in an application heading for a marketplace as it will probably have negative impact on the battery and user experience. The proposed way for something like that to be done for a marketplace application is through the Work Manager.

The idea is the following: instead of creating a repeating alarm which does not respect the 15 second limit we create an exact alarm that is allowed with the idle state. The trick is that every time the alarm goes off, we set it again 15 seconds later! We are going to create now a background service which runs every 15 seconds and makes a toast for us!

Lets see the source of the class that creates the alarm:

package com.erev0s.keepthetoastscomming;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class AlarmReceiver extends BroadcastReceiver
{
    @Override
    public void onReceive(Context context, Intent intent)
    {
        Intent in = new Intent(context, MakeMyToast.class);
        context.startService(in);
        setAlarm(context);
    }

    public void setAlarm(Context context)
    {
        AlarmManager am =( AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        Intent i = new Intent(context, AlarmReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);
        assert am != null;
        am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, (System.currentTimeMillis()/1000L + 15L) *1000L, pi); //Next alarm in 15s
    }
}

This is the class of AlarmReceiver. The method setAlarm is the one actually setting the alarm to occur in 15 seconds, which when it is being triggered the onReceive method creates a new Intent for the MakyMyToast class and re-sets the alarm so it will run again after 15 seconds. This process repeats endlessly.

The class MakeMyToast is the example service we have running which it just makes a toast every time it runs. Lets see it below:

package com.erev0s.keepthetoastscomming;

import android.app.Service;
import android.content.Intent;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Process;
import android.util.Log;
import android.widget.Toast;



public class MakeMyToast extends Service {

    // This method run only one time. At the first time of service created and running
    @Override
    public void onCreate() {
        HandlerThread thread = new HandlerThread("ServiceStartArguments",
                Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        Log.d("onCreate()", "After service created");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        //Here is the source of the TOASTS :D
        Toast.makeText(this, "Freshly Made toast!", Toast.LENGTH_SHORT).show();
        
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        // We don't provide binding
        return null;
    }

}

The onStartCommand method is where the Toast are being generated. Pay attention to the START_STICKY in there, as it tells to the Android System to recreate the service in the case it is running out of memory and it needs to free up some. This way the service remains active!

Finally the MainActivity class:

package com.erev0s.keepthetoastscomming;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private static final int STORAGE_PERMISSION_CODE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AlarmReceiver alarm = new AlarmReceiver();
        alarm.setAlarm(this);
    }
}

Things are simple here, create the AlarmReceiver and call the setAlarm so our endless loop can start.

Finally for completion in the following listing you will find the androidmanifest where the service and the receiver are declared

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.erev0s.keepthetoastscomming">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name="com.erev0s.keepthetoastscomming.MakeMyToast" android:process=":remote"></service>
        <receiver android:name="com.erev0s.keepthetoastscomming.AlarmReceiver" android:process=":remote" />
    </application>

</manifest>


An example case of the app in action can be found here https://i.imgur.com/Yfke37w.mp4. Notice how the toasts keep coming no matter if the application is in the background or killed.

I hope this article will help someone and save some time when you try to recreate a similar scenario. Please do note though as mentioned in the beginning as well that this method is not supposed to be used for apps that intend to go to a marketplace as the user experience might not be ideal.