2015-08-07 20 views
19

Questo è stato chiesto in una delle interviste Android. Mi è stato chiesto se è possibile avviare un'altra attività asincrona (lasciatelo Task2) dal metodo doInBackground() dell'attività asincrona 1 (lascia che sia Task1). Ho esaminato i documenti che dicono quanto segue:Task asincrono Android eseguito

L'istanza dell'attività deve essere creata sul thread dell'interfaccia utente.

execute (Params ...) deve essere richiamato sul thread dell'interfaccia utente.

Come da queste dichiarazioni, penso che non dovrebbe essere possibile avviare un'attività dal metodo in background di un'altra attività. Inoltre, l'attività asincrona ha metodi UI (che non possono essere usati su un thread in background), in modo da rafforzare la mia argomentazione e io non ho risposto.

Controllando su una semplice app demo, ho visto che è davvero possibile farlo. Alcuni codice dimostrativo:

@Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 
     mContext = this; 
     init(); 
     Log.v ("gaurav", "Thread is : " + Thread.currentThread().getName()); 
     Task1 task = new Task1(); 
     task.execute(); 
    } 

class Task1 extends AsyncTask { 
    @Override 
    protected Object doInBackground(Object... params) { 
     // TODO Auto-generated method stub 
     Log.v ("gaurav", "Thread task 1 is : " + Thread.currentThread().getName()); 

     Task2 task = new Task2(); 

     task.execute(); 
     return null; 
    } 
} 

class Task2 extends AsyncTask { 
    @Override 
    protected Object doInBackground(Object... params) { 
     // TODO Auto-generated method stub 
     Log.v ("gaurav", "Thread task 2 is : " + Thread.currentThread().getName()); 

     Log.v ("gaurav", "Task 2 started"); 
     return null; 
    } 
} 

ottengo seguenti registri che indicano l'esecuzione con successo:

> 08-07 09:46:25.564: V/gaurav(2100): Thread is : main 08-07 
> 09:46:25.564: V/gaurav(2100): Thread task 1 is : AsyncTask #3 08-07 
> 09:46:25.564: V/gaurav(2100): Thread task 2 is : AsyncTask #4 08-07 
> 09:46:25.564: V/gaurav(2100): Task 2 started 

ho controllato questo su ICS, KK e dispositivo L e tutto funziona bene per tutti.

Uno dei motivi per cui posso pensare è che non sto eseguendo l'override dei metodi dell'interfaccia utente e non eseguo alcun aggiornamento dell'interfaccia utente nella mia seconda attività, quindi non causa problemi, ma non ne sono sicuro. Anche se questo è il caso, viola le regole di threading menzionate nella guida per gli sviluppatori.

Come riferimento, ho verificato anche questo collegamento: Start AsyncTask from another AsyncTask doInBackground() ma la risposta afferma di avviare la seconda attività utilizzando il metodo runOnUiThread() all'interno di doInBackground(). Vorrei un po 'di aiuto su cosa sta succedendo qui. Grazie.

+0

Un'altra ragione per cui ho utilizzato domande simili nelle interviste è determinare se il candidato k per quanto riguarda l'interruzione delle modifiche del comportamento dei thread in AsyncTasks da Honeycomb verso l'alto: http://stackoverflow.com/questions/21165505/mutliple-asynctasks-with-sequential-execution – adelphus

+0

@adelphus, non riuscivo a capire il significato di "A partire da HONEYCOMB, le attività vengono eseguite su un singolo thread per evitare errori di applicazione comuni causati dall'esecuzione parallela. " Definisce l'esecuzione seriale, ma le attività nidificate avrebbero ancora il loro onPreExecute()/onPostExecute() eseguito sul thread in background nel contesto della mia domanda? O come dice la risposta sotto riportata sul carico della classe all'avvio dell'app, in quel caso i metodi onPre/onPost di tutte le attività nidificate vengono eseguiti sul thread dell'interfaccia utente indipendentemente da dove è stata avviata l'attività? –

+0

Qui ci sono due problemi: il mio commento sta parlando del fatto che più AsyncTasks venivano usati in parallelo con 1 thread per task. Dal momento che Honeycomb, più AsyncTasks sono ora accodati in modo che vengano eseguiti tutti su un singolo thread: solo 1 può essere eseguito alla volta. Il cambiamento ha infranto un sacco di codice perché gli sviluppatori hanno assunto compiti eseguiti in parallelo. La tua domanda sull'esecuzione di onPreExecute/onPostExecute sul thread dell'interfaccia utente è un problema diverso, ma yaa110 lo descrive bene nella sua risposta. – adelphus

risposta

12

Cambiamo il codice per le seguenti:

