AsyncTask e tratamento de erros no Android

147

Estou convertendo meu código de usar Handlerpara AsyncTask. O último é excelente no que faz - atualizações assíncronas e manipulação de resultados no thread principal da interface do usuário. O que não está claro para mim é como lidar com exceções se algo der errado AsyncTask#doInBackground.

O jeito que eu faço é ter um manipulador de erros e enviar mensagens para ele. Funciona bem, mas é a abordagem "certa" ou existe uma alternativa melhor?

Também entendo que, se eu definir o manipulador de erros como um campo de atividade, ele deverá ser executado no thread da interface do usuário. No entanto, algumas vezes (de forma imprevisível), recebo uma exceção dizendo que o código acionado Handler#handleMessageestá sendo executado no thread errado. Em Activity#onCreatevez disso, devo inicializar o manipulador de erros ? Colocando runOnUiThreadem Handler#handleMessageparece redundante, mas ele executa muito confiável.

Bostone
fonte
Por que você deseja converter seu código? Havia uma boa razão?
HGPB
4
@Haraldo é uma prática melhor codificação, pelo menos é assim que eu me sinto
Bostone

Respostas:

178

Funciona bem, mas é a abordagem "correta" e existe uma alternativa melhor?

Eu seguro a Throwableou Exceptionna AsyncTaskprópria instância e, em seguida, faço algo com ela onPostExecute(), para que meu tratamento de erros tenha a opção de exibir uma caixa de diálogo na tela.

CommonsWare
fonte
8
Brilhante! Não há necessidade de macaco com manipuladores mais
Bostone
5
É assim que devo me apegar ao Throwable ou Exception? "Adicione uma variável de instância à sua própria subclasse AsyncTask que manterá o resultado do seu processamento em segundo plano." Quando você receber uma exceção, armazene a exceção (ou algum outro código / sequência de erros) nessa variável. Quando onPostExecute é chamado, veja se esta variável de instância está configurada para algum erro. Em caso afirmativo, mostre uma mensagem de erro. "(Do usuário" Streets of Boston " groups.google.com/group/android-developers/browse_thread/thread/… )
OneWorld
1
@ OneWorld: Sim, tudo bem.
CommonsWare
2
Oi CW, você poderia explicar sua maneira de fazer isso com mais detalhes - talvez com um breve exemplo de código? Muito obrigado!!
Bruiser 23/03
18
@Bruiser: github.com/commonsguy/cw-lunchlist/tree/master/15-Internet/… tem o AsyncTaskseguinte padrão descrito.
CommonsWare 23/03
140

Crie um objeto AsyncResult (que você também pode usar em outros projetos)

public class AsyncTaskResult<T> {
    private T result;
    private Exception error;

    public T getResult() {
        return result;
    }

    public Exception getError() {
        return error;
    }

    public AsyncTaskResult(T result) {
        super();
        this.result = result;
    }

    public AsyncTaskResult(Exception error) {
        super();
        this.error = error;
    }
}

Retorne esse objeto dos métodos AsyncTask doInBackground e verifique-o no postExecute. (Você pode usar esta classe como uma classe base para suas outras tarefas assíncronas)

Abaixo está uma maquete de uma tarefa que obtém uma resposta JSON do servidor da web.

AsyncTask<Object,String,AsyncTaskResult<JSONObject>> jsonLoader = new AsyncTask<Object, String, AsyncTaskResult<JSONObject>>() {

        @Override
        protected AsyncTaskResult<JSONObject> doInBackground(
                Object... params) {
            try {
                // get your JSONObject from the server
                return new AsyncTaskResult<JSONObject>(your json object);
            } catch ( Exception anyError) {
                return new AsyncTaskResult<JSONObject>(anyError);
            }
        }

        protected void onPostExecute(AsyncTaskResult<JSONObject> result) {
            if ( result.getError() != null ) {
                // error handling here
            }  else if ( isCancelled()) {
                // cancel handling here
            } else {

                JSONObject realResult = result.getResult();
                // result handling here
            }
        };

    }
