Android:取消异步任务

Posted

技术标签:

【中文标题】Android:取消异步任务【英文标题】:Android: Cancel Async Task 【发布时间】:2011-05-18 02:13:27 【问题描述】:

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

上传图片时,我看到一个进度对话框,用 onPreExecute() 方法编写,如下所示:

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

好的,当我按下后退按钮时,显然对话框会因为 setCancelable(true) 而消失。

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

那么我该如何解决这个问题?当我按下后退按钮时,我想取消对话框和异步任务。有什么想法吗?

编辑:找到解决方案。在下面查看我的答案。

【问题讨论】:

【参考方案1】:

来自 SDK:

取消任务

可以通过调用 cancel(boolean) 随时取消任务。 调用此方法将导致后续调用 isCancelled() 返回真。 调用此方法后,onCancelled(Object),而不是 onPostExecute(Object) 将在 doInBackground(Object[]) 返回后调用。 为确保尽快取消任务, 您应该始终定期检查 isCancelled() 的返回值 doInBackground(Object[]),如果可能的话(例如在循环中。)

所以您的代码适合对话侦听器:

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

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

例如:

if (isCancelled()) 
    break;
else

   // do your work here

【讨论】:

是的,我同意你的观点,但问题是我没有在我的 doInBackground 上使用循环。我只是使用一些命令通过 ftp 上传图像。所以我想知道,像你说的那样,在我发送图像之前检查任务是否被取消有关系吗? @mole363,您只需在 doInBackground 方法中使用 isCancelled() 来检查任务是否被取消。 这不会取消网络操作,因为不涉及循环。 “必须检查 onPreExecute() 方法中的 isCancelled()。”...您能解释一下吗?就文档而言:“调用此方法将导致 onCancelled(Object) 被调用在 doInBackground(Object[]) 返回后的 UI 线程上。调用此方法保证永远不会调用 onPostExecute(Object)。调用此方法后,您应该定期检查 isCancelled() 返回的值从 doInBackground(Object[]) 到尽早完成任务。” 这意味着一旦用户调用 mytask.cancel(true); 就会调用 onCancelled() 回调;在异步任务上【参考方案2】:

找到解决方案: 我在上传Dialog.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();

【讨论】:

你的解决方案和我上面发布的添加动作监听器的解决方案有什么区别? 您不必在显示对话框之前如此公开地设置取消侦听器,有一个show 方法接受取消侦听器作为其参数之一。 正常操作下onPostExecute是否应该关闭对话框?【参考方案3】:

我花了一段时间才弄清楚这一点,我想要的只是一个简单的例子来说明如何做到这一点,所以我想我会发布我是如何做到的。这是一些更新图书馆的代码,并有一个进度对话框显示已更新的图书数量,并在用户关闭对话框时取消:

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);
    

【讨论】:

【参考方案4】:

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

YourAsyncTask mTask;
Dialog mDialog;

将这些用于您的对话和任务;

在 onPause() 中只需调用

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

【讨论】:

【参考方案5】:

我想改进代码。当您取消aSyncTask 时,onCancelled()aSyncTask 的回调方法)会被自动调用,您可以在那里隐藏您的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();
        

    

【讨论】:

【参考方案6】:

在我使用 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;
    

【讨论】:

【参考方案7】:

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

volatile boolean running;

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

    running=true;
    ...
    


public void onDestroy() 
    super.onDestroy();

    running=false;
    ...

然后,我会定期在我的异步代码中检查“是否正在运行”。我已经对此进行了压力测试,现在我无法“破坏”我的活动。这完美地工作并且具有比我在 SO 上看到的一些解决方案更简单的优点。

【讨论】:

【参考方案8】:

您可以只要求取消,但不能真正终止它。看到这个answer。

【讨论】:

【参考方案9】:

如何取消 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() 上 isCancelled() 返回的值。如下图所示。

protected Object doInBackground(Params… params)   
 while (condition)

 ...
if (isCancelled()) 
break;

return null; 

【讨论】:

以上是关于Android:取消异步任务的主要内容,如果未能解决你的问题,请参考以下文章

Android取消多次调用的异步任务

Android异步任务不会改变用户界面

我如何知道取消的异步任务是不是完成?

如何从客户端取消异步任务

在完成之前取消列表视图适配器的异步任务

浅谈android中异步加载之"取消异步加载"二