-1

When the user swipes my app from the recent tasks screen, my app shuts down and the scheduled jobs don't run at all. I don't find it in the "Show cached processes" screen. But there are apps which survive this. I close them and swipe them away but they still have a cached process and their scheduled jobs run normally. If I stop them, they are restarted. What are these apps doing different to have a cached process?

I tried to create a service with START_STICKY but it didn't work.

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.e("My Service", "started");
    return Service.START_STICKY;
}

I even tried to detect the service's close and restart the service using AlarmManager. But it seems the alarm manager is not working in many devices.

 @Override
public void onTaskRemoved(Intent rootIntent) {
       Log.e("My Service", "taskremoved");
        PendingIntent service = PendingIntent.getService(
            getApplicationContext(),
            1001,
            new Intent(getApplicationContext(), Test.class),
            PendingIntent.FLAG_ONE_SHOT);

    AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 1000, service);
}

If I turn off Battery optimization, it works but the other other apps do it with optimization turned on.

mik dass
  • 381
  • 3
  • 16
  • short answer: use a service. If your Service gets killed maybe https://stackoverflow.com/questions/49637967/minimal-android-foreground-service-killed-on-high-end-phone/49782297#49782297 might give you some insight – leonardkraemer May 25 '18 at 09:41
  • @leonardkraemer Thanks for the help, but I am confused as what to do inside the service so as to keep the process in the cached state. – mik dass May 25 '18 at 09:48
  • The Android OS can close a cached service at any time. If you need to post a notification at a specific time use [AlarmManager](https://developer.android.com/training/scheduling/alarms) or [JobScheduler](https://developer.android.com/reference/android/app/job/JobScheduler) and a BroadcastReciever. – leonardkraemer May 25 '18 at 10:08
  • @leonardkraemer Thanks but the problem is these devices don't let job schedulers and others to run as well(have tried and failed), so cached process seems like the only way – mik dass May 25 '18 at 10:12

2 Answers2

0

It all depends on how are you generating the notification. If your notifications are on some actions, listen to those actions using a broadcast receiver and post a notification when that action occurs, on the other hand if it requires some code to be running constantly then you have to keep a service running like a chat application constantly listening to new messages

Suhaib Roomy
  • 2,501
  • 1
  • 16
  • 22
  • My notifications are sent from the server which in turn triggers a background job and displays the notifications. Since the notifications are sent once daily, so I don't want to keep the service running always. I thought of job scheduler, but they don't run as well on these devices. – mik dass May 25 '18 at 09:57
  • Isn't that the usecase of Firebase? – leonardkraemer May 25 '18 at 10:15
  • @leonardkraemer Have tried to trigger firebase notifications to trigger the job, but nothing happens, nothing gets triggered. – mik dass May 25 '18 at 10:21
0

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));
    }


}
Juan
  • 5,525
  • 2
  • 15
  • 26
  • Ok but I noticed that if I stop these apps from the cached process list, they spawn up again, so I think there must be some other mechanism. – mik dass May 28 '18 at 06:27
  • what api are you compiling against? – Juan May 29 '18 at 00:43
  • See my Edit I, I added an example of a service running with AlarmManager which should work on Android Oreo and back until Api 19 at least – Juan May 29 '18 at 03:12
  • Thanks for the example and explanation, I am targeting API level 26 and their is one problem. The thread always ends within 5 seconds even if I change the sleep value. Also is there any way to just cache the background process or services instead of running it again? I have seen most apps just having a cached background process instead of running a service. – mik dass May 29 '18 at 08:43
  • I am using one plus 3 and the problem is that notifications and job schedulers don't run as expected, when the app is swiped off the screen. But I noticed that the apps which have a cached process run normally. – mik dass May 29 '18 at 09:37
  • Ok after some more research, I found out that even job schedulers don't run with cached processes too. But push notifications work normally when the app has a cached process. – mik dass May 29 '18 at 10:59
  • The service runs every 30 seconds due to alarm manager and it takes 10 seconds to complete because of the sleep value in the simulated job. If it is not running like this you have missed something. Did you add the service to the manifest? I had forgotten to add that to the answer. – Juan May 29 '18 at 13:17
  • I know I added the service myself. I actually changed the sleep value but it was actually not running for the time specified. It was always running for 5 seconds. – mik dass May 30 '18 at 06:31