Domanda JobScheduler postare due volte i lavori (non previsto)


Sto scrivendo un tutorial su JobScheduler e trovo uno strano comportamento. Chiedo che vengano programmati 3 lavori diversi in 1 secondo (.setOverrideDeadline (1000)), ma sono tutti inviati ed eseguiti due volte ... Quindi qui il codice:

public class MyApplication extends Application {
    private static final int JOB_ID_HanlderThread = 100;
    private static final int JOB_ID_ExecutorService = 200;
    private static final int JOB_ID_AsyncTask = 300;
    JobScheduler mJobScheduler;
    ExecutorService myExecutorServiceForJobs=null;
    private static MyApplication INSTANCE;
    public static MyApplication getInstance(){
        return INSTANCE;
    }


    /**
     * Called when the application is starting, before any activity, service,
     * or receiver objects (excluding content providers) have been created.
     * Implementations should be as quick as possible (for example using
     * lazy initialization of state) since the time spent in this function
     * directly impacts the performance of starting the first activity,
     * service, or receiver in a process.
     * If you override this method, be sure to call super.onCreate().
     */
    @Override
    public void onCreate() {
        Log.e("MyApplication", "*********************** onCreate *****************************");
        super.onCreate();
        //use only for the ExceutorService case
        INSTANCE=this;
        //instanciate your JobScheduler
        mJobScheduler= (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
        Log.e("MyApplication", "onCreate: JobScheduler instanciate");

        //this first example use the HandlerThread (no need of executor service)
        //---------------------------------------------------------------------
        //define your JobServices here
        JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_HanlderThread,
                new ComponentName( getPackageName(),
                        MyJobServiceUsingHandlerThread.class.getName() ) );
        //begin in one second
        builder.setOverrideDeadline(1000);
        int returnedValue;
        //the return value is failure(0) or success(1) not the JobId if success (Javadoc wrong)
        returnedValue=mJobScheduler.schedule( builder.build() );
        //launch it
        if( returnedValue <= 0 ) {
            //If something goes wrong (manage exception/error is better than logging them)
            Log.e("MyApplication", "onCreate: JobScheduler launch the task failure");
        }else{
            //nothing goes wrong
            Log.e("MyApplication", "onCreate: JobScheduler launch the task suceess JOB_ID_HanlderThread "+returnedValue);
        }

        //this second example use ExecutorService
        //---------------------------------------
        //then again define your Job and launch it
        JobInfo.Builder builder1 = new JobInfo.Builder(JOB_ID_ExecutorService,
                new ComponentName( getPackageName(),
                        MyJobServiceUsingExecutor.class.getName() ) );
        //begin in one second
        builder1.setOverrideDeadline(1000);
        //launch it
        returnedValue=mJobScheduler.schedule( builder1.build() );
        if( returnedValue <= 0 ) {
            //If something goes wrong (manage exception/error is better than logging them)
            Log.e("MyApplication", "onCreate: JobScheduler launch the task failure");
        }else{
            //nothing goes wrong
            Log.e("MyApplication", "onCreate: JobScheduler launch the task suceess JOB_ID_ExecutorService "+returnedValue);
        }

        //this third example use AsyncTask
        //--------------------------------
        //then again define your Job and launch it
        JobInfo.Builder builder2 = new JobInfo.Builder(JOB_ID_AsyncTask,
                new ComponentName( getPackageName(),
                        MyJobServiceUsingAsyncTask.class.getName() ) );
        //begin in one second
        builder2.setOverrideDeadline(1000);
        //launch it
        returnedValue=mJobScheduler.schedule( builder2.build() );
        if( returnedValue <= 0 ) {
            //If something goes wrong (manage exception/error is better than logging them)
            Log.e("MyApplication", "onCreate: JobScheduler launch the task failure");
        }else{
            //nothing goes wrong
            Log.e("MyApplication", "onCreate: JobScheduler launch the task suceess JOB_ID_AsyncTask "+returnedValue);
        }
    }   

Usando questo codice mi aspetto che i miei lavori vengano eseguiti una volta, ma se guardo il log ottengo:

10-20 06:45:13.118 13041-13041/? E/MyApplication: *********************** onCreate *****************************
10-20 06:45:13.122 13041-13041/? E/MyApplication: onCreate: JobScheduler instanciate
10-20 06:45:13.126 13041-13041/? E/MyApplication: onCreate: JobScheduler launch the task suceess JOB_ID_HanlderThread 1
10-20 06:45:13.127 13041-13041/? E/MyApplication: onCreate: JobScheduler launch the task suceess JOB_ID_ExecutorService 1
10-20 06:45:13.130 13041-13041/? E/MyApplication: onCreate: JobScheduler launch the task suceess JOB_ID_AsyncTask 1
10-20 06:45:13.559 13041-13041/? E/MyJobServiceHandler: onStartJob called <--------------------------------
10-20 06:45:13.572 13041-13041/? E/MyJobServiceExecutor: onStartJob called <--------------------------------
10-20 06:45:14.133 13041-13041/? E/MyJobServiceAsync: onStartJob called <--------------------------------
10-20 06:45:14.141 13041-13041/? E/MyJobServiceAsync: onStartJob called <--------------------------------
10-20 06:45:18.571 13041-13066/? E/MyHandler: The work is done in a separate thread called MyJobServiceUsingHandlerThread
10-20 06:45:18.573 13041-13041/? E/MyJobServiceHandler: onDestroy called, Looper is dead  <******************************************
10-20 06:45:18.574 13041-13041/? E/MyJobServiceHandler: onStartJob called <--------------------------------
10-20 06:45:18.576 13041-13067/? E/MyRunnable: The work is done in a separate thread called MyJobServiceUsingExecutorService
10-20 06:45:18.577 13041-13041/? E/MyJobServiceExecutor: onDestroy called, executor service is dead  <******************************************
10-20 06:45:18.577 13041-13041/? E/MyApplication: killMyExecutorServiceForJob called
10-20 06:45:18.577 13041-13041/? E/MyApplication: myExecutorServiceForJobs isShutDown
10-20 06:45:18.580 13041-13041/? E/MyJobServiceExecutor: onStartJob called <--------------------------------
10-20 06:45:19.145 13041-13070/? E/MyAsyncTask: The work is done in a separate thread called AsyncTask #1
10-20 06:45:19.145 13041-13041/? E/MyAsyncTask: The work is finished  <******************************************
10-20 06:45:23.576 13041-13075/? E/MyHandler: The work is done in a separate thread called MyJobServiceUsingHandlerThread
10-20 06:45:23.577 13041-13041/? E/MyJobServiceHandler: onDestroy called, Looper is dead  <******************************************
10-20 06:45:23.582 13041-13076/? E/MyRunnable: The work is done in a separate thread called MyJobServiceUsingExecutorService
10-20 06:45:23.584 13041-13041/? E/MyJobServiceExecutor: onDestroy called, executor service is dead  <******************************************
10-20 06:45:23.584 13041-13041/? E/MyApplication: killMyExecutorServiceForJob called
10-20 06:45:23.584 13041-13041/? E/MyApplication: myExecutorServiceForJobs isShutDown
10-20 06:45:24.147 13041-13077/? E/MyAsyncTask: The work is done in a separate thread called AsyncTask #2
10-20 06:45:24.148 13041-13041/? E/MyAsyncTask: The work is finished  <******************************************

Quello che faccio nel tutorial è che eseguo un Job con HandlerThread, un altro con ExecutorService e l'ultimo usando AsyncTask per spiegare come eseguire il lavoro in un thread in background. Mostro queste diverse tecniche perché ci possono essere casi d'uso in cui si desidera accodare i propri lavori nello stesso thread (HandlerThread) o gestire un pool di Thread (ExecutorService) o semplicemente utilizzare thread non gestiti (AsyncTask).

Definisco i lavori e li programma in MyApplication: metodo onCreate. Per dare un'occhiata più approfondita al codice, l'ho messo su GitHub qui:https://github.com/MathiasSeguy-Android2EE/JobSchedulerForGitHub


11
2017-10-20 11:44


origine


risposte:


Grazie per questo - ho lavorato a JobScheduler. In base alla tua app (grazie!) Sono riuscito a riproporlo abbastanza facilmente e rintracciare la causa del bug.

tl; dr, Questo è un caso che non succederà molto spesso al di fuori di un'app tutorial. Per aggirare il problema nel tuo tutorial, aumenta la scadenza del tuo lavoro a un tempo superiore a quello di esecuzione dei thread in background.

Quello che sta succedendo è che pianifichi i tuoi lavori in successione e che JobScheduler li esegua praticamente subito mentre sono in programma. Tuttavia, un secondo dopo (l'un secondo è la parte che non avverrà per una app "reale"), l'allarme di scadenza del limite si attiva e il responsabile del processo decide in modo molto aggressivo che è necessario eseguire nuovamente qualsiasi lavoro la cui scadenza è scaduta (l'API contratto stabilisce che "scadenza scadenza" supera tutte le altre considerazioni), quindi lo inserisce nella coda in sospeso. Non appena il processo di esecuzione è terminato, la coda in attesa viene controllata, e lì c'è un lavoro, quindi è eseguito.

Quindi, il lavoro scatterà 2x se la scadenza scade mentre il lavoro è in esecuzione. Assicurarsi che la scadenza scada prima che il lavoro venga eseguito (il che si traduce nell'esecuzione del lavoro) o dopo (l'allarme non sarà effettivamente atterrato b / c il lavoro è già finito) e tutto funziona come previsto.

Ho risolto questo problema in Android N (purtroppo M è già stato spedito) e ho aggiunto un test CTS per assicurarmi che rimanga fisso.  Grazie per averlo portato alla nostra attenzione


18
2017-10-23 00:42