AsyncTask y tratamiento de errores en Android
-
20-09-2019 - |
Pregunta
Me estoy convirtiendo mi código de usar Handler
a AsyncTask
. Este último es grande en lo que hace - actualizaciones asíncronas y la manipulación de los resultados en el hilo principal interfaz de usuario. Lo que es claro para mí es cómo manejar excepciones si algo se descontrola en AsyncTask#doInBackground
.
La manera de hacerlo es tener un gestor de errores y enviar mensajes a ella. Funciona bien, pero es que el enfoque de "derecha" o hay una mejor alternativa?
También entiendo que si yo defino el gestor de errores como un campo de actividad, se debe ejecutar en el hilo de interfaz de usuario. Sin embargo, a veces (muy impredecible) voy a tener una excepción diciendo que el código disparado desde Handler#handleMessage
se ejecuta en el hilo equivocado. ¿Debo inicializar el controlador de errores en Activity#onCreate
en su lugar? La colocación de runOnUiThread
en Handler#handleMessage
parece redundante, pero se ejecuta de manera muy fiable.
Solución
Funciona bien, pero es que el "derecho" aproximación y hay una mejor alternativa?
Me aferrarse a la Throwable
o Exception
en la propia instancia AsyncTask
y luego hacer algo con él en onPostExecute()
, por lo que mi tratamiento de errores tiene la opción de mostrar un cuadro de diálogo en la pantalla.
Otros consejos
Crea un objeto AsyncResult (que también se puede utilizar en otros proyectos)
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;
}
}
Vuelta este objeto a partir de sus métodos AsyncTask doInBackground y comprobar que en el postExecute. (Puede utilizar esta clase como clase base para sus otras tareas asíncronas)
A continuación se muestra una maqueta de una tarea que recibe una respuesta JSON desde el servidor 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
}
};
}
Cuando siento la necesidad de manejar excepciones en AsyncTask
adecuadamente, yo uso esto como superclase:
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 es normal, se reemplaza doInBackground
en la subclase para hacer el trabajo de fondo, que lanza feliz excepciones cuando sea necesario. Usted se ven obligados a poner en práctica onPostExecute
(porque es abstracta) y esto suavemente le recuerda que debe manejar todo tipo de Exception
, que se pasan como parámetro. En la mayoría de los casos, excepciones conducen a algún tipo de salida de la interfaz de usuario, por lo onPostExecute
es un lugar perfecto para hacerlo.
Si desea utilizar el marco RoboGuice que trae otros beneficios que usted puede probar el RoboAsyncTask que tiene una devolución de llamada onException extra (). Funciona muy bien y lo uso. http://code.google.com/p/roboguice/wiki/RoboAsyncTask
Hice mi propia subclase AsyncTask con una interfaz que define las devoluciones de llamada para el éxito y el fracaso. Así que si se produce una excepción en su AsyncTask, la función onFailure se pasa la excepción, de lo contrario la devolución de llamada se pasa onSuccess su resultado. ¿Por qué Android no tiene algo mejor disponible es más allá.
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);
}
}
una solución más completa para Cagatay Kalan 's se muestra a continuación:
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();
}
}
Ejemplo de tarea
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 clase simple puede ayudar
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);
}
Otra forma de que no depende de intercambio de miembro de la variable es el uso de cancelar.
Esto es de docs Android:
public final boolean cancelar (mayInterruptIfRunning booleano)
Los intentos de cancelar la ejecución de esta tarea. Esta intento fallará si la tarea ya ha finalizado, ya ha sido cancelado, o no podría ser cancelada por alguna otra razón. Si éxito, y esta tarea no ha comenzado cuando la cancelación se llama, este tarea no debe funcionar nunca. Si la tarea ya ha comenzado, entonces el mayInterruptIfRunning parámetro determina si el hilo la ejecución de esta tarea debe ser interrumpido en un intento de detener la tarea.
llama a este método dará lugar a onCancelled (Object) se invoca en el subproceso de interfaz de usuario después de doInBackground (Object []) devuelve. Al llamar a este método garantiza que onPostExecute (Objeto) no se invoca. Después la invocación de este método, se debe comprobar el valor devuelto por isCancelled () periódicamente de doInBackground (Object []) para terminar la tarea tan pronto como sea posible.
Así que usted puede llamar a cancelar en la declaración de capturas y estar seguros de que onPostExcute nunca es llamado, pero en lugar onCancelled se invoca en hilo de interfaz de usuario. Para que pueda mostrar el mensaje de error.
En realidad, AsyncTask utilizar FutureTask y Ejecutor, FutureTask apoyo excepción de cadena En primer lugar vamos a definir una clase de ayuda
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
}
}
En segundo lugar, vamos a 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();
}
En lo personal, voy a utilizar este enfoque. Usted puede simplemente coger las excepciones e imprimir el seguimiento de la pila si necesita la información.
hacer su tarea en segundo plano devuelve un valor booleano.
es como sigue:
@Override
protected Boolean doInBackground(String... params) {
return readXmlFromWeb(params[0]);
}
@Override
protected void onPostExecute(Boolean result) {
if(result){
// no error
}
else{
// error handling
}
}
Otra posibilidad sería utilizar Object
como tipo de retorno, y el registro de entrada onPostExecute()
para el tipo de objeto. Es corto.
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();
}
}
Si conoce la excepción correcta, entonces puede llamar a la
Exception e = null;
publishProgress(int ...);
por ejemplo:
@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;
}
}
y vaya a "onProgressUpdate" y hacer lo folowing
@Override
protected void onProgressUpdate(final Integer... values) {
// TODO Auto-generated method stub
super.onProgressUpdate(values);
mDialog.dismiss();
OptionPane.showMessage(mActivity, "Connection error", e.getMessage());
}
Esto será útil sólo en algunos casos. También se puede mantener una variable Global
Exception
y acceder a la excepción.