class Task1 extends AsyncTask { 
    @Override 
    protected Object doInBackground(Object... params) { 
     // TODO Auto-generated method stub 
     Log.v ("gaurav", "Thread task 1 is : " + Thread.currentThread().getName()); 

     Task2 task = new Task2(); 
     task.execute(); 

     try { 
      Thread.sleep(5000); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 

     Log.v ("gaurav", "Log after sleeping"); 

     return null; 
    } 
} 

class Task2 extends AsyncTask { 
    @Override 
    protected Object doInBackground(Object... params) { 
     // TODO Auto-generated method stub 
     Log.v ("gaurav", "Thread task 2 is : " + Thread.currentThread().getName()); 

     Log.v ("gaurav", "Task 2 Started"); 
     return null; 
    } 
} 

Ora i rendimenti Logcat:

08-07 06:13:44.208 3073-3073/testapplication V/gaurav﹕ Thread is : main 
08-07 06:13:44.209 3073-3091/testapplication V/gaurav﹕ Thread task 1 is : AsyncTask #1 
08-07 06:13:49.211 3073-3091/testapplication V/gaurav﹕ Log after sleeping 
08-07 06:13:49.213 3073-3095/testapplication V/gaurav﹕ Thread task 2 is : AsyncTask #2 
08-07 06:13:49.213 3073-3095/testapplication V/gaurav﹕ Task 2 Started 

Come si può vedere la Task 2 viene eseguito dopo la fine della Task 1 esecuzione (anche dopo il sonno per 5 secondi). Significa che la seconda attività non verrà avviata fino a quando non viene eseguita la prima.

Perché? Il motivo è dietro il source code of AsyncTask. Perche il metodo execute():

public synchronized void execute(final Runnable r) { 
    mTasks.offer(new Runnable() { 
     public void run() { 
      try { 
       r.run(); 
      } finally { 
       scheduleNext(); 
      } 
     } 
    }); 
    if (mActive == null) { 
     scheduleNext(); 
    } 
} 

e scheduleNext() metodo:

protected synchronized void scheduleNext() { 
    if ((mActive = mTasks.poll()) != null) { 
     THREAD_POOL_EXECUTOR.execute(mActive); 
    } 
} 

La parola più importante in questi metodi è synchronized che assicura questi metodi dovrebbero essere eseguiti solo in un thread contemporaneamente. Quando chiami il metodo execute, offre un nuovo Runnable a mTask che è un'istanza della classe ArrayDeque<Runnable> che funziona come serializzatore delle diverse richieste su diversi thread [more info]. Se non è stato eseguito Runnable (ad es.if (mActive == null)), verrà chiamato il scheduleNext(), altrimenti il ​​scheduleNext() nel blocco finally verrà chiamato dopo (per qualsiasi motivo) la fine della corrente eseguita Runnable. Tutti gli Runnable s vengono eseguiti su un thread separato da THREAD_POOL_EXECUTOR.

Cosa c'è che non va nell'esecuzione di AsyncTask da altri thread? A partire da Jelly Bean, un AsyncTask è caricato in classe all'avvio dell'applicazione sul thread dell'interfaccia utente, in modo che i callback siano garantiti sul thread dell'interfaccia utente, tuttavia, prima del rilascio di Jelly Bean, se un altro thread crea i callback AsyncTaskpotrebbe non essere possibile nella discussione corretta.

Quindi, le implementazioni AsyncTask devono essere chiamate dal thread dell'interfaccia utente solo su piattaforme precedenti a Jelly Bean (+ e +).


Chiarimento: perche il seguente esempio, che si limita a chiarire le differenze tra le diverse release della piattaforma di Android:

protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_main2); 

    new Thread() { 
     @Override 
     public void run() { 
      Task1 task = new Task1(); 
      task.execute(); 
     } 
    }.start(); 
} 

class Task1 extends AsyncTask { 
    @Override 
    protected Object doInBackground(Object... params) { 
     return null; 
    } 
} 

Funziona bene su Android 5.1, ma si blocca con la seguente eccezione su Android 2.3:

08-07 12:05:20.736  584-591/github.yaa110.testapplication E/AndroidRuntime﹕ FATAL EXCEPTION: Thread-8 
    java.lang.ExceptionInInitializerError 
      at github.yaa110.testapplication.Main2Activity$1.run(Main2Activity.java:21) 
    Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 
      at android.os.Handler.<init>(Handler.java:121) 
      at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:421) 
      at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:421) 
      at android.os.AsyncTask.<clinit>(AsyncTask.java:152) 
            at github.yaa110.testapplication.Main2Activity$1.run(Main2Activity.java:21) 
+1

Come per il tuo commento, deduco che è effettivamente possibile. L'unico punto è che le attività verranno accodate in quanto per impostazione predefinita viene utilizzato l'esecutore seriale per eseguire le attività. In tal caso, il documento non dovrebbe specificare che ciò è possibile. Il "must" mi permette di supporre che causerà un crash. –

+0

@gauravjain la risposta è stata modificata. –

+0

La parte sopra è abbastanza chiara per me, che riassume che Android avrebbe accodato le richieste usando mTask che è un serializzatore. Ma l'ultima parte in cui hai detto che questo dovrebbe essere fatto solo su API> = JellyBean è un po 'complicato perché ho testato lo stesso codice su ICS. Puoi verificare perché funziona su ICS? O è una specie di scenario "potrebbe funzionare" che potrebbe non funzionare con successo ogni volta? –

Problemi correlati