Cagatay Kalan
fonte
1
Eu gosto disso. Encapsulamento agradável. Uma vez que esta é uma paráfrase da resposta original as estadias resposta, mas isso definitivamente merece um ponto
Bostone
Esta é uma demonstração bastante agradável de como os genéricos podem ser úteis. Está lançando um cheiro estranho em termos de complexidade, mas não de uma maneira que eu possa realmente articular.
num1
4
Boa idéia, apenas uma pergunta: Por que você chama super()em AsyncTaskResultquando a classe não se estende alguma coisa?
Donturner 15/07/12
7
"nenhum dano" - código redundante é sempre prejudicial à legibilidade e manutenção. Tire isso de lá! :)
donturner 15/09/12
2
Realmente gostei da solução ... vir a pensar sobre isso - o # caras C usados exatamente o mesmo método no C # implementação nativa BackgroundTask correspondente ...
Vova
11

Quando sinto a necessidade de lidar com exceções AsyncTaskcorretamente, eu uso isso como super classe:

public abstract class ExceptionAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {

    private Exception exception=null;
    private Params[] params;

    @Override
    final protected Result doInBackground(Params... params) {
        try {
            this.params = params; 
            return doInBackground();
        }
        catch (Exception e) {
            exception = e;
            return null;
        }
    }

    abstract protected Result doInBackground() throws Exception;

    @Override
    final protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        onPostExecute(exception, result);
    }

    abstract protected void onPostExecute(Exception exception, Result result);

    public Params[] getParams() {
        return params;
    }

}

Como de costume, você substitui doInBackgroundsua subclasse para fazer o trabalho em segundo plano, lançando felizes exceções quando necessário. Você é forçado a implementar onPostExecute(porque é abstrato) e isso lembra-o gentilmente de lidar com todos os tipos de Exceptionque são passados ​​como parâmetro. Na maioria dos casos, as exceções levam a algum tipo de saída da interface do usuário, por isso onPostExecuteé o local perfeito para fazer isso.

sulai
fonte
1
Uau, por que não apenas passar paramsadiante, para que seja mais parecido com o original e mais fácil migrar?
TWiStErRob 12/12
@TWiStErRob nada de errado com essa ideia. É uma questão de preferência pessoal, eu acho, porque eu tendem a não usar parâmetros. Eu prefiro new Task("Param").execute()mais do que isso new Task().execute("Param").
sulai 12/12
5

Se você quiser usar a estrutura do RoboGuice, que oferece outros benefícios, tente o RoboAsyncTask, que possui um retorno de chamada extra na exceção (). Funciona muito bem e eu uso. http://code.google.com/p/roboguice/wiki/RoboAsyncTask

ludwigm
fonte
qual a sua experiência com isso? bastante estável?
nickaknudson
RoboGuiceAinda está vivo? Parece não ter sido atualizado desde 2012?
precisa
Não, o RoboGuice está morto e está obsoleto. Dagger2 é a substituição recomendada, mas é apenas uma biblioteca DI básica.
Avi Cherry
3

Criei minha própria subclasse AsyncTask com uma interface que define retornos de chamada para obter sucesso e fracasso. Portanto, se uma exceção for lançada no seu AsyncTask, a função onFailure será aprovada na exceção, caso contrário, o retorno de chamada onSuccess será aprovado no resultado. Por que o Android não tem algo melhor disponível está além de mim.

public class SafeAsyncTask<inBackgroundType, progressType, resultType>
extends AsyncTask<inBackgroundType, progressType, resultType>  {
    protected Exception cancelledForEx = null;
    protected SafeAsyncTaskInterface callbackInterface;

    public interface SafeAsyncTaskInterface <cbInBackgroundType, cbResultType> {
        public Object backgroundTask(cbInBackgroundType[] params) throws Exception;
        public void onCancel(cbResultType result);
        public void onFailure(Exception ex);
        public void onSuccess(cbResultType result);
    }

    @Override
    protected void onPreExecute() {
        this.callbackInterface = (SafeAsyncTaskInterface) this;
    }

    @Override
    protected resultType doInBackground(inBackgroundType... params) {
        try {
            return (resultType) this.callbackInterface.backgroundTask(params);
        } catch (Exception ex) {
            this.cancelledForEx = ex;
            this.cancel(false);
            return null;
        }
    }

    @Override
    protected void onCancelled(resultType result) {
        if(this.cancelledForEx != null) {
            this.callbackInterface.onFailure(this.cancelledForEx);
        } else {
            this.callbackInterface.onCancel(result);
        }
    }

    @Override
    protected void onPostExecute(resultType result) {
        this.callbackInterface.onSuccess(result);
    }
}
ErlVolton
fonte
3

