AsyncTask и обработка ошибок на Android

147

Я конвертирую свой код из использования Handlerв AsyncTask. Последний хорош в том, что делает - асинхронные обновления и обработка результатов в основном потоке пользовательского интерфейса. Что мне неясно, так это то, как обрабатывать исключения, если что-то пошло не так AsyncTask#doInBackground.

Для этого я должен иметь обработчик ошибок и отправлять ему сообщения. Это работает нормально, но это "правильный" подход или есть лучшая альтернатива?

Также я понимаю, что если я определяю обработчик ошибок как поле Activity, он должен выполняться в потоке пользовательского интерфейса. Однако иногда (очень непредсказуемо) я получаю исключение, говорящее о том, что код, запущенный из Handler#handleMessage, выполняется в неправильном потоке. Должен ли я Activity#onCreateвместо этого инициализировать обработчик ошибок ? Размещение runOnUiThreadв Handler#handleMessageкажется избыточным, но это выполняется очень надежно.

Bostone
источник
Почему вы хотите конвертировать свой код? Была ли веская причина?
HGPB
4
@Харальдо, это лучшая практика кодирования, по крайней мере, я так себя чувствую
Бостон,

Ответы:

178

Это работает нормально, но это "правильный" подход и есть ли лучшая альтернатива?

Я держусь за Throwableили Exceptionв самом AsyncTaskэкземпляре, а затем что-то с ним делаю onPostExecute(), поэтому моя обработка ошибок имеет возможность отображать диалоговое окно на экране.

CommonsWare
источник
8
Brilliant! Больше не нужно
махать с Хэндлерами
5
Это способ, которым я должен придерживаться Throwable или Exception? «Добавьте переменную экземпляра в ваш собственный подкласс AsyncTask, который будет содержать результат вашей фоновой обработки». Когда вы получите исключение, сохраните исключение (или некоторую другую строку ошибки / код) в этой переменной. Когда вызывается onPostExecute, посмотрите, не установлена ​​ли для этой переменной-экземпляра какая-либо ошибка. Если это так, покажите сообщение об ошибке. "(От пользователя" Улицы Бостона " groups.google.com/group/android-developers/browse_thread/thread/… )
OneWorld
1
@OneWorld: Да, это должно быть хорошо.
CommonsWare
2
Привет CW, не могли бы вы объяснить, как это сделать более подробно, может быть, с кратким примером кода? Большое спасибо!!
Bruiser
18
@Bruiser: github.com/commonsguy/cw-lunchlist/tree/master/15-Internet/… имеет AsyncTaskследующую схему, которую я описываю.
CommonsWare
140

Создайте объект AsyncResult (который вы также можете использовать в других проектах)

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

Верните этот объект из ваших методов AsyncTask doInBackground и проверьте его в postExecute. (Вы можете использовать этот класс в качестве базового класса для других ваших асинхронных задач)

Ниже приведен макет задачи, которая получает ответ JSON от веб-сервера.

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

    }
Кагатай Калан
источник
1
Мне это нравится. Хорошая инкапсуляция. Поскольку это парафраз оригинального ответа, ответ остается, но это определенно заслуживает
внимания
Это довольно хорошая демонстрация того, насколько полезными могут быть Generics. Это создает странный запах с точки зрения сложности, но не так, как я могу сформулировать.
Num1
4
Хорошая идея, только один вопрос: почему вы звоните super()в , AsyncTaskResultкогда класс ничего не распространяется?
donturner
7
«Без вреда» - избыточный код всегда вреден для удобства чтения и обслуживания. Убери это оттуда! :)
donturner
2
Очень понравилось решение ... если подумать - ребята из C # использовали точно такой же метод в соответствующей реализации BackgroundTask на C # ...
Вова
11

Когда я чувствую необходимость правильно обрабатывать исключения AsyncTask, я использую это как суперкласс:

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

}

Как обычно, вы переопределяете doInBackgroundв своем подклассе для выполнения фоновой работы, к счастью выбрасывая исключения, где это необходимо. Затем вы вынуждены реализовать onPostExecute(потому что это абстрактный), и это мягко напоминает вам обрабатывать все типы Exception, которые передаются в качестве параметра. В большинстве случаев исключения приводят к некоторому типу пользовательского интерфейса, поэтому onPostExecuteэто идеальное место для этого.

