Pergunta

Eu tenho um objeto AsyncTask que começa a ser executado quando a atividade é criada e faz coisas em segundo plano (baixa até 100 imagens).Tudo funciona bem, mas existe um comportamento peculiar que não consigo entender.

Por exemplo:quando a orientação da tela do Android muda, a atividade é destruída e criada novamente.Então eu substituo o método onRetainNonConfigurationInstance() e salvo todos os dados baixados executados no AsyncTask.Meu objetivo ao fazer isso é não fazer com que o AsyncTask seja executado sempre que a atividade for destruída e criada durante as mudanças de orientação, mas como posso ver em meus logs, o AsyncTask anterior ainda está em execução.(Os dados são salvos corretamente)

Até tentei cancelar o AsyncTask no método onDestroy() da atividade, mas os logs ainda mostram o AsyncTask em execução.

Este é um comportamento realmente estranho e ficaria muito grato se alguém pudesse me dizer o procedimento correto para parar/cancelar o AsyncTask.

Obrigado

Foi útil?

Solução

A resposta dada por @Romain Guy está correta. No entanto, gostaria de adicionar um complemento de informações e dar um ponteiro a uma biblioteca ou 2 que pode ser usada para assíncas de longa data e ainda mais para as assínctas de rede orientadas para a rede.

As assínctas foram projetadas para fazer coisas em segundo plano. E sim, você pode parar de usar o cancel método. Ao baixar coisas da internet, eu sugiro fortemente você Cuide do seu tópico quando é o estado de bloqueio de IO. Você deve organizar seu download da seguinte maneira:

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
}

Usando o Thread.interrupted A bandeira ajudará seu tópico a sair corretamente de um estado de IO bloqueador. Seu tópico será mais responsivo a uma invocação do cancel método.

Falha no design da assíncada

Mas se a sua assíncada durar muito tempo, você enfrentará 2 problemas diferentes:

  1. As atividades estão mal ligadas ao ciclo de vida da atividade e você não obterá o resultado da sua assíncrogem se sua atividade morrer. De fato, sim, você pode, mas será o caminho difícil.
  2. A assíncada não está muito bem documentada. Uma implementação e o uso ingênuos, embora intuitivos, e o uso de uma assíncada pode levar rapidamente a vazamentos de memória.

Robospice, a biblioteca que eu gostaria de introduzir, usa um serviço de segundo plano para executar esse tipo de solicitação. Ele foi projetado para solicitações de rede. Ele fornece recursos adicionais, como o armazenamento automático dos resultados das solicitações.

Aqui está a razão pela qual as assínctas são ruins para tarefas de longa duração. O seguinte razoatorial é uma adaptação de exercícios de Motivações de robospice : O aplicativo que explica por que o uso do Robospice está preenchendo uma necessidade na plataforma Android.

O ciclo de vida da assíncada e da atividade

As assínctas não seguem o ciclo de vida das instâncias de atividade. Se você iniciar uma assíncotagem dentro de uma atividade e girar o dispositivo, a atividade será destruída e uma nova instância será criada. Mas a assíncada não morrerá. Ele continuará vivendo até concluir.

E quando for concluído, o AsyncTask não atualizará a interface do usuário da nova atividade. De fato, atualiza a primeira instância da atividade que não é mais exibida. Isso pode levar a uma exceção do tipo java.lang.illegalargumentException: visualizar não anexado ao gerenciador de janelas se você usar, por exemplo, o FindViewById para recuperar uma exibição dentro da atividade.

Problema de vazamento de memória

É muito conveniente criar assíncas como classes internas de suas atividades. Como a assínceta precisará manipular as visões da atividade quando a tarefa estiver concluída ou em andamento, o uso de uma classe interna da atividade parece conveniente: as classes internas podem acessar diretamente qualquer campo da classe externa.

No entanto, isso significa que a classe interna manterá uma referência invisível em sua instância da classe externa: a atividade.

A longo prazo, isso produz um vazamento de memória: se o AsyncTask durar por muito tempo, mantém a atividade "viva", enquanto o Android gostaria de se livrar dela, pois não pode mais ser exibido. A atividade não pode ser coletada de lixo e esse é um mecanismo central para o Android preservar os recursos no dispositivo.

O progresso da sua tarefa será perdido

Você pode usar algumas soluções alternativas para criar uma assíncada de longa data e gerenciar seu ciclo de vida de acordo com o ciclo de vida da atividade. Você também pode Cancelar a assínceta no método Onstop de sua atividade Ou você pode deixar sua tarefa assíncrona terminar, e não perder seu progresso e vincule -o à próxima instância de sua atividade.

