Android - 强制取消 AsyncTask

64

我在我的一个活动中实现了AsyncTask:

 performBackgroundTask asyncTask = new performBackgroundTask();
 asyncTask.execute();
现在,我需要实现“取消”按钮的功能,因此我必须停止正在运行的任务的执行。我不知道如何停止正在运行的任务(后台任务)。
请建议我如何强制取消AsyncTask?
更新:
我发现了其 Cancel() 方法,但我发现调用cancel(boolean mayInterruptIfRunning)并不一定会停止后台进程的执行。似乎发生的所有事情都是 AsyncTask 将执行 onCancelled(),并且在完成时不会运行 onPostExecute()。

请查看以下链接,了解正确取消Android中Asynctask的方法:http://www.quicktips.in/correct-way-to-cancel-an-asynctask-in-android/ - Deepak Swami
6个回答

91

定期检查 isCancelled() 即可:

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

2
@user456046,是的,您指出了我在代码中缺少的东西。实际上,我已经实现了cancel()方法,但不知道我们需要在doInBackground()方法中编写这种指示。非常感谢。 - Paresh Mayani
1
@PareshMayani:我应该从哪里调用asynctask.cancel方法,也就是说,我是否应该将我的取消按钮放在asynctask描述的5个方法中的任何一个中? - Shahzad Imam
@PareshMayani 嘿,请在那里检查一下,我已经解释了我的问题。谢谢...... - Shahzad Imam
2
@PareshMayani isCancelled() 总是返回 false,即使我调用了 asynctask.cancel(true); - hemanth kumar
在doinBackground的开头、结尾或其他任何地方添加isCancelled()? - BaDo

49

调用AsyncTask上的cancel()方法。这是否会真正取消任何内容,取决于你正在做什么。引用Romain Guy的话:

如果你调用cancel(true),将向后台线程发送一个中断信号, 这可能有助于中断任务。否则,您应该确保在doInBackground()方法中定期检查isCancelled()。 您可以在code.google.com/p/shelves里看到此类示例。


1
但是即使调用了Cancel()方法,任务仍在后台运行,并且onPostExecute()方法没有被执行。 - Paresh Mayani
每次你添加关于答案的所有信息,感谢! - Tushar Pandey
Romain的引言(以及文档)中有一个有用的收获,即线程被中断。因此,如果您正在执行长时间循环操作但没有asynctask引用来调用isCancelled(),则可以调用Thread.currentThread.isInterrupted。 - Flynny75
关于:中断信号将被发送到后台线程,这可能有助于可中断任务。我想知道一些不可中断任务的例子是什么?由于doInBackground()在不同的线程上运行,因此该线程应该在任何情况下都可以被中断。那么这是否意味着调用cancel(true)应该始终中断当前正在运行的线程?我之所以问这个问题,是因为有很多例子建议使用isCanceled()检查AsyncTask是否已停止,但如果cancel(true)中断了当前线程,我们为什么还需要这个呢? - Andy Res
1
@AndyRes:请参考Java文档,以及这个这个。"该线程在任何情况下都应该是可中断的" -- 不是。 - CommonsWare
显示剩余3条评论

16

这真的取决于你在异步任务中做什么。

如果它是一个循环处理大量文件,你可以在每个文件后检查isCanceled()标志是否被触发,如果是,则从循环中退出。

如果它是一条执行非常长时间操作的单行命令,那么你没有太多办法。

最好的解决方法是不使用asynctask的cancel方法,而是使用自己的cancelFlag布尔值。然后你可以在postExecute中测试这个cancelFlag来决定如何处理结果。


谢谢您的详细回复,但是当我尝试实现这个东西时,我读了你的答案,却没有得到确切的想法。这次我已经实现了它,AsyncTask能够成功地启动和停止。非常感谢您的帮助性答案。 - Paresh Mayani

5
在评论中提到的问题是,即使我调用asynctask.cancel(true),isCancelled()总是返回false。如果我关闭我的应用程序,但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()对任何尝试取消它都没有反应(即继续保持false),例如在mViewGroup.removeAllViews();期间或在MainActivity的OnDestroy期间,在这些期间会导致视图分离。
   @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,在回答类似问题时说)。
另一方面,如果相同的 AsyncTask 在取消之前没有启动其 doInBackground(),则一切正常,isCancelled() 变为 true,我可以在其中检查。
@Override
    protected void onCancelled() {
        Log.d(TAG, String.format("mAsyncTask - onCancelled: isCancelled = %b, FlagCancelled = %b", this.isCancelled(), FlagCancelled ));
    super.onCancelled();
}

2
尽管AsyncTask不应用于长时间运行的操作,但有时它可能会陷入无响应的任务中(例如无响应的HTTP调用)。在这种情况下,可能需要取消AsyncTask。
我们在执行此操作时面临两个挑战: 1. 当用户按下后退按钮时,通常与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()" 的内容是什么? - Khalid ElSayed
1
nullifyAsyncTask() 是一个辅助方法,用于删除注册到活动中的任何异步任务。这对于在应用程序关闭或转到另一个活动后删除与异步任务的任何关联非常有用。 - Droid Chris

1
我们的全局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);
    }

它对我有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接