Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save calvin2021y/ef94d17983a31c6ffb4904fff5d5da8f to your computer and use it in GitHub Desktop.
Save calvin2021y/ef94d17983a31c6ffb4904fff5d5da8f to your computer and use it in GitHub Desktop.
How to create an always running service in Android

Full Source Code: https://github.com/varunon9/DynamicWallpaper/tree/always_running_service

Steps-

  1. Create a Foreground Service (MyService.java)
  2. Create a Manifest registered Broadcast Receiver (MyReceiver.java) which will start your Foreground Service
  3. In onDestroy lifecycle of MyService, send a broadcast intent to MyReceiver
  4. Launch the MyService on app start from MainActivity (see step 8)
  5. With above 4 steps, MyService will always get re-started when killed as long as onDestroy of Service gets called
  6. onDestroy method of Service is not always guaranteed to be called and hence it might not get started again
  7. To overcome this step, register a unique periodic Background Job via WorkManager which will restart MyService if not already started
  8. Register this UniquePeriodicWork (this will run every ~16 minutes) from MainActivity and it will also be responsible for the first launch of MyService
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="varunon9.me.dynamicwallpaper">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<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">
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="false"></receiver>
<service
android:name=".MyService"
android:enabled="true"
android:exported="true" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
package varunon9.me.dynamicwallpaper;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
private String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate called");
setContentView(R.layout.activity_main);
startServiceViaWorker();
}
public void onStartServiceClick(View v) {
startService();
}
public void onStopServiceClick(View v) {
stopService();
}
@Override
protected void onDestroy() {
Log.d(TAG, "onDestroy called");
stopService();
super.onDestroy();
}
public void startService() {
Log.d(TAG, "startService called");
if (!MyService.isServiceRunning) {
Intent serviceIntent = new Intent(this, MyService.class);
ContextCompat.startForegroundService(this, serviceIntent);
}
}
public void stopService() {
Log.d(TAG, "stopService called");
if (MyService.isServiceRunning) {
Intent serviceIntent = new Intent(this, MyService.class);
stopService(serviceIntent);
}
}
public void startServiceViaWorker() {
Log.d(TAG, "startServiceViaWorker called");
String UNIQUE_WORK_NAME = "StartMyServiceViaWorker";
WorkManager workManager = WorkManager.getInstance(this);
// As per Documentation: The minimum repeat interval that can be defined is 15 minutes
// (same as the JobScheduler API), but in practice 15 doesn't work. Using 16 here
PeriodicWorkRequest request =
new PeriodicWorkRequest.Builder(
MyWorker.class,
16,
TimeUnit.MINUTES)
.build();
// to schedule a unique work, no matter how many times app is opened i.e. startServiceViaWorker gets called
// do check for AutoStart permission
workManager.enqueueUniquePeriodicWork(UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, request);
}
}
package varunon9.me.dynamicwallpaper;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
public class MyReceiver extends BroadcastReceiver {
private String TAG = "MyReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive called");
// We are starting MyService via a worker and not directly because since Android 7
// (but officially since Lollipop!), any process called by a BroadcastReceiver
// (only manifest-declared receiver) is run at low priority and hence eventually
// killed by Android.
WorkManager workManager = WorkManager.getInstance(context);
OneTimeWorkRequest startServiceRequest = new OneTimeWorkRequest.Builder(MyWorker.class)
.build();
workManager.enqueue(startServiceRequest);
}
}
package varunon9.me.dynamicwallpaper;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import androidx.core.app.NotificationCompat;
public class MyService extends Service {
private String TAG = "MyService";
public static boolean isServiceRunning;
private String CHANNEL_ID = "NOTIFICATION_CHANNEL";
public MyService() {
Log.d(TAG, "constructor called");
isServiceRunning = false;
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate called");
createNotificationChannel();
isServiceRunning = true;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand called");
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Service is Running")
.setContentText("Listening for Screen Off/On events")
.setSmallIcon(R.drawable.ic_wallpaper_black_24dp)
.setContentIntent(pendingIntent)
.setColor(getResources().getColor(R.color.colorPrimary))
.build();
startForeground(1, notification);
return START_STICKY;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String appName = getString(R.string.app_name);
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
appName,
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy called");
isServiceRunning = false;
stopForeground(true);
// call MyReceiver which will restart this service via a worker
Intent broadcastIntent = new Intent(this, MyReceiver.class);
sendBroadcast(broadcastIntent);
super.onDestroy();
}
}
package varunon9.me.dynamicwallpaper;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class MyWorker extends Worker {
private final Context context;
private String TAG = "MyWorker";
public MyWorker(
@NonNull Context context,
@NonNull WorkerParameters params) {
super(context, params);
this.context = context;
}
@NonNull
@Override
public Result doWork() {
Log.d(TAG, "doWork called for: " + this.getId());
Log.d(TAG, "Service Running: " + MyService.isServiceRunning);
if (!MyService.isServiceRunning) {
Log.d(TAG, "starting service from doWork");
Intent intent = new Intent(this.context, MyService.class);
ContextCompat.startForegroundService(context, intent);
}
return Result.success();
}
@Override
public void onStopped() {
Log.d(TAG, "onStopped called for: " + this.getId());
super.onStopped();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment