AsyncTask et traitement des erreurs sur Android
-
20-09-2019 - |
Question
Je convertir mon code d'utiliser Handler
à AsyncTask
. Ce dernier est grande à ce qu'il fait - les mises à jour asynchrones et la manipulation des résultats dans le thread principal de l'interface utilisateur. Ce qui est clair pour moi est de savoir comment gérer les exceptions si quelque chose se détraque dans AsyncTask#doInBackground
.
La façon dont je le fais est d'avoir un gestionnaire d'erreur et d'envoyer des messages. Il fonctionne très bien, mais est-ce l'approche « droit » ou est-il une meilleure alternative?
Je comprends également que si je définis le gestionnaire d'erreur comme un champ d'activité, il doit exécuter dans le thread d'interface utilisateur. Cependant, parfois (très imprévisiblement) Je vais faire une exception en disant que le code déclenché à partir Handler#handleMessage
est exécuté sur le mauvais fil. Dois-je initialiser gestionnaire d'erreurs dans Activity#onCreate
à la place? Mise en place runOnUiThread
en Handler#handleMessage
semble redondant, mais il exécute de manière très fiable.
La solution
Il fonctionne très bien, mais est-ce le « droit » approche et est-il mieux alternative?
J'agripper la Throwable
ou Exception
dans l'instance de AsyncTask
lui-même et faire quelque chose avec elle dans onPostExecute()
, donc ma gestion des erreurs a la possibilité d'afficher une boîte de dialogue à l'écran.
Autres conseils
Créer un objet AsyncResult (que vous pouvez également utiliser dans d'autres projets)
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;
}
}
Retour cet objet de vos méthodes AsyncTask doInBackground et vérifier dans le postExecute. (Vous pouvez utiliser cette classe comme une classe de base pour vos autres tâches async)
Voici une maquette d'une tâche qui obtient une réponse JSON à partir du serveur 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
}
};
}
Quand je sens la nécessité de gérer les exceptions dans AsyncTask
correctement, je l'utiliser comme 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;
}
}
Comme d'habitude, vous substituez doInBackground
dans votre sous-classe pour faire un travail de fond, jetant heureusement des exceptions en cas de besoin. Vous êtes alors obligé de mettre en œuvre onPostExecute
(parce qu'il est abstrait) et cela vous rappelle doucement pour traiter tous les types de Exception
, qui sont passés en paramètre. Dans la plupart des cas, des exceptions conduisent à un certain type de sortie ui, donc onPostExecute
est un endroit idéal pour le faire.
Si vous souhaitez utiliser le cadre de RoboGuice qui vous apporte d'autres avantages que vous pouvez essayer le RoboAsyncTask qui a un onException de rappel supplémentaire (). Fonctionne vraiment bien et je l'utilise. http://code.google.com/p/roboguice/wiki/RoboAsyncTask
J'ai fait ma propre sous-classe AsyncTask avec une interface qui définit les callbacks pour le succès et l'échec. Donc, si une exception est levée dans votre AsyncTask, la fonction onFailure est transmis à l'exception, sinon le rappel onSuccess est transmis votre résultat. Pourquoi android ne pas quelque chose de mieux disponible est au-delà de moi.
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);
}
}
Une solution plus complète Cagatay solution de Kalan est indiqué ci-dessous:
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();
}
}
Exemple Task
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);
}
}
Cette classe simple peut vous aider
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);
}
Une autre façon qui ne dépend pas de partage de membre variable est d'utiliser annuler.
Ceci est de docs Android:
boolean public final annuler (mayInterruptIfRunning booléenne)
Les tentatives d'annuler l'exécution de cette tâche. Cette tentative échouera si la tâche a déjà terminé, déjà été annulé, ou ne peut pas être annulée pour une autre raison. Si avec succès, et cette tâche n'a pas commencé quand annulation est appelée, cette tâche ne doit jamais fonctionner. Si la tâche a déjà commencé, le mayInterruptIfRunning paramètre détermine si le fil l'exécution de cette tâche doit être interrompue pour tenter d'arrêter la tâche.
L'appel de cette méthode se traduira par onCancelled (Object) invoquée sur le thread d'interface utilisateur après doInBackground (Object []) les retours. L'appel de cette méthode garantit que onPostExecute (Object) est jamais invoqué. Après l'invocation de cette méthode, vous devez vérifier la valeur retournée par isCancelled () périodiquement à partir de doInBackground (Object []) pour terminer le tâche le plus tôt possible.
Vous pouvez appeler annuler dans la déclaration de capture et assurez-vous que onPostExcute est jamais appelé, mais onCancelled est appelée sur thread d'interface utilisateur. Ainsi, vous pouvez afficher le message d'erreur.
En fait, AsyncTask utiliser FutureTask & Exécuteur, exception chaîne de soutien FutureTask Tout d'abord nous allons définir une classe d'aide
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 second lieu, nous allons utiliser
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();
}
Personnellement, je vais utiliser cette approche. Vous pouvez juste attraper les exceptions et imprimer la trace de la pile si vous avez besoin d'informations.
rendre votre tâche en arrière-plan renvoie une valeur booléenne.
il est comme ceci:
@Override
protected Boolean doInBackground(String... params) {
return readXmlFromWeb(params[0]);
}
@Override
protected void onPostExecute(Boolean result) {
if(result){
// no error
}
else{
// error handling
}
}
Une autre possibilité serait d'utiliser Object
comme type de retour, et dans le contrôle de onPostExecute()
pour le type d'objet. Il est court.
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 vous connaissez l'exception correcte, vous pouvez appeler le
Exception e = null;
publishProgress(int ...);
par exemple:
@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;
}
}
et aller à "onProgressUpdate" et faire le 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());
}
Cela sera utile dans certains cas seulement. vous pouvez également conserver une variable Global
de Exception
et accéder à l'exception.