Domanda

Ho un oggetto AsyncTask che inizia l'esecuzione quando viene creata l'attività e fa cose in background (download fino a 100 immagini). Tutto funziona bene, ma non c'è questo comportamento particolare che io non sono in grado di capire.

Per esempio: quando l'orientamento dello schermo android cambia allora l'attività viene distrutto e ricreato. Così ho l'override del metodo onRetainNonConfigurationInstance () e salvare tutti i dati scaricati eseguiti nel AsyncTask. Il mio scopo di fare questo è quello di non avere AsyncTask eseguito ogni attività volta che viene distrutta-creato durante i cambi di orientamento, ma come posso vedere nei miei ceppi della AsyncTask precedente è ancora in esecuzione. (I dati vengono salvati in modo corretto però)

Ho anche cercato di cancellare l'AsyncTask nel OnDestroy () di attività, ma i registri mostrano ancora AsyncTask come in esecuzione.

Questo è davvero strano comportamento e sarebbe davvero essere grati se qualcuno può dirmi la procedura corretta per fermare / annullare l'AsyncTask.

Grazie

È stato utile?

Soluzione

La risposta data da @Romain Guy è corretta. Tuttavia, vorrei aggiungere un complemento di informazioni e dare un puntatore a una libreria o 2 che può essere utilizzato per molto tempo AsyncTask esecuzione e ancora di più per asynctasks di rete orientata.

AsyncTasks sono state progettate per fare cose in background. E sì, è possibile fermarla con il metodo cancel. Come si scarica roba da Internet, vi consiglio caldamente di prende cura del tuo thread quando è l'IO bloccando statali . Si dovrebbe organizzare il download come segue:

public void download() {
    //get the InputStream from HttpUrlConnection or any other
    //network related stuff
    while( inputStream.read(buffer) != -1 && !Thread.interrupted() ) {
      //copy data to your destination, a file for instance
    }
    //close the stream and other resources
}

Uso della bandiera Thread.interrupted aiuterà il vostro filo per uscire correttamente uno stato io blocco. Il tuo filo sarà più sensibile alle un'invocazione del metodo cancel.

AsyncTask difetto di progettazione

Ma se il vostro AsyncTask dura troppo a lungo, poi si dovrà affrontare diverse questioni 2:

  1. Le attività sono poco legate al ciclo di vita di attività e non sarà possibile ottenere il risultato della vostra AsyncTask se la vostra attività muore. In effetti, sì, si può ma sarà il modo approssimativo.
  2. AsyncTask non sono molto ben documentato. Un naive, però intuitivo, l'attuazione e l'uso di un AsyncTask possono condurre rapidamente a perdite di memoria.

RoboSpice , la biblioteca vorrei introdurre, utilizza un servizio in background per eseguire questo tipo di richieste. E 'stato progettato per le richieste di rete. Esso fornisce funzionalità aggiuntive come la cache automatica dei risultati richieste.

Ecco il motivo per cui AsyncTasks sono un male per attività in esecuzione lunghi. Il seguente è un adattamento reasonning da exerpts di motivazioni RoboSpice :. l'applicazione che spiega il motivo per cui utilizzando RoboSpice sta riempiendo un bisogno sulla piattaforma Android

Il ciclo di AsyncTask e la vita di attività

AsyncTasks non seguono il ciclo di vita istanze di attività. Se si avvia un AsyncTask all'interno di un'attività e ruotate il dispositivo, l'attività sarà distrutto e sarà creata una nuova istanza. Ma l'AsyncTask non morirà. Si andrà a vivere fino al completamento.

E quando completa, l'AsyncTask non aggiornerà l'interfaccia utente della nuova attività. Infatti aggiorna il primo caso di attività che Non si vede più. Questo può portare a un'eccezione di tipo java.lang.IllegalArgumentException: View non attaccato al window manager se si utilizzare, per esempio, findViewById per recuperare una vista all'interno della attività.

Perdita di memoria problema

E 'molto comodo per creare AsyncTasks come classi interne delle vostre attività. Come l'AsyncTask avrà bisogno di manipolare i punti di vista dell'Attività quando il compito è completo o in corso, utilizzando una classe interna della attività sembra conveniente: classi interne può accesso direttamente qualsiasi campo della classe esterna.

Tuttavia, significa che il classe interna terrà un riferimento invisibile sulla sua istanza di classe esterno:. Activity

Sul lungo periodo, questo produce una perdita di memoria: se l'AsyncTask dura a lungo, mantiene l'attività di "vivo" mentre Android vorrebbe sbarazzarsi di esso in quanto può essere visualizzato non è più. L'attività non può essere garbage collection e questa è una centrale meccanismo per Android per preservare le risorse del dispositivo.

Il progresso del vostro compito saranno persi

È possibile utilizzare alcune soluzioni per creare un AsyncTask lunga corsa e gestire il ciclo di vita di conseguenza per il ciclo di vita delle attività. È possibile annullare l'AsyncTask nel metodo onStop della vostra attività o potete lasciare il vostro compito finitura asincrona, e nont perdere il suo avanzamento e ricollegare alla istanza successiva della vostra attività .

