8

I have a JobIntentService that is launched every time a push notification comes in to tell the service to go fetch more data. While the app is in the foreground everything works as it should.

When the app is in the background and multiple push notifications come in, the intent gets queued up and it executes the same intent multiple times putting unneeded stress on the server because the first call to the server will get all the information it needs making the other queued intent's from the push notifications unnecessary.

Is there anyway to cancel or not add the same intent to the queue or some other way to prevent the extra calls to the server?

tyczj
  • 71,600
  • 54
  • 194
  • 296
  • Could you work with something like a timestamp that you store each time the service is executed, and that you use to make sure that it's not executed more than x times in n minutes? – fjc Jul 05 '19 at 13:35
  • How about: we create 1 list of pending intent, inside `enqueueWork` we check if list pending intent contain new work or not and if it contains then we don't enqueueWork (we check it contains by add 1 more property to intent for example). in `onHandleWork`, when it finish we remove intent from pending intent list – Linh Jul 08 '19 at 07:42
  • 1
    @PhanVanLinh hmm interesting idea, I could just keep a list of the pending actions ( or even just check for that specific action that happens for push notifications to prevent other bugs from popping up due to this change ) and then remove it from the list when handle works happens – tyczj Jul 08 '19 at 13:15
  • "While the app is in the foreground everything works as it should" does it mean that if fg then it automagically works or it just not receiving next job before the old is done? – Marian Paździoch Jul 11 '19 at 13:59
  • @MarianPaździoch meaning the intents don't queue up because they execute immediately so any push notification that would come in with the app is fg would result in legitimate data vs app is bg and multiple notifications come in, intents queue and when first intent is executed it would grab all data in the first intent and any subsequent server calls will return no data – tyczj Jul 12 '19 at 13:19

2 Answers2

3

So first a couple of already similar questions with lots of good knowledge: question1 and question2. Maybe your question even duplicates those similar.

The cleanest would be to get your hands on JobIntentService's jobs queue in your MyOwnJobIntentService extends JobIntentService.

In androidx.core.app.JobIntentService.java there is:

final ArrayList<CompatWorkItem> mCompatQueue;

And in your MyOwnJobIntentService extends JobIntentService:

if(mCompatQueue.isEmpty()){
  //only in this case enqueue new job   
}

But unfortunately mCompatQueue is not public field.

And 10 minutes later we're getting with working solution -> SingleJobIntentService a JobIntentService which will not queue jobs if it's already working.

import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v4.app.JobIntentService;
import android.util.Log;

public class SingleJobIntentService extends JobIntentService {

    private static final String TAG = "SingleJobIntentService";

    private Intent theOnlyJobIhave = null;

    public static void enqueue(Context context, Intent work) {
        Log.d(TAG, "enqueue: someone tries to add me work " + work.hashCode());
        JobIntentService.enqueueWork(
                context,
                SingleJobIntentService.class,
                SingleJobIntentService.class.hashCode(),
                work);
    }

    @Override
    protected void onHandleWork(@NonNull final Intent theWorkIgot) {
        Log.d(TAG, "onHandleWork: " + this.hashCode());
        if (theOnlyJobIhave == null) {
            theOnlyJobIhave = theWorkIgot;
            final int extraValue = theOnlyJobIhave.getIntExtra(MainActivity.KEY, -500);
            Log.d(TAG, "onHandleWork: " + extraValue);
            try {
                Thread.sleep(7000); //this simulates fetch to server
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            Log.d(TAG, "onHandleWork I'm already busy, refuse to work >:(");
        }
        Log.d(TAG, "onHandleWork end");
    }
}

You may want test it with simple activity with a button:

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;

public class MainActivity extends AppCompatActivity {

    public static final String KEY = "KEYKEY";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.button).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                final Intent theIntent = new Intent();
                theIntent.putExtra(KEY, 666);
                SingleJobIntentService.enqueue(MainActivity.this, theIntent);
            }
        });
    }
}

Notice: you must be careful with threading as the solution I gave is not thread-safe. For example in androidx.core.app.JobIntentService.java touching mCompatQueue is synchronized. EDIT: after thinking about it -> since onHandleWork is called from JobIntentService from single thread there is no thread issue and the solution is thread safe!

-2

You can use the notification id same for all the notification so it will override the old notification and gives you the only latest one with the latest intent.

If you have multiple type of notification for different actions you can create a different group of notification providing the intents which can work for a special type of action only.

And also keep clearing the group of notification so once ane one of the notification is clicked you can clear out the same group of notification.

Hardik Chauhan
  • 2,750
  • 15
  • 30
  • This isnt about the notifications API at all, its about multiple intents sent to JobIntentService that queue up – tyczj Jul 11 '19 at 13:12