Isso é possível e mostramos como nas motivações do RobopSpice, mas se torna complicado e o código não é realmente genérico. Além disso, você ainda perderá o progresso da sua tarefa se o usuário deixar a atividade e voltar. Esse mesmo problema aparece com os carregadores, embora seja um equivalente mais simples à assíncotagem com a remoção de soluções alternativas mencionadas acima.

Usando um serviço Android

A melhor opção é usar um serviço para executar suas tarefas de fundo de longa execução. E essa é exatamente a solução proposta pelo Robospice. Novamente, ele foi projetado para networking, mas pode ser estendido a coisas relacionadas à rede. Esta biblioteca tem um grande número de recursos.

Você pode até ter uma idéia disso em menos de 30 segundos, graças a um infográficos.


É realmente uma idéia muito muito ruim usar as sincronizações para operações de longa execução. No entanto, eles são bons para a vida curta, como atualizar uma visão após 1 ou 2 segundos.

Eu encorajo você a baixar o Aplicativo de motivações de robospice, realmente explica isso em profundidade e fornece amostras e demonstrações das diferentes maneiras de fazer algumas coisas relacionadas à rede.


Se você está procurando uma alternativa ao Robospice para tarefas não relacionadas à rede (por exemplo, sem armazenamento em cache), também pode dar uma olhada Fita.

Outras dicas

Romain Guy está certo.Na verdade, uma tarefa assíncrona é responsável por finalizar seu próprio trabalho em qualquer caso.Interromper não é a melhor maneira, então você deve verificar continuamente se alguém deseja que você cancele ou interrompa sua tarefa.

Digamos que seu AsyncTask faz algo em loop muitas vezes.Então você deve verificar isCancelled() em cada ciclo.

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

doTheTask() é o seu verdadeiro trabalho e antes de fazê-lo em cada loop, você verifica se sua tarefa deve ser cancelada.

Geralmente você deve definir uma bandeira em seu AsyncTask classe ou retornar um resultado apropriado do seu doInBackground() para que, em seu onPostExecute(), você pode verificar se conseguiu terminar o que deseja ou se seu trabalho foi cancelado no meio.

A seguir, não resolve o seu problema, mas evita: no aplicativo manifesto, faça isso:

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

Quando você adiciona isso, sua atividade não recarregue a alteração da configuração e, se você deseja fazer algumas alterações quando a orientação for alterada, apenas substitui o seguinte método da atividade:

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

        //your code here
    }

A atividade é recriada na mudança de orientação, sim, isso é verdade. Mas você pode continuar com o ASYNCTASK sempre que esse evento acontecer.

você verifica

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

-Felicidades

De MVC ponto de vista, atividade é o Controlador; é errado para o Controlador para realizar operações que sobrevivem ao Visão (Derivado de Android.View.View, geralmente você apenas reutiliza as classes existentes). Portanto, deve ser o ModeloResponsabilidade de iniciar assínctas.

Se o AsyncTask não estiver no pool de threads (processamento paralelo), você não poderá parar de executar o AsyncTask, pois ele já está sendo executado pela CPU e seu comando Stop ou Cancel será executado depois que a CPU estiver gratuita (a CPU é feita com o AsyncTask). No entanto, está no pool de threads, as tarefas serão na fila e serão executadas uma a uma. Portanto, se o seu comando cancelar a fila ao executar a tarefa Async, ela poderá interromper sua tarefa assíncrona.

Você pode usar class MagicAppRestart a partir de esta postagempara Mate o processo junto com todas as assínctas; O Android restaurará a pilha de atividades (o usuário não mencionará nada). É importante observar que a única notificação antes de um processo reiniciar está ligando onPause(); de acordo com Android App Lifecycle Logic, seu aplicativo deve estar pronto para essa rescisão de qualquer maneira.

Eu tentei e parece funcionar. No entanto, no momento em que pretendo usar métodos "mais civilizados", como referências fracas da classe de aplicação (minhas assíncronas são bastante curtas e, esperançosamente, não são tanto consumidoras de memória).

Aqui está algum código com o qual você pode jogar:

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

O resto é o que o Eclipse criou para um novo projeto Android para com.xyz.asyncTaskTestActivity:

AssynctaskTestactivity.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 uma parte relevante dos troncos (observe que onPause é chamado):

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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top