As far as I know you have control over your app upto the onStop() activity callback, and onPause() in older versions of Android (pre api 11). After that Android may kill your app any time.
So to get back to the state you app was when it was closed, you have to save the state in onPause() or in onStop() -Api 11 or newer- and restore it in onResume() or onStart() depending on the api you are supporting.
If the process is wiped out of memory or put in a cache for some time, I don't think it is under your control as a developer.
See this other ansewer too: On Android, what's the difference between running processes and cached background processes?
EDIT I
After reading again your question, I think it is more about your service being restarted if it gets killed than about the cached processes.
If it is that case, I put together a Service
that is started and stopped from an Activity
and runs powered by the AlarmManager
.
The Activity can start an stop the service. After ther service is started you can exit the Activity and see the service running.
(FYI, at least in my Oreo emulator I could see the service in the cached processes.)
When the activity starts it checks if the service is "running" in the sense you haven't stopped it. And uses that information to set the status of the buttons.
The service tells the activity (if it is there) about the changes in its run status.
And regarding the scheduling, the service reschedules on each execution.
Along the code there are distinctions between version of Android (Oreo and Marshmellow) because of the way they treat background processes. As of Oreo to be able to run without time limits, the service must run in foreground which shows a message to the user and makes him aware of what is going on.
The Manifest (change the package name)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="your.package.here">
<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=".ServiceExample">
</service>
</application>
</manifest>
The Activity.
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
//Some intent actions to receive from the service.
public static final String STOPPED = "stopped";
public static final String STARTING = "starting";
public static final String RUNNING = "running";
public static final String WAITING = "waiting";
public static final String STOPPING = "stopping";
TextView tvDisplay;
Button btnStart, btnStop;
String serviceStatus = STOPPED;
BroadcastReceiver receiver;
IntentFilter intentFilter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction(STOPPED);
intentFilter.addAction(STARTING);
intentFilter.addAction(RUNNING);
intentFilter.addAction(WAITING);
intentFilter.addAction(STOPPING);
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
action = action == null ? "" : action;
if(!action.equals("")) {
serviceStatus = action;
updateUi();
}
}
};
tvDisplay = findViewById(R.id.tvDisplay);
btnStart = findViewById(R.id.btnStart);
btnStop = findViewById(R.id.btnStop);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this.getApplicationContext(), ServiceExample.class);
intent.putExtra("stop",false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent);
} else {
startService(intent);
}
}
});
btnStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this.getApplicationContext(), ServiceExample.class);
intent.putExtra("stop",true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent);
} else {
startService(intent);
}
}
});
}
@Override
protected void onPause() {
super.onPause();
LocalBroadcastManager.getInstance(getApplicationContext())
.unregisterReceiver(receiver);
}
@Override
protected void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(getApplicationContext())
.registerReceiver(receiver,intentFilter);
if(isMyServiceRunning(ServiceExample.class)){
serviceStatus = WAITING;
}else{
serviceStatus = STOPPED;
}
updateUi();
}
private void updateUi(){
if(serviceStatus.equals(STOPPED)){
btnStart.setEnabled(true);
btnStop.setEnabled(false);
}else if(serviceStatus.equals(STOPPING)){
btnStart.setEnabled(false);
btnStop.setEnabled(false);
}else if(serviceStatus.equals(STARTING)){
btnStart.setEnabled(false);
btnStop.setEnabled(false);
}else if(serviceStatus.equals(RUNNING)){
btnStart.setEnabled(false);
btnStop.setEnabled(true);
}else if(serviceStatus.equals(WAITING)){
btnStart.setEnabled(false);
btnStop.setEnabled(true);
}
tvDisplay.setText(serviceStatus);
}
private boolean isMyServiceRunning(Class<?> serviceClass) {
//This part checks for a running service
//https://stackoverflow.com/questions/600207/how-to-check-if-a-service-is-running-on-android
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
//This part checks if there is a pending intent (scheduled in the alarm manager)
Intent intent = new Intent(getApplicationContext(), ServiceExample.class);
PendingIntent testIntent;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
testIntent = PendingIntent.getForegroundService(getApplicationContext(),12345,intent,PendingIntent.FLAG_NO_CREATE);
}else{
testIntent = PendingIntent.getService(getApplicationContext(),12345,intent,PendingIntent.FLAG_NO_CREATE);
}
return (testIntent != null);
}
}
The Service
import android.app.AlarmManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast;
public class ServiceExample extends Service {
private boolean stop = false;
private String CHANNEL_ID = "ServiceExample";
private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
@Override
public void onCreate() {
Toast.makeText(this, "creating service", Toast.LENGTH_SHORT).show();
stop = false;
alarmMgr = (AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(getApplicationContext(), ServiceExample.class);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
alarmIntent = PendingIntent.getForegroundService(getApplicationContext(),12345,intent,0);
}else{
alarmIntent = PendingIntent.getService(getApplicationContext(),12345,intent,0);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
.setContentText("ServiceExample is running")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true);
Notification notification = builder.build();
startForeground(1, notification);
boolean stopExtra = intent.getBooleanExtra("stop", false);
if(stopExtra) {
if (!stop) {
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(MainActivity.STOPPING));
stop = true;
alarmMgr.cancel(alarmIntent);
alarmIntent.cancel();
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(MainActivity.STOPPED));
}
}else{
if(stop){
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(MainActivity.STARTING));
stop = false;
}
}
if(!stop){
Thread thread = new Thread(new SomeJobClass());
thread.start();
long now = System.currentTimeMillis();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (alarmMgr != null) {
alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, now + 30000, alarmIntent);
}
}else{
if (alarmMgr != null) {
alarmMgr.setExact(AlarmManager.RTC_WAKEUP, now + 30000, alarmIntent);
}
}
}else{
stopSelf();
}
return START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
private final class SomeJobClass implements Runnable{
@Override
public void run() {
broadcastToActivity(MainActivity.RUNNING);
try {
//Simulate the time it takes to run the job
Thread.sleep(10000);
} catch (InterruptedException e) {
// Restore interrupt status.
Thread.currentThread().interrupt();
}
broadcastToActivity(MainActivity.WAITING);
stopSelf();
}
}
private void broadcastToActivity(String intentAction){
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(intentAction));
}
}