安卓:取消异步任务

63

我使用异步任务来上传图像并获取一些结果。

在上传图像时,我会看到一个进度对话框,在onPreExecute()方法中编写如下:

    protected void onPreExecute() { 
         uploadingDialog = new ProgressDialog(MyActivity.this); 
         uploadingDialog.setMessage("uploading"); 
         uploadingDialog.setCancelable(true);
         uploadingDialog.show();
    }

当我按下返回按钮时,对话框会因为设置了 setCancelable(true) 而消失。

但是(显然)异步任务并没有停止。

那么我该怎么办呢?我希望在按下返回按钮时同时取消对话框和异步任务。有任何想法吗?

9个回答

110

从SDK中:

取消任务

可以通过调用cancel(boolean)随时取消任务。调用此方法将导致后续调用isCancelled()返回true。

调用此方法后,doInBackground(Object [])返回后将调用onCancelled(Object),而不是onPostExecute(Object)。

为确保尽快取消任务,如果可能的话(例如,在循环内部),您应该定期检查doInBackground(Object [])的isCancelled()的返回值。

因此,对于对话框侦听器,您的代码是正确的:

uploadingDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
    public void onCancel(DialogInterface dialog) {
        myTask.cancel(true);
        //finish();
    }
});

现在,正如我之前从SDK中提到的那样,您必须检查任务是否已取消,为此您必须在onPreExecute()方法中检查isCancelled()

例如:

if (isCancelled()) 
    break;
else
{
   // do your work here
}

2
是的,我同意你的观点,但问题在于我在doInBackground中没有使用循环。我只是使用一些命令通过ftp上传图像。所以我想知道,在发送图像之前检查任务是否被取消是否重要,就像你说的那样? - steliosf
@mole363,你只需要在doInBackground方法中使用isCancelled()来检查任务是否被取消。 - Paresh Mayani
这不会取消网络操作,因为没有涉及循环。 - Monish Kamble
在onPreExecute()方法中必须检查isCancelled()。你能解释一下吗?根据文档:“调用此方法将导致在doInBackground(Object[])返回后在UI线程上调用onCancelled(Object)。调用此方法保证不会调用onPostExecute(Object)。在调用此方法之后,您应该定期检查从doInBackground(Object[])返回的isCancelled()值,以尽早完成任务。” - kgandroid
这意味着当用户在AsyncTask上调用mytask.cancel(true)时,onCancelled()回调将被调用一次。 - kgandroid

31

找到了解决方案: 在uploadingDialog.show()之前,我添加了一个动作监听器,代码如下:

    uploadingDialog.setOnCancelListener(new DialogInterface.OnCancelListener(){
          public void onCancel(DialogInterface dialog) {
              myTask.cancel(true);
              //finish();
          }
    });

这样当我按下返回按钮时,上面的OnCancelListener会取消对话框和任务。 如果您想在按下后退键时完成整个活动,还可以添加finish()。 记得将异步任务声明为变量,例如:

    MyAsyncTask myTask=null;

并且您可以像这样执行异步任务:

    myTask = new MyAsyncTask();
    myTask.execute();

50
你的解决方案和我在上面发布的添加动作监听器的解决方案有什么区别? - Paresh Mayani
2
您不必在显示对话框之前如此明确地设置取消监听器,因为 show 方法接受取消监听器作为其参数之一。 - Ken
在正常操作下,onPostExecute应该关闭对话框吗? - danny117

12

我花了一些时间弄清楚这个问题,我只想要一个简单的例子来展示如何实现它,所以我认为我应该发布一下我是如何做到的。这是一些更新库的代码,并显示进度对话框,展示已经更新了多少本书,并在用户取消对话框时取消:

private class UpdateLibrary extends AsyncTask<Void, Integer, Boolean>{
    private ProgressDialog dialog = new ProgressDialog(Library.this);
    private int total = Library.instance.appState.getAvailableText().length;
    private int count = 0;

    //Used as handler to cancel task if back button is pressed
    private AsyncTask<Void, Integer, Boolean> updateTask = null;

    @Override
    protected void onPreExecute(){
        updateTask = this;
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        dialog.setOnDismissListener(new OnDismissListener() {               
            @Override
            public void onDismiss(DialogInterface dialog) {
                updateTask.cancel(true);
            }
        });
        dialog.setMessage("Updating Library...");
        dialog.setMax(total);
        dialog.show();
    }

    @Override
    protected Boolean doInBackground(Void... arg0) {
            for (int i = 0; i < appState.getAvailableText().length;i++){
                if(isCancelled()){
                    break;
                }
                //Do your updating stuff here
            }
        }

