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

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

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

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

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

Обновление:

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

Ответ 1

Просто проверяйте isCancelled() время от времени:

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

Ответ 2

Вызовите cancel() на AsyncTask. Независимо от того, действительно ли это отменит что-либо, зависит немного от того, что вы делаете. Процитировать Romain Guy:

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

Ответ 3

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

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

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

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

Ответ 4

Указанный в комментариях случай, что 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() не должны ", - говорит доктор API API. Вызов метода cancel() гарантирует, что onPostExecute (Object) никогда не вызывается." - IdleSun, отвечая на аналогичный вопрос).

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

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

Ответ 5

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

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

Создав функцию offsetDialogListerner в 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();
    }
}

Ответ 6

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

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