Uma solução mais abrangente para a solução da Cagatay Kalan é mostrada abaixo:

AsyncTaskResult

public class AsyncTaskResult<T> 
{
    private T result;
    private Exception error;

    public T getResult() 
    {
        return result;
    }

    public Exception getError() 
    {
        return error;
    }

    public AsyncTaskResult(T result) 
    {
        super();
        this.result = result;
    }

    public AsyncTaskResult(Exception error) {
        super();
        this.error = error;
    }
}

ExceptionHandlingAsyncTask

public abstract class ExceptionHandlingAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, AsyncTaskResult<Result>>
{
    private Context context;

    public ExceptionHandlingAsyncTask(Context context)
    {
        this.context = context;
    }

    public Context getContext()
    {
        return context;
    }

    @Override
    protected AsyncTaskResult<Result> doInBackground(Params... params)
    {
        try
        {
            return new AsyncTaskResult<Result>(doInBackground2(params));
        }
        catch (Exception e)
        {
            return new AsyncTaskResult<Result>(e);
        }
    }

    @Override
    protected void onPostExecute(AsyncTaskResult<Result> result)
    {
        if (result.getError() != null)
        {
            onPostException(result.getError());
        }
        else
        {
            onPostExecute2(result.getResult());
        }
        super.onPostExecute(result);
    }

    protected abstract Result doInBackground2(Params... params);

    protected abstract void onPostExecute2(Result result);

    protected void onPostException(Exception exception)
    {
                        new AlertDialog.Builder(context).setTitle(R.string.dialog_title_generic_error).setMessage(exception.getMessage())
                .setIcon(android.R.drawable.ic_dialog_alert).setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener()
                {
                    public void onClick(DialogInterface dialog, int which)
                    {
                        //Nothing to do
                    }
                }).show();
    }
}

Tarefa de exemplo

public class ExampleTask extends ExceptionHandlingAsyncTask<String, Void, Result>
{
    private ProgressDialog  dialog;

    public ExampleTask(Context ctx)
    {
        super(ctx);
        dialog = new ProgressDialog(ctx);
    }

    @Override
    protected void onPreExecute()
    {
        dialog.setMessage(getResources().getString(R.string.dialog_logging_in));
        dialog.show();
    }

    @Override
    protected Result doInBackground2(String... params)
    {
        return new Result();
    }

    @Override
    protected void onPostExecute2(Result result)
    {
        if (dialog.isShowing())
            dialog.dismiss();
        //handle result
    }

    @Override
    protected void onPostException(Exception exception)
    {
        if (dialog.isShowing())
            dialog.dismiss();
        super.onPostException(exception);
    }
}
vahapt
fonte
Eu tenho as getResources () método como em myActivity.getApplicationContext () getResources ().
Stephane
2

Esta aula simples pode ajudá-lo

public abstract class ExceptionAsyncTask<Param, Progress, Result, Except extends Throwable> extends AsyncTask<Param, Progress, Result> {
    private Except thrown;

    @SuppressWarnings("unchecked")
    @Override
    /**
     * Do not override this method, override doInBackgroundWithException instead
     */
    protected Result doInBackground(Param... params) {
        Result res = null;
        try {
            res = doInBackgroundWithException(params);
        } catch (Throwable e) {
            thrown = (Except) e;
        }
        return res;
    }

    protected abstract Result doInBackgroundWithException(Param... params) throws Except;

    @Override
    /**
     * Don not override this method, override void onPostExecute(Result result, Except exception) instead
     */
    protected void onPostExecute(Result result) {
        onPostExecute(result, thrown);
        super.onPostExecute(result);
    }

    protected abstract void onPostExecute(Result result, Except exception);
}
Denis
fonte
2

Outra maneira que não depende do compartilhamento variável de membros é usar cancel.

Isto é dos documentos do Android:

cancelamento booleano final público (boolean mayInterruptIfRunning)

Tenta cancelar a execução desta tarefa. Essa tentativa falhará se a tarefa já tiver sido concluída, cancelada ou não puder ser cancelada por outro motivo. Se for bem-sucedida, e esta tarefa não tiver sido iniciada quando o cancelamento for chamado, essa tarefa nunca deverá ser executada. Se a tarefa já tiver sido iniciada, o parâmetro mayInterruptIfRunning determinará se o encadeamento que está executando esta tarefa deve ser interrompido na tentativa de interromper a tarefa.

