Android - принудительно отмените AsyncTask

Я реализовал AsyncTask в моей деятельности:

 performBackgroundTask asyncTask = new performBackgroundTask();
 asyncTask.execute();

Теперь мне нужно реализовать функцию кнопки "Отмена", поэтому мне нужно остановить выполнение запущенной задачи. Я не знаю, как остановить текущее задание (фоновое задание).

Поэтому, пожалуйста, предложите мне, как я могу принудительно отменить AsyncTask?

Обновить:

Я нашел о Cancel() метод тот же, но я обнаружил, что вызов cancel(boolean mayInterruptIfRunning) не обязательно останавливает выполнение фонового процесса. Похоже, все, что происходит, это то, что AsyncTask будет выполнять onCancelled() и не будет запускаться onPostExecute() после его завершения.

6 ответов

Решение

Просто проверь isCancelled() иногда:

 protected Object doInBackground(Object... x) {
    while (/* condition */) {
      // work...
      if (isCancelled()) break;
    }
    return null;
 }

Вызов cancel() на AsyncTask, Будет ли это на самом деле отменять что-либо, зависит немного от того, что вы делаете. Цитировать Ромена Гая:

Если вы вызываете Cancel(true), прерывание будет отправлено в фоновый поток, что может помочь прерываемым задачам. В противном случае вы должны просто регулярно проверять isCancelled() в вашем методе doInBackground(). Вы можете увидеть примеры этого на code.google.com/p/shelves.

Это действительно зависит от того, что вы делаете в своей асинктической задаче.

Если это цикл, обрабатывающий много файлов, вы можете просто проверить после каждого файла, установлен ли флаг isCanceled() или нет, а затем отключить его от цикла, если он есть.

Если это однострочная команда, которая выполняет очень длинную операцию, вы ничего не можете сделать.

Лучший обходной путь - не использовать метод отмены асинхронной задачи и использовать свой логический параметр cancelFlag. Затем вы можете протестировать этот cancelFlag в вашем postExecute, чтобы решить, что делать с результатом.

Упомянутое в комментариях бывает, что isCancelled() always returns false even i call asynctask.cancel(true); Особенно вредно, если я закрываю свое приложение, но AsyncTask продолжает работать.

Чтобы решить эту проблему, я изменил предложенный Jacob Nordfalk код следующим образом:

protected Object doInBackground(Object... x) {
    while (/* condition */) {
      // work...
      if (isCancelled() || (FlagCancelled == true)) break;
    }
    return null;
 }

и добавил следующее к основному виду деятельности:

@Override
protected void onStop() {
    FlagCancelled = true;
    super.onStop();
}

Так как мой AsyncTask был закрытым классом одного из представлений, поэтому получатели или установщики флага были необходимы для информирования AsyncTask о текущем текущем значении флага.

Мои множественные тесты (AVD Android 4.2.2, Api 17) показали, что если AsyncTask уже выполняет doInBackground, затем isCancelled() никоим образом не реагирует (то есть остается ложным) на любые попытки отменить его, например, во время mViewGroup.removeAllViews(); или во время OnDestroy из MainActivityкаждый из которых ведет к отрыву взглядов

   @Override 
   protected  void  onDetachedFromWindow() { 
    mAsyncTask.cancel(false); // and the same result with mAsyncTask.cancel(true);
    super.onDetachedFromWindow(); 
   } 

Если мне удастся заставить остановить doInBackground() благодаря введенному FlagCancelled, затем onPostExecute() называется, но ни onCancelled() ни onCancelled(Void result) (начиная с уровня API 11) не вызываются. (Я понятия не имею, почему, потому что они должны быть вызваны и onPostExecute() не следует: "Документ Android API гласит: вызов метода cancel() гарантирует, что onPostExecute(Object) никогда не будет вызван". - IdleSun отвечая на аналогичный вопрос).

С другой стороны, если тот же AsyncTask не начал свою doInBackground() перед отменой, тогда все в порядке, isCancelled() меняется на истину, и я могу проверить, что в

@Override
    protected void onCancelled() {
        Log.d(TAG, String.format("mAsyncTask - onCancelled: isCancelled = %b, FlagCancelled = %b", this.isCancelled(), FlagCancelled ));
    super.onCancelled();
}

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

Нам приходится сталкиваться с проблемами в этом. 1. Обычный диалог прогресса, отображаемый с AsyncTask, - это первое, что отменяется в AsyncTask, когда пользователь нажимает кнопку "назад". 2. AsyncTask может быть в методе doInBackground

Создав dismissDialogListerner в ProgressDialog, пользователь может нажать кнопку "Назад" и фактически аннулировать AsycnTask и закрыть само диалоговое окно.

Вот пример:

public void openMainLobbyDoor(String username, String password){
    if(mOpenDoorAsyncTask == null){
        mOpenDoorAsyncTask = (OpenMainDoor) new OpenMainDoor(username, password, Posts.API_URL, 
                mContext, "Please wait while I unlock the front door for you!").execute(null, null, null);
    }
}

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

    //declare needed variables
    String username, password, url, loadingMessage;
    int userValidated;
    boolean canConfigure;
    Context context;
    ProgressDialog progressDialog;

    public OpenMainDoor(String username, String password, String url, 
                Context context, String loadingMessage){
        userValidated = 0;
        this.username = username;
        this.password = password;
        this.url = url;
        this.context = context;
        this.loadingMessage = loadingMessage;
    }

    /**
     * used to cancel dialog on configuration changes
     * @param canConfigure
     */
    public void canConfigureDialog(boolean canConfigure){
        this.canConfigure = canConfigure;
    }

    @Override
    protected void onPreExecute(){
        progressDialog = new ProgressDialog(this.context);
        progressDialog.setMessage(loadingMessage);
        progressDialog.setIndeterminate(true);
        progressDialog.setCancelable(true);
        progressDialog.setOnCancelListener(new OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                mOpenDoorAsyncTask.cancel(true);
            }
        });
        progressDialog.show();
        this.canConfigure = true;
    }

    @Override
    protected Void doInBackground(Void... params) {
        userValidated = Posts.authenticateNTLMUserLogin(username, password, url, context);
        while(userValidated == 0){
            if(isCancelled()){
                break;
            }
        }
        return null;
    }

    @Override
    protected void onPostExecute(Void unused){
        //determine if this is still attached to window
        if(canConfigure)
            progressDialog.dismiss();

        if(userValidated == 1){
            saveLoginValues(username, password, true);
            Toast.makeText(context, R.string.main_login_pass, Toast.LENGTH_SHORT).show();
        }else{
            saveLoginValues(username, password, false);
            Toast.makeText(context, R.string.main_login_fail, Toast.LENGTH_SHORT).show();
        }
        nullifyAsyncTask();
    }

    @Override
    protected void onCancelled(){
        Toast.makeText(context, "Open door request cancelled!", Toast.LENGTH_SHORT).show();
        nullifyAsyncTask();
    }
}

Наша глобальная переменная класса 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);
    }

Меня устраивает.

Другие вопросы по тегам