    @Override
    protected void onProgressUpdate(Integer... progress){
        count += progress[0];
        dialog.setProgress(count);
    }

    @Override
    protected void onPostExecute(Boolean finished){
        dialog.dismiss();
        if (finished)
            DialogHelper.showMessage(Str.TEXT_UPDATELIBRARY, Str.TEXT_UPDATECOMPLETED, Library.instance);
        else 
            DialogHelper.showMessage(Str.TEXT_UPDATELIBRARY,Str.TEXT_NOUPDATE , Library.instance);
    }
}

9

在您的活动中创建一些成员变量,例如

YourAsyncTask mTask;
Dialog mDialog;

使用这些内容来创建你的对话框和任务;

在onPause()中,简单调用

if(mTask!=null) mTask.cancel(); 
if(mDialog!=null) mDialog.dismiss();

7
我希望你能改进代码。当您取消异步任务aSyncTask时,aSyncTask的回调方法onCancelled()将自动被调用,在那里您可以隐藏progressBarDialog您也可以包含此代码:
public class information extends AsyncTask<String, String, String>
    {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected String doInBackground(String... arg0) {
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            this.cancel(true);
        }

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

        @Override
        protected void onCancelled() {
            Toast.makeText(getApplicationContext(), "asynctack cancelled.....", Toast.LENGTH_SHORT).show();
            dialog.hide(); /*hide the progressbar dialog here...*/
            super.onCancelled();
        }

    }

3
大多数情况下,我使用AsyncTask时,我的业务逻辑是在一个独立的业务类中而不是在UI上。在这种情况下,我无法在doInBackground()中使用循环。例如,同步过程会消耗服务并按顺序保存数据。

最终,我将任务移交给业务对象来处理取消。我的设置如下:

public abstract class MyActivity extends Activity {

    private Task mTask;
    private Business mBusiness;

    public void startTask() {
        if (mTask != null) {
            mTask.cancel(true);
        }
        mTask = new mTask();
        mTask.execute();
    }
}

protected class Task extends AsyncTask<Void, Void, Boolean> {
    @Override
    protected void onCancelled() {
        super.onCancelled();

        mTask.cancel(true);

        // ask if user wants to try again
    }

    @Override
    protected Boolean doInBackground(Void... params) {
        return mBusiness.synchronize(this);
    }

    @Override
    protected void onPostExecute(Boolean result) {
        super.onPostExecute(result);

        mTask = null;

        if (result) {
            // done!
        }
        else {
            // ask if user wants to try again
        }
    }
}

public class Business {
    public boolean synchronize(AsyncTask<?, ?, ?> task) {
        boolean response = false;
        response = loadStuff(task);

        if (response)
            response = loadMoreStuff(task);

        return response;
    }

    private boolean loadStuff(AsyncTask<?, ?, ?> task) {
        if (task != null && task.isCancelled()) return false;

        // load stuff

        return true;
    }
}

3

我有一个类似的问题 - 实际上,我在用户销毁活动后在异步任务中得到了NPE。 在Stack Overflow上研究了这个问题后,我采用了以下解决方案:

volatile boolean running;

public void onActivityCreated (Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    running=true;
    ...
    }


public void onDestroy() {
    super.onDestroy();

    running=false;
    ...
}

然后,在我的异步代码中,我定期检查"是否运行"。经过压力测试,我现在无法"破坏"我的活动。这种方法完美地工作,并且比我在SO上看到的某些解决方案更简单。


2

你可以要求取消操作,但并不能真正终止它。请参考这个答案


1

如何取消AsyncTask

完整答案在这里 - Android AsyncTask Example

AsyncTask提供了更好的取消策略,以终止当前正在运行的任务。

cancel(boolean mayInterruptIfitRunning)

myTask.cancel(false)- 它使isCancelled返回true。有助于取消任务。

myTask.cancel(true) - 它还使isCancelled()返回true,中断后台线程并释放资源。

如果后台线程中有thread.sleep()方法正在执行,它被认为是一种傲慢的方式,cancel(true)会在那时中断后台线程。但是,cancel(false)将等待它完成并在该方法完成时取消任务。

如果调用cancel()并且doInBackground()尚未开始执行,则将调用onCancelled()。

在调用cancel(...)之后,应定期检查doInbackground()返回的值。就像下面所示。

protected Object doInBackground(Params… params)  { 
 while (condition)
{
 ...
if (isCancelled()) 
break;
}
return null; 
}

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