sulai
источник
1
Вау, почему бы просто не передать paramsфорвард, чтобы он больше походил на оригинал и его было легче перенести?
TWiStErRob
@TWiStErRob ничего плохого в этой идее. Я полагаю, это вопрос личных предпочтений, так как я не использую params. Я предпочитаю new Task("Param").execute()более new Task().execute("Param").
Сулай
5

Если вы хотите использовать платформу RoboGuice, которая дает вам другие преимущества, вы можете попробовать RoboAsyncTask, который имеет дополнительный Callback onException (). Работает очень хорошо, и я им пользуюсь. http://code.google.com/p/roboguice/wiki/RoboAsyncTask

ludwigm
источник
каков твой опыт с этим? довольно стабильно?
nickaknudson
Все RoboGuiceеще жив? Кажется, не обновляется с 2012 года?
Димитрий К
Нет, RoboGuice мертв и устарел. Dagger2 является рекомендуемой заменой, но это только библиотека DI.
Ави Черри
3

Я создал собственный подкласс AsyncTask с интерфейсом, который определяет обратные вызовы для успеха и неудачи. Поэтому, если в AsyncTask выдается исключение, функция onFailure получает исключение, в противном случае обратный вызов onSuccess получает ваш результат. Почему у андроида нет чего-то лучшего, я не знаю.

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
источник
3

Более полное решение для решения Cagatay Kalan показано ниже:

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

Пример задачи

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
источник
Я получил метод getResources () как в myActivity.getApplicationContext (). GetResources ()
Стефан
2

Этот простой класс может помочь вам

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);
}
Денис
источник
2

Другой способ, который не зависит от общего доступа к переменным, заключается в использовании отмены.

Это из Android документов:

публичная финальная логическая отмена (логическое mayInterruptIfRunning)

Попытки отменить выполнение этого задания. Эта попытка потерпит неудачу, если задача уже завершена, уже отменена или не может быть отменена по какой-либо другой причине. Если успешно, и эта задача не была запущена при вызове отмены, эта задача никогда не должна выполняться. Если задача уже запущена, то параметр mayInterruptIfRunning определяет, следует ли прерывать поток, выполняющий эту задачу, в попытке остановить задачу.

Вызов этого метода приведет к тому, что onCancelled (Object) будет вызываться в потоке пользовательского интерфейса после возврата doInBackground (Object []). Вызов этого метода гарантирует, что onPostExecute (Object) никогда не вызывается. После вызова этого метода вы должны периодически проверять значение, возвращаемое isCancelled () из doInBackground (Object []), чтобы завершить задачу как можно раньше.

Таким образом, вы можете вызвать cancel в операторе catch и быть уверенным, что onPostExcute никогда не вызывается, а вместо этого onCancelled вызывается в потоке пользовательского интерфейса. Таким образом, вы можете показать сообщение об ошибке.

Али
источник
Вы не можете правильно отобразить сообщение об ошибке, потому что вы не знаете проблему (Исключение), вам все еще нужно перехватить и вернуть AsyncTaskResult. Кроме того, отмена пользователя - это не ошибка, это ожидаемое взаимодействие: как вы различаете их?
TWiStErRob
cancel(boolean)в результате вызов onCancelled()существовал с самого начала, но onCancelled(Result)был добавлен в API 11 .
TWiStErRob
0

На самом деле, AsyncTask использует FutureTask & Executor, FutureTask поддерживает цепочку исключений. Сначала давайте определим вспомогательный класс

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

Во-вторых, давайте использовать

    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
источник
-2

Лично я буду использовать этот подход. Вы можете просто перехватить исключения и распечатать трассировку стека, если вам нужна информация.

сделайте вашу задачу в фоновом режиме, верните логическое значение.

это вот так:

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

        @Override
                protected void onPostExecute(Boolean result) {

              if(result){
              // no error
               }
              else{
                // error handling
               }
}
Гарри
источник
-2

Другой возможностью будет использование в Objectкачестве типа возвращаемого значения и onPostExecute()проверки типа объекта. Это коротко.

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();
    }
}
Матиас Ронге
источник
1
совершенно не имеет значения
Dinu
-2

Если вы знаете правильное исключение, вы можете позвонить

Exception e = null;

publishProgress(int ...);

например:

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

и перейдите к «onProgressUpdate» и выполните следующие действия

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

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

Это будет полезно только в некоторых случаях. Также вы можете сохранить Global Exceptionпеременную и получить доступ к исключению.

Аджмал Мухаммед П
источник
1
Пожалуйста, не делай этого. Это действительно очень плохой стиль!
JimmyB