Android - 强制取消 AsyncTask

Posted

技术标签:

【中文标题】Android - 强制取消 AsyncTask【英文标题】:Android - Cancel AsyncTask Forcefully 【发布时间】:2011-06-12 13:08:44 【问题描述】:

我已经在我的一项活动中实现了 AsyncTask:

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

现在,我需要实现“取消”按钮功能,所以我必须停止执行正在运行的任务。我不知道如何停止正在运行的任务(后台任务)。

所以请建议我,我如何强制取消 AsyncTask ?

更新:

我发现了同样的Cancel()方法,但我发现调用cancel(boolean mayInterruptIfRunning)并不一定会停止后台进程的执行。似乎发生的一切是 AsyncTask 将执行 onCancelled(),并且在完成时不会运行 onPostExecute()。

【问题讨论】:

查看这个关于取消异步任务的正确方法的一个很好的例子quicktips.in/correct-way-to-cancel-an-asynctask-in-android 【参考方案1】:

偶尔检查一下isCancelled()

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

【讨论】:

@user456046 是的,您已经指出了我在代码中缺少的确切内容。实际上,我已经实现了 cancel() 方法,但不知道我们需要在 doInBackground() 方法中写入这种指示。非常感谢 @PareshMayani:我应该从哪里调用 asynctask.cancel 方法,即我想将我的取消按钮放在 asynctask 描述的 5 个方法中的任何一个中 @PareshMayani 嘿,请过来看看,我已经解释了我的问题。谢谢...... @PareshMayani isCancelled() 总是返回 false,即使我调用 asynctask.cancel(true); 在 doinBackground 的开头或结尾处或任何地方添加 isCancelled()?【参考方案2】:

AsyncTask 上致电cancel()。这是否真的会取消任何东西取决于你在做什么。引用 Romain Guy 的话:

如果你调用cancel(true),一个中断 将被发送到后台线程, 这可能有助于可中断的任务。 否则,您只需确保 定期检查 isCancelled() 你的 doInBackground() 方法。你可以 请参阅此处的示例 code.google.com/p/shelves.

【讨论】:

但是即使在调用 Cancel() 方法之后,任务仍在后台运行,并且 onPostExecute() 方法没有被执行。 Thanx,你是绝对正确的,你也在突出显示的描述中指出了确切的东西,但当时我无法理解这件事,但现在我开始理解你在回答中突出显示的描述。感谢 +1 点赞 调用 Cancel(true) 调用 onCancelled(Object) 而不是 onPostExecute()。 每次你添加你所知道的关于答案的每一件事,谢谢! @AndyRes:参见the Java documentation,以及this 和this。 “该线程在任何情况下都应该是可中断的”——不。【参考方案3】:

这实际上取决于您在异步任务中执行的操作。

如果它是一个处理大量文件的循环,您可以在每个文件之后检查是否引发了 isCanceled() 标志,如果是则从循环中中断。

如果它是一个执行很长操作的单行命令,那么你无能为力。

最好的解决方法是不使用异步任务的取消方法并使用您自己的 cancelFlag 布尔值。 然后你可以在你的 postExecute 中测试这个 cancelFlag 来决定如何处理结果。

【讨论】:

感谢深入描述的回复,但是当我试图实现这个东西时,当时我读了你的答案但没有得到确切的想法,这次我已经实现了这个东西并且AsyncTask 正在工作并成功停止。感谢您提供有用的答案。【参考方案4】:

在 cmets 案例中提到,如果我关闭我的应用程序,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 是其中一个视图的私有类,因此需要标志的 getter 或 setter 来通知 AsyncTask 当前实际的标志值。

我的多项测试(AVD Android 4.2.2,Api 17)表明,如果 AsyncTask 已经在执行其doInBackground,那么isCancelled() 不会对任何取消尝试做出反应(即继续为假)它,例如在mViewGroup.removeAllViews();MainActivityOnDestroy 期间,每个都会导致视图分离

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

如果由于引入了FlagCancelled,我设法强制停止doInBackground(),则调用onPostExecute(),但不会调用onCancelled()onCancelled(Void result)(自API 级别11 起)。 (我不知道为什么,因为它们应该被调用而onPostExecute() 不应该被调用,“Android API 文档说:调用 cancel() 方法保证永远不会调用 onPostExecute(Object)。” - IdleSun,answering a similar question )。

另一方面,如果同一个 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方法中

通过在 ProgressDialog 上创建dismissDialogListerner,用户可以按下后退按钮并实际取消 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();
    

【讨论】:

这个方法“nullifyAsyncTask()”的内容是什么 nullifyAsyncTask() 是一个帮助方法,用于删除注册到活动的任何异步任务。这对于在应用程序关闭或转到另一个活动后删除与异步任务的任何关联很有用。【参考方案6】:

我们的全局 AsyncTask 类变量

LongOperation LongOperationOdeme = new LongOperation();

以及中断 AsyncTask 的 KEYCODE_BACK 动作

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

它对我有用。

【讨论】:

以上是关于Android - 强制取消 AsyncTask的主要内容,如果未能解决你的问题,请参考以下文章

承诺 - 是不是可以强制取消承诺

强制取消静音 HTML5 视频自动播放

通过指针、强制转换和取消引用加载向量?

强制从饼图中取消选择切片

为啥 Rust 不在匹配模式中执行隐式取消引用强制?

sqlyog不用密码登陆(强制取消)