Skip to content

Instantly share code, notes, and snippets.

@varunon9
Last active March 3, 2024 13:41
Show Gist options
  • Star 34 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save varunon9/f2beec0a743c96708eb0ef971a9ff9cd to your computer and use it in GitHub Desktop.
Save varunon9/f2beec0a743c96708eb0ef971a9ff9cd 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();
}
}
@Lee858
Copy link

Lee858 commented Aug 27, 2021

Sometimes Foreground services stop without onDestroy. I need to get an MQTT message when the application closes.

The onDestroy for the initial instance rarely executes. If you return START_STICKY then it will start another instance that it kills a lot of the time. The onDestroy usually runs when the second instance is killed. I'm successfully using it. The periodic work request every 16 minutes is the insurance to make sure that it's restarted sooner or later.

@devtadiyal
Copy link

Hey, thanks for your detailed explanation. While I'm working on my endless map, I found out that doze mode is ruining the flow of foreground service that provides me the device's location. I considering to use this method but my problem that the onDestroy() execution is not guarantee. Or that I'm missing something. Can you introduce me to something new?

Yes u r right in Doze mode Work Manager not perform any task.

@ubrainrepo
Copy link

Without AutoStart permission it is possible to start service again even device restart (Xiaomi device) ?

@pawlowskim
Copy link

AFAIR no, you need to have autostart permissions otherwise you won't receive boot event or you cannot run in from the background.

@busslina
Copy link

busslina commented May 30, 2023

Well, I will make some points:

  • Still in 2023, as developers we can't make a non-stop service on mobile phones. This is not serious. This is a joke. Yes, there is battery issues, but if the user allows an app to run forever, it must be done like this. A special permission "App can Run forever". There is "REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" but this is still the same joke.
  • I know an app (it is not in the Google Play store) that runs forever in the foreground notification mode. I am not an Android expert, now I'm focused on Flutter, and if someone want to help every body and study this app it would be great. It remains active for 4 days easily... Sending location to remote server every 5 seconds....
    https://gpslogger.app/
    https://github.com/mendhak/gpslogger/
    mendhak/gpslogger#1080
  • I think the same people that don't want us to eat meat, to drive cars, to own a house... are the same that are fucking us with this shit. (1984-2030)
  • In Flutter, there is a package that I think is near to do the trick. I will update in few days.
    https://pub.dev/packages/flutter_foreground_task

urbandroid-team/dont-kill-my-app#639

@moradi-morteza
Copy link

I read telegram source code, and test it, they had a non-stop service, how they can do it?

@busslina
Copy link

I read telegram source code, and test it, they had a non-stop service, how they can do it?

Maybe having their app on manufacturers white list

@varunon9
Copy link
Author

I read telegram source code, and test it, they had a non-stop service, how they can do it?

Maybe having their app on manufacturers white list

This can be one possibility. But I don't think this is the only solution.
https://github.com/varunon9/AarogyaSetu_Android This app has an unstoppable service (even if you kill it, it'll appear again in a few seconds). I don't think they are whitelisted by manufacturers. I had talked to early developers of this app and they didn't mention anything about the manufacturer's white list. They pointed me toward standard documentation and I went through code as well to figure it out, but couldn't make any breakthrough due to my limited understanding of native Android.

@busslina
Copy link

busslina commented Jun 20, 2023

I read telegram source code, and test it, they had a non-stop service, how they can do it?

Maybe having their app on manufacturers white list

This can be one possibility. But I don't think this is the only solution. https://github.com/varunon9/AarogyaSetu_Android This app has an unstoppable service (even if you kill it, it'll appear again in a few seconds). I don't think they are whitelisted by manufacturers. I had talked to early developers of this app and they didn't mention anything about the manufacturer's white list. They pointed me toward standard documentation and I went through code as well to figure it out, but couldn't make any breakthrough due to my limited understanding of native Android.

The fact that it will appear again in a few seconds after being killed can indicate that is using Android Alarm Manager that executes periodically and checks if it needs to be relaunched. I do something similar but I'm not sure that the application that you mentioned is doing that after seeing his Android Manifest file.

You can see here the second service has an intent filter related with Firebase, so maybe it is restarted via FCM.

<service android:name="nic.goi.aarogyasetu.background.BluetoothScanningService" />
<service
    android:name="nic.goi.aarogyasetu.FcmMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

@perfeshnal
Copy link

Thank you Varun kumar to sharing source code
your code problem is we can't stop service manually either
I have some ideas, but I wanted to ask your opinion as well

@ally9335
Copy link

ally9335 commented Mar 3, 2024

I have a service and no Activity. When I need to run it, I do it via adb as "start-foreground-service".
My phone is rooted.

How would I make a service run indefinitely and restart itself when it crashes or gets killed? With no activity.

This solution utilises an Activiy. But is there a way to do it without one?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment