Идеальный способ отменить выполнение AsyncTask

Я выполняю операции удаленного аудиофайла и воспроизведения аудиофайлов в фоновом потоке с помощью AsyncTask. Индикатор выполнения Cancellable отображается на время выполнения операции выборки.

Я хочу отменить/прервать выполнение AsyncTask, когда пользователь отменяет (отменяет) операцию. Каков идеальный способ справиться с таким случаем?

Ответ 1

Только что обнаружил, что AlertDialogs boolean cancel(...);, который я использую везде, на самом деле ничего не делает. Отлично.
Итак...

public class MyTask extends AsyncTask<Void, Void, Void> {

    private volatile boolean running = true;
    private final ProgressDialog progressDialog;

    public MyTask(Context ctx) {
        progressDialog = gimmeOne(ctx);

        progressDialog.setCancelable(true);
        progressDialog.setOnCancelListener(new OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                // actually could set running = false; right here, but I'll
                // stick to contract.
                cancel(true);
            }
        });

    }

    @Override
    protected void onPreExecute() {
        progressDialog.show();
    }

    @Override
    protected void onCancelled() {
        running = false;
    }

    @Override
    protected Void doInBackground(Void... params) {

        while (running) {
            // does the hard work
        }
        return null;
    }

    // ...

}

Ответ 2

Если вы выполняете вычисления:

  • Вы должны периодически проверять isCancelled().

Если вы выполняете HTTP-запрос:

  • Сохраните экземпляр вашего HttpGet или HttpPost где-нибудь (например, общедоступное поле).
  • После вызова cancel вызовите request.abort(). Это приведет к тому, что IOException будет выбрано внутри doInBackground.

В моем случае у меня был класс соединителя, который я использовал в различных AsyncTasks. Чтобы это было просто, я добавил новый метод abortAllRequests к этому классу и вызвал этот метод непосредственно после вызова cancel.

Ответ 3

Дело в том, что вызов AsyncTask.cancel() вызывает только функцию onCancel в вашей задаче. Здесь вы хотите обработать запрос отмены.

Вот небольшая задача, которую я использую для запуска метода обновления

private class UpdateTask extends AsyncTask<Void, Void, Void> {

        private boolean running = true;

        @Override
        protected void onCancelled() {
            running = false;
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
            onUpdate();
        }

        @Override
        protected Void doInBackground(Void... params) {
             while(running) {
                 publishProgress();
             }
             return null;
        }
     }

Ответ 4

Просто: не используйте AsyncTask. AsyncTask предназначен для коротких операций, которые быстро заканчиваются (десятки секунд) и, следовательно, их не нужно отменять. "Воспроизведение аудиофайлов" не подходит. Вам даже не нужен фоновый поток для обычного воспроизведения аудиофайлов.

Ответ 5

Единственный способ сделать это - проверить значение метода isCancelled() и остановить воспроизведение, когда оно вернет true.

Ответ 6

Вот как я пишу свою AsyncTask
ключевым моментом является добавление Thread.sleep(1);

@Override   protected Integer doInBackground(String... params) {

        Log.d(TAG, PRE + "url:" + params[0]);
        Log.d(TAG, PRE + "file name:" + params[1]);
        downloadPath = params[1];

        int returnCode = SUCCESS;
        FileOutputStream fos = null;
        try {
            URL url = new URL(params[0]);
            File file = new File(params[1]);
            fos = new FileOutputStream(file);

            long startTime = System.currentTimeMillis();
            URLConnection ucon = url.openConnection();
            InputStream is = ucon.getInputStream();
            BufferedInputStream bis = new BufferedInputStream(is);

            byte[] data = new byte[10240]; 
            int nFinishSize = 0;
            while( bis.read(data, 0, 10240) != -1){
                fos.write(data, 0, 10240);
                nFinishSize += 10240;
                **Thread.sleep( 1 ); // this make cancel method work**
                this.publishProgress(nFinishSize);
            }              
            data = null;    
            Log.d(TAG, "download ready in"
                  + ((System.currentTimeMillis() - startTime) / 1000)
                  + " sec");

        } catch (IOException e) {
                Log.d(TAG, PRE + "Error: " + e);
                returnCode = FAIL;
        } catch (Exception e){
                 e.printStackTrace();           
        } finally{
            try {
                if(fos != null)
                    fos.close();
            } catch (IOException e) {
                Log.d(TAG, PRE + "Error: " + e);
                e.printStackTrace();
            }
        }

        return returnCode;
    }

Ответ 7

Наша глобальная переменная класса AsyncTask

LongOperation LongOperationOdeme = new LongOperation();

И действие KEYCODE_BACK, которое прерывает AsyncTask

   @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            LongOperationOdeme.cancel(true);
        }
        return super.onKeyDown(keyCode, event);
    }

Это работает для меня.

Ответ 8

Мне не нравится принудительно прерывать мои задачи async с помощью cancel(true) без необходимости, потому что у них могут быть освобождены ресурсы, такие как закрытие сокетов или потоков файлов, запись данных в локальную базу данных и т.д. С другой стороны, я столкнулись с ситуациями, в которых асинхронная задача отказывается заканчивать часть времени, например, иногда, когда основное действие закрывается, и я запрашиваю задачу асинхронного завершения изнутри метода активности onPause(). Так что это не просто вызов running = false. Мне нужно пойти на смешанное решение: оба звонят running = false, а затем задают асинхронную задачу на несколько миллисекунд, а затем вызывают либо cancel(false), либо cancel(true).

if (backgroundTask != null) {
    backgroundTask.requestTermination();
    try {
        Thread.sleep((int)(0.5 * 1000));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    if (backgroundTask.getStatus() != AsyncTask.Status.FINISHED) {
        backgroundTask.cancel(false);
    }
    backgroundTask = null;
}

В качестве побочного результата после завершения doInBackground() иногда вызывается метод onCancelled(), а иногда onPostExecute(). Но, по крайней мере, гарантируется прекращение асинхронной задачи.

Ответ 9

Что касается ответа Яньченко 29 апреля 10 года: использование подхода "пока (работает)" является аккуратным, когда ваш код под "doInBackground" должен выполняться несколько раз во время каждого выполнения AsyncTask. Если ваш код под "doInBackground" должен быть выполнен только один раз за выполнение AsyncTask, перенос всего вашего кода под "doInBackground" в цикле "while (running)" не остановит фоновый код (фоновый поток) от запуска, когда Сам AsyncTask отменяется, потому что условие "while (running)" будет оцениваться только после того, как весь код внутри цикла while будет выполнен хотя бы один раз. Таким образом, вы должны либо (a.) Разбить свой код под "doInBackground" на несколько блоков "while (running)", либо (b.) Выполнить многочисленные проверки "isCancelled" по всему вашему "doInBackground", как описано в разделе "Отмена задачи" "на странице https://developer.android.com/reference/android/os/AsyncTask.html.

Для варианта (а) можно, таким образом, изменить ответ Яньченко следующим образом:

public class MyTask extends AsyncTask<Void, Void, Void> {

private volatile boolean running = true;

//...

@Override
protected void onCancelled() {
    running = false;
}

@Override
protected Void doInBackground(Void... params) {

    // does the hard work

    while (running) {
        // part 1 of the hard work
    }

    while (running) {
        // part 2 of the hard work
    }

    // ...

    while (running) {
        // part x of the hard work
    }
    return null;
}

// ...

Для опции (b.) Ваш код в 'doInBackground' будет выглядеть примерно так:

public class MyTask extends AsyncTask<Void, Void, Void> {

//...

@Override
protected Void doInBackground(Void... params) {

    // part 1 of the hard work
    // ...
    if (isCancelled()) {return null;}

    // part 2 of the hard work
    // ...
    if (isCancelled()) {return null;}

    // ...

    // part x of the hard work
    // ...
    if (isCancelled()) {return null;}
}

// ...