Questo è possibile e ci mostra come in motivazioni RobopSpice, ma diventa complicato e il codice non è davvero generico. Inoltre, si continua a perdere i progressi del vostro compito, se l'utente lascia l'attività e ritorna. Questo stesso problema appare con caricatori, anche se sarebbe un equivalente più semplice al AsyncTask con ricollegamento soluzione citati sopra.

L'utilizzo di un servizio di Android

L'opzione migliore è quella di utilizzare un servizio per eseguire le attività di sfondo a esecuzione prolungata. E questo è esattamente la soluzione proposta da RoboSpice. Anche in questo caso, è stato progettato per il networking, ma potrebbe essere esteso a cose relative non di rete. Questa libreria ha un gran numero di caratteristiche .

Si può anche avere un'idea di esso in meno di 30 secondi grazie ad una infografica .


E 'davvero una pessima idea di utilizzare AsyncTasks per le operazioni di lunga esecuzione. Tuttavia, essi sono bene per quelli di vita breve come l'aggiornamento di una vista dopo 1 o 2 secondi.

Vi incoraggio a scaricare il RoboSpice Motivazioni App , in realtà spiega questo in modo approfondito e fornisce esempi e dimostrazioni dei diversi modi di fare qualche rete cose relative.


Se siete alla ricerca di un'alternativa al RoboSpice per le attività non relative alla rete (per esempio, senza la cache), si potrebbe anche dare un'occhiata a Tape .

Altri suggerimenti

Romain Guy ha ragione. In realtà, un compito asincrona è responsabile per la finitura di un proprio lavoro in ogni caso. Interrupt non è il modo migliore, quindi è necessario controllare continuamente se qualcuno vuole di annullare o interrompere il vostro compito.

Diciamo che il tuo AsyncTask fa qualcosa in un ciclo molte volte. Poi si dovrebbe verificare isCancelled() in ogni ciclo.

while ( true ) {
    if ( isCancelled())
        break;
    doTheTask();
}

doTheTask() è il tuo vero lavoro e prima di farlo in ogni ciclo, è controllare se il vostro compito deve essere annullato.

In generale si dovrebbe impostare un flag nella tua classe AsyncTask o restituire un risultato appropriato dal vostro doInBackground() in modo che, nel vostro onPostExecute(), è possibile verificare se si potesse finire ciò che si desidera o se il vostro lavoro è stato annullato nel mezzo.

Di seguito non risolve il problema, ma lo impedisce: Nel file manifest fare questo:

    <activity
        android:name=".(your activity name)"
        android:label="@string/app_name"
        android:configChanges="orientation|keyboardHidden|screenSize" > //this line here
    </activity>

Quando si aggiunge questo, la vostra attività non si ricarica sul cambiamento della configurazione, e se si desidera apportare alcune modifiche quando l'orientamento modifiche appena sovrascrivere il seguente metodo dell'attività:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

        //your code here
    }

l'attività viene ricreata sul cambiamento dell'orientamento, sì questo è vero. ma è possibile continuare a AsyncTask ogni volta che questo evento accada.

si controlla su

@Override
protected void onCreate(Bundle savedInstanceState) { 
     if ( savedInstanceState == null ) {
           startAsyncTask()
     } else {
           // ** Do Nothing async task will just continue.
     }
}

-cheers

Da MVC punto di vista, attività è la controller ; è sbagliato per il controller per eseguire le operazioni che vengono conservati oltre il Visualizza (derivato da android.view.View, di solito basta riutilizzare classi esistenti). Pertanto, dovrebbe essere il Modello s 'responsabilità di avviare AsyncTasks.

Se AsyncTask non è nel pool di thread (elaborazione parallela) non si può fermare l'esecuzione AsyncTask dal momento che è già in corso di esecuzione da parte della CPU e la tua fermata o annullare verrà eseguito il comando dopo la CPU è libera (cpu è fatto con AsyncTask). Tuttavia è nel pool di thread, i compiti saranno messi in coda e saranno eseguiti uno ad uno. Così, se il comando di annullamento in coda durante l'esecuzione di operazione asincrona, si può fermare la vostra operazione asincrona.

È possibile utilizzare class MagicAppRestart da questo post a uccidere il processo insieme a tutti AsyncTasks; Android ripristinerà lo stack di attività (l'utente non parlare di nulla). E 'importante notare che l'unica notifica prima un riavvio processo sta chiamando onPause(); secondo il Android logica applicazione del ciclo di vita , l'applicazione deve essere pronto a tale risoluzione in ogni caso.

ho provato e sembra funzionare. Tuttavia, al momento ho in programma di utilizzare metodi "più civili" come riferimenti deboli della classe Application (i miei AsyncTasks sono piuttosto breve vita e si spera non tanto di memoria consuma).

Ecco alcuni codice è possibile giocare con:

MagicAppRestart.java

package com.xyz;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

/** This activity shows nothing; instead, it restarts the android process */
public class MagicAppRestart extends Activity {
    // Do not forget to add it to AndroidManifest.xml
    // <activity android:name="your.package.name.MagicAppRestart"/>
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        System.exit(0);
    }
    public static void doRestart(Activity anyActivity) {
        anyActivity.startActivity(new Intent(anyActivity.getApplicationContext(), MagicAppRestart.class));
    }
}

