AsyncTask та керування помилками на Android


147

Я перетворюю свій код із використання Handlerв AsyncTask. Останнє чудово підходить до того, що він робить - асинхронні оновлення та обробка результатів у головному потоці інтерфейсу. Мені незрозуміло - як поводитись з винятками, якщо щось перебуває без перешкодAsyncTask#doInBackground .

Як я це роблю, це мати Обробник помилок і надсилати на нього повідомлення. Це прекрасно працює, але це "правильний" підхід чи є краща альтернатива?

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


Чому ви хотіли перетворити свій код? Чи були вагомі причини?
HGPB

4
@Haraldo це краща практика кодування, принаймні, так я відчуваю
Bostone

Відповіді:


178

Це добре працює, але чи це "правильний" підхід і чи є краща альтернатива?

Я тримаюсь за сам Throwableабо Exceptionв AsyncTaskекземплярі, а потім щось роблю з ним onPostExecute(), тож у мене в обробці помилок є можливість відображення діалогового вікна на екрані.


8
Блискуче! Більше не потрібно
мавпати з Хендлерами

5
Це я повинен триматися на викиданні чи винятку? "Додайте змінну екземпляра до власного підкласу AsyncTask, який буде містити результат вашої фонової обробки." Коли ви отримуєте виняток, зберігайте виняток (або якийсь інший рядок / код помилки) у цій змінній. Коли onPostExecute викликається, перевірте, чи вказана ця змінна інстанція на якусь помилку. Якщо так, покажіть повідомлення про помилку. "(Від користувача" Вулиці Бостона " groups.google.com/group/android-developers/browse_thread/thread/… )
OneWorld

1
@OneWorld: Так, це має бути добре.
CommonsWare

2
Привіт CW, ви можете пояснити, будь ласка, більш докладно про це - будь ласка, з коротким прикладом коду? Дуже дякую!!
Брюзер

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

Поверніть цей об’єкт з методів doInBackground AsyncTask і перевірте його у 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 # використовували саме той самий метод у C # відповідній натільній програмі BackgroundTask ...
Vova

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це ідеальне місце для цього.


1
Уао, чому б не просто пройти paramsвперед, так що це більше схоже на оригінальний і легше мігрувати?
TWiStErRob

@TWiStErRob нічого поганого з цією ідеєю. Я думаю, це питання особистої переваги, оскільки я, як правило, не використовую парами. Я віддаю перевагу new Task("Param").execute()понад new Task().execute("Param").
sulai

5

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


який у вас досвід з цим? досить стійкий?
nickaknudson

Є RoboGuiceще живий? Здається, не оновлювався з 2012 року?
Димитрій К

Ні, RoboGuice мертвий і застарілий. Dagger2 - рекомендована заміна, але це лише бібліотека DI з голими кістками.
Avi Cherry

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

3

Більш комплексне рішення для Кагатая Калана показано нижче:

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

ВинятокHandlingAsyncTask

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

Я отримав метод 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

Інший спосіб, який не залежить від спільного використання змінних членів, - це використовувати скасувати.

Це з андроїд-документів:

загальнодоступна фінальна булева скасування (boolean may InterterIfRunning)

Спроби скасувати виконання цього завдання. Ця спроба завершиться невдачею, якщо завдання вже виконано, вже скасовано або не вдалося скасувати з іншої причини. Якщо це успішно, і це завдання не запустилося, коли викликається скасування, це завдання ніколи не слід виконувати. Якщо завдання вже запущено, параметр mayInterruptIfRunning визначає, чи слід переривати потік, що виконує це завдання, у спробі зупинити завдання.

Виклик цього методу призведе до того, що onCancegled (Object) буде викликаний на потоці інтерфейсу після повернення doInBackground (Object []). Виклик цього методу гарантує, що onPostExecute (Object) ніколи не викликається. Після виклику цього методу слід періодично перевіряти значення, повернене isCancegled (), з doInBackground (Object []), щоб якнайшвидше закінчити завдання.

Таким чином, ви можете зателефонувати скасувати в заяві catch і бути впевненим, що onPostExcute ніколи не викликається, а натомість onCanceiled викликається в потоці інтерфейсу користувача. Таким чином, ви можете показати повідомлення про помилку.


Ви не можете правильно відобразити повідомлення про помилку, оскільки ви не знаєте проблеми (Виняток), вам все одно потрібно знайти та повернути 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();
    }

-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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.