AsyncTask e tratamento de erros no Android
-
20-09-2019 - |
Pergunta
Estou convertendo meu código usando Handler
para AsyncTask
.O último é ótimo no que faz - atualizações assíncronas e manipulação de resultados no thread principal da UI.O que não está claro para mim é como lidar com exceções se algo der errado AsyncTask#doInBackground
.
A maneira como faço isso é 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, às vezes (de forma muito imprevisível) recebo uma exceção dizendo que o código foi acionado por Handler#handleMessage
está sendo executado no thread errado.Devo inicializar o manipulador de erros em Activity#onCreate
em vez de?Colocação runOnUiThread
em Handler#handleMessage
parece redundante, mas é executado de maneira muito confiável.
Solução
Funciona bem, mas é a abordagem "certa" e há uma alternativa melhor?
Eu me apego ao Throwable
ou Exception
no AsyncTask
instância em si e depois faça algo com isso em onPostExecute()
, então meu manuseio de erros tem a opção de exibir uma caixa de diálogo na tela.
Outras dicas
Crie um objeto assíncrono (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 este objeto dos seus 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
}
};
}
Quando sinto necessidade de lidar com exceções em AsyncTask
corretamente, eu uso isso como superclasse:
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 doInBackground
em sua subclasse para fazer trabalho em segundo plano, lançando exceções quando necessário.Você é então forçado a implementar onPostExecute
(porque é abstrato) e isso gentilmente lembra você de lidar com todos os tipos de Exception
, que são passados como parâmetro.Na maioria dos casos, as exceções levam a algum tipo de saída de interface do usuário, então onPostExecute
é um lugar perfeito para fazer isso.
Se você deseja usar a estrutura Roboguice, que traz outros benefícios, você pode experimentar o RoboSyncTask, que possui um retorno de chamada extra Onexception (). Funciona muito bem e eu o uso.http://code.google.com/p/roboguice/wiki/roboasynctask
Fiz minha própria subclasse de assíncrogem com uma interface que define retornos de chamada para sucesso e fracasso. Portanto, se uma exceção for lançada em sua assíncada, a função OnFailure será aprovada pela exceção; caso contrário, o retorno de chamada do Onsuccess será aprovado no seu 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);
}
}
Uma solução mais abrangente para CAGATAY KALANA solução é 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);
}
}
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);
}
Outra maneira que não depende do compartilhamento de membros da variável é usar o Cancel.
Isto é da Android Docs:
cancelamento booleano final público (booleano MayInterruptifrunning)
Tentativas de cancelar a execução desta tarefa. Essa tentativa falhará se a tarefa já tiver concluída, já foi cancelada ou não puder ser cancelada por algum outro motivo. Se for bem -sucedido, e essa tarefa não foi iniciada quando o cancelamento é chamado, essa tarefa nunca deve ser executada. Se a tarefa já foi iniciada, o parâmetro MayInterruptifrunning determina se o thread que executa essa tarefa deve ser interrompido na tentativa de interromper a tarefa.
Chamar esse método resultará na invocada (objeto) oncelado (objeto) no thread da interface do usuário após o retorno doinbackground (objeto []). Chamar esse método garante que o OnPostexecute (objeto) nunca seja invocado. Depois de invocar esse método, você deve verificar o valor retornado por iscancelled () periodicamente do Doinbackground (objeto []) para concluir a tarefa o mais cedo possível.
Assim, você pode ligar para cancelar a declaração de captura e verifique se o OnPostexCute nunca é chamado, mas, em vez disso, está chamado no encadeamento da interface do usuário. Então você pode mostrar a mensagem de erro.
Na verdade, assínctask Use FutureTask & Executor, FutureTask Support Chain de exceção 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();
}
Pessoalmente, usarei essa abordagem. Você pode simplesmente pegar as exceções e imprimir o rastreamento da pilha, se precisar das informações.
Faça sua tarefa em segundo plano retorne 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
}
}
Outra possibilidade seria usar Object
como tipo de retorno, e em onPostExecute()
Verifique 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();
}
}
Se você souber a exceção correta, pode ligar para 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. Também você pode manter um Global
Exception
variável e acesse a exceção.