Il resto è ciò che Eclipse ha creato per un nuovo progetto Android per com.xyz.AsyncTaskTestActivity :

AsyncTaskTestActivity.java

package com.xyz;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class AsyncTaskTestActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.d("~~~~","~~~onCreate ~~~ "+this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    public void onStartButton(View view) {
        Log.d("~~~~","~~~onStartButton {");
        class MyTask extends AsyncTask<Void, Void, Void> {

            @Override
            protected Void doInBackground(Void... params) {
                // TODO Auto-generated method stub
                Log.d("~~~~","~~~doInBackground started");
                try {
                    for (int i=0; i<10; i++) {
                        Log.d("~~~~","~~~sleep#"+i);
                        Thread.sleep(200);
                    }
                    Log.d("~~~~","~~~sleeping over");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                Log.d("~~~~","~~~doInBackground ended");
                return null;
            }
            @Override
            protected void onPostExecute(Void result) {
                super.onPostExecute(result);
                taskDone();
            }
        }
        MyTask task = new MyTask();
        task.execute(null);
        Log.d("~~~~","~~~onStartButton }");
    }
    private void taskDone() {
        Log.d("~~~~","\n\n~~~taskDone ~~~ "+this+"\n\n");
    }
    public void onStopButton(View view) {
        Log.d("~~~~","~~~onStopButton {");
        MagicAppRestart.doRestart(this);
        Log.d("~~~~","~~~onStopButton }");
    }
    public void onPause() {   Log.d("~~~~","~~~onPause ~~~ "+this);   super.onPause(); }
    public void onStop() {    Log.d("~~~~","~~~onStop ~~~ "+this);    super.onPause(); }
    public void onDestroy() { Log.d("~~~~","~~~onDestroy ~~~ "+this); super.onDestroy(); }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button android:text="Start" android:onClick="onStartButton" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
    <Button android:text="Stop" android:onClick="onStopButton" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

</LinearLayout>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xyz"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="7" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".AsyncTaskTestActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MagicAppRestart"/>
    </application>
</manifest>

e di una parte rilevante dei registri (Si noti che solo onPause è chiamato ):

D/~~~~    (13667): ~~~onStartButton {
D/~~~~    (13667): ~~~onStartButton }
D/~~~~    (13667): ~~~doInBackground started
D/~~~~    (13667): ~~~sleep#0
D/~~~~    (13667): ~~~sleep#1
D/~~~~    (13667): ~~~sleep#2
D/~~~~    (13667): ~~~sleep#3
D/~~~~    (13667): ~~~sleep#4
D/~~~~    (13667): ~~~sleep#5
D/~~~~    (13667): ~~~sleep#6
D/~~~~    (13667): ~~~sleep#7
D/~~~~    (13667): ~~~sleep#8
D/~~~~    (13667): ~~~sleep#9
D/~~~~    (13667): ~~~sleeping over
D/~~~~    (13667): ~~~doInBackground ended
D/~~~~    (13667): 
D/~~~~    (13667): 
D/~~~~    (13667): ~~~taskDone ~~~ com.xyz.AsyncTaskTestActivity@40516988
D/~~~~    (13667): 




D/~~~~    (13667): ~~~onStartButton {
D/~~~~    (13667): ~~~onStartButton }
D/~~~~    (13667): ~~~doInBackground started
D/~~~~    (13667): ~~~sleep#0
D/~~~~    (13667): ~~~sleep#1
D/~~~~    (13667): ~~~sleep#2
D/~~~~    (13667): ~~~sleep#3
D/~~~~    (13667): ~~~sleep#4
D/~~~~    (13667): ~~~sleep#5
D/~~~~    (13667): ~~~onStopButton {
I/ActivityManager(   81): Starting: Intent { cmp=com.xyz/.MagicAppRestart } from pid 13667
D/~~~~    (13667): ~~~onStopButton }
D/~~~~    (13667): ~~~onPause ~~~ com.xyz.AsyncTaskTestActivity@40516988
I/ActivityManager(   81): Process com.xyz (pid 13667) has died.
I/WindowManager(   81): WIN DEATH: Window{4073ceb8 com.xyz/com.xyz.AsyncTaskTestActivity paused=false}
I/ActivityManager(   81): Start proc com.xyz for activity com.xyz/.AsyncTaskTestActivity: pid=13698 uid=10101 gids={}
I/ActivityManager(   81): Displayed com.xyz/.AsyncTaskTestActivity: +44ms (total +65ms)
D/~~~~    (13698): ~~~onCreate ~~~ com.xyz.AsyncTaskTestActivity@40517238
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top