A chamada desse método resultará na invocação de onCancelled (Object) no thread da interface do usuário após o retorno de doInBackground (Object []). A chamada desse método garante que onPostExecute (Object) nunca seja chamado. Depois de chamar esse método, você deve verificar o valor retornado por isCancelled () periodicamente de doInBackground (Object []) para concluir a tarefa o mais cedo possível.

Portanto, você pode ligar para cancelar na instrução catch e garantir que onPostExcute nunca seja chamado, mas onCancelled é invocado no thread da interface do usuário. Então você pode mostrar a mensagem de erro.

Todos
fonte
Você não pode mostrar a mensagem de erro corretamente, porque não conhece o problema (Exceção), ainda é necessário capturar e retornar um AsyncTaskResult. Além disso, o cancelamento de um usuário não é um erro, é uma interação esperada: como distinguir entre eles?
TWIStErRob
cancel(boolean)resultando em uma chamada onCancelled()existente desde o início, mas onCancelled(Result)foi adicionada na API 11 .
TWIStErRob
0

Na verdade, o AsyncTask usa FutureTask & Executor, o FutureTask suporta a cadeia de exceções Primeiro, vamos definir uma classe auxiliar

public static class AsyncFutureTask<T> extends FutureTask<T> {

    public AsyncFutureTask(@NonNull Callable<T> callable) {
        super(callable);
    }

    public AsyncFutureTask<T> execute(@NonNull Executor executor) {
        executor.execute(this);
        return this;
    }

    public AsyncFutureTask<T> execute() {
        return execute(AsyncTask.THREAD_POOL_EXECUTOR);
    }

    @Override
    protected void done() {
        super.done();
        //work done, complete or abort or any exception happen
    }
}

Segundo, vamos usar

    try {
        Log.d(TAG, new AsyncFutureTask<String>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //throw Exception in worker thread
                throw new Exception("TEST");
            }
        }).execute().get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        //catch the exception throw by worker thread in main thread
        e.printStackTrace();
    }
Yessy
fonte
-2

Pessoalmente, usarei essa abordagem. Você pode simplesmente capturar as exceções e imprimir o rastreamento da pilha, se precisar das informações.

faça sua tarefa em segundo plano retornar um valor booleano.

É tipo isso:

    @Override
                protected Boolean doInBackground(String... params) {
                    return readXmlFromWeb(params[0]);
         }

        @Override
                protected void onPostExecute(Boolean result) {

              if(result){
              // no error
               }
              else{
                // error handling
               }
}
atormentar
fonte
-2

Outra possibilidade seria usar Objectcomo tipo de retorno e onPostExecute()verificar o tipo de objeto. É curto.

class MyAsyncTask extends AsyncTask<MyInObject, Void, Object> {

    @Override
    protected AsyncTaskResult<JSONObject> doInBackground(MyInObject... myInObjects) {
        try {
            MyOutObject result;
            // ... do something that produces the result
            return result;
        } catch (Exception e) {
            return e;
        }
    }

    protected void onPostExecute(AsyncTaskResult<JSONObject> outcome) {
        if (outcome instanceof MyOutObject) {
            MyOutObject result = (MyOutObject) outcome;
            // use the result
        } else if (outcome instanceof Exception) {
            Exception e = (Exception) outcome;
            // show error message
        } else throw new IllegalStateException();
    }
}
Matthias Ronge
fonte
1
completamente irrelevante
Dinu
-2

Se você souber a exceção correta, poderá chamar o

Exception e = null;

publishProgress(int ...);

por exemplo:

@Override
protected Object doInBackground(final String... params) {

    // TODO Auto-generated method stub
    try {
        return mClient.call(params[0], params[1]);
    } catch(final XMLRPCException e) {

        // TODO Auto-generated catch block
        this.e = e;
        publishProgress(0);
        return null;
    }
}

e vá para "onProgressUpdate" e faça o seguinte

@Override
protected void onProgressUpdate(final Integer... values) {

    // TODO Auto-generated method stub
    super.onProgressUpdate(values);
    mDialog.dismiss();
    OptionPane.showMessage(mActivity, "Connection error", e.getMessage());
}

Isso será útil apenas em alguns casos. Além disso, você pode manter uma Global Exceptionvariável e acessar a exceção.

Ajmal Muhammad P
fonte
1
Por favor, não faça isso. É um estilo muito, muito ruim!
JimmyB