防止AsyncTask被多次执行

5

我在 UI 线程上点击按钮调用一个异步任务,该任务执行一些 HTTP-Get 请求。

如果用户在当前任务完成之前再次单击按钮,会发生什么?这是否由异步任务内部处理,还是我需要自行处理?


1
一个 AsyncTask 实例永远不可能被执行超过一次。 - njzk2
@njzk2 每次按钮点击都会创建一个新实例。 - Johan
1
然后就由你决定了。我建议在按钮被点击后将其禁用,并且只有在异步任务完成时(很可能是在onPostExecute中)才重新启用它。你也可以只有一个异步任务实例。 - njzk2
5个回答

6

有两种方法可以做到这一点。

1:显示进度对话框并阻止用户进行进一步的输入。

2:在类范围内私有创建你的AsyncTask变量。这样它将永远不会再次运行。


我知道了。关于你的第二个选项:我应该在我的onCreate方法中设置异步任务,并在按钮中执行execute()吗? - Johan
@Johan 如果你这样做,以后就无法再运行它了。请创建一个asynctask成员,这样你可以检查它的状态,如果已经完成,你可以创建一个新的asynctask并分配给同一个成员。 - lenik
是的,你可以做到这一点。并且还要检查已经运行的异常情况。 - Naeem
2
强制用户查看进度对话框是个坏主意(你看过Google+或Gmail应用中的进度对话框吗?)…… 用户应该在等待数据时能够做一些其他事情(比如关闭应用程序、更改设置)。 - Selvin
这也意味着,如果用户更改了手机的配置,他们将不得不重新提交GET请求。通常情况下,这是次优的,因为您可能只想重新排列布局。 - BrantApps
1
@lenik 你有没有关于如何“拥有一个异步任务成员”的示例?谢谢。 - Johan

4
为什么不使用布尔标记并检查其状态?我在我的无限列表中使用这种逻辑,它从未失败过。
在doInBackground()中切换标记。
@Override
protected Void doInBackground(Void... params) {

    loadingData = true;
}

onPostExecute()中再次执行。
@Override
protected void onPostExecute(Void result) {

    // CHANGE THE LOADINGMORE STATUS TO PERMIT FETCHING MORE DATA
    loadingData = false;
}

对于按钮的点击监听器

your_btn.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {

        if (loadingData == false)    {
            // RUN THE ASYNCTASK AGAIN
        } else if (loadingData == true) {
            // SHOW A TOAST THAT YOU ARE ALREADY FETCHING DATA
        }
    }
});

1
如果您曾经通过调用 cancel 取消了一个 AsyncTask,那么您还需要在 onCancelled 中切换标志,因为 onPostExecute 不会被调用。 - dave.c
@dave.c:那是真的。但从问题的背景来看,我认为答案已经足够了。 - Siddharth Lele
我认为这不会起作用。由于调用新的“AsyncTask”,您正在不同的线程中更改“loadingData”值,因此每次都会有所不同。 - Mysterious_android

2

如果您想检查AsyncTask是否已在运行并希望忽略按钮点击,则可以在其上调用getStatus()

要实现这一点,您需要在某处保存AsyncTask的实例并且可以访问它。


好的,但如果它已经在运行并且我再次按下它会发生什么?它似乎不会抛出任何异常。 - Johan
你可以为每个按钮保留一个AsyncTask,如果它已经在运行,则忽略按钮点击。 - lenik

1

将会生成另一个AsyncTask,这可能会导致困难 - 如果您正在持久化数据,则数据保存时可能会出现竞争条件等。我建议在接收到onClick事件时禁用按钮,并显示某种指示器,表示后台正在发生某些事情。

我们已经通过在操作栏中放置一个刷新按钮来实现类似的功能。请注意,我们的异步任务在一个app.Service下运行,因此在配置更改(其中hasStartedSync被重新初始化)期间,我们依赖于一个“isTheSyncServiceRunning”检查。

// on setting the selection buttons up
public void onPrepareOptionsMenu(final Menu menu) {
 super.onPrepareOptionsMenu(menu);
 final MenuItem refreshItem = menu.findItem(id.sync);
  if (isSynchronisationServiceRunning() || hasStartedSync) {
   refreshItem.setEnabled(false);
   if (Build.VERSION.SDK_INT > 10) {
     // Replace the refresh icon with an indeterminate spinner for > 10 API.
     final LayoutInflater inflater = (LayoutInflater)getSherlockActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
     refreshItem.setActionView(inflater.inflate(layout.actionbar_indeterminate_progress, null));
   }
 }  else {
   refreshItem.setEnabled(true);
   refreshItem.setActionView(null);
 }
}


// on selection.
public boolean onOptionsItemSelected(final MenuItem item) {
 boolean isSelected;
 switch (item.getItemId()) {
   case id.sync:
     // Fire request to start GET here.
     hasStartedSync = true;
     invalidateOptionsMenu();
     break;
   ...

1

两种方法:

  1. 在AsyncTask的onPreExecute()中使用ProgressDialog,并使用setCancelable(false)设置为不可取消。在AsyncTask的onPostExecute()中调用ProgressDialog的dismiss()方法。这将显示一个阻塞进度对话框。您还可以在此处显示一些消息或进度。

  2. 在AsyncTask的onPreExecute()中,调用Button的setEnabled(false)方法。在AsyncTask的onPostExecute()中,调用Button的setEnabled(true)方法。这将在任务运行时禁用按钮。您还可以在这些点上更改按钮文本。甚至可以暂时将其替换为进度旋转器(如ActionBar中的)。

此外,您可以在“运行”和“取消”之间切换按钮,就像现代Web浏览器中的刷新按钮一样。这将给用户更多控制权。

注意事项:onDoInBackground()中的所有内容都包裹在try-catch中,以便AsyncTask始终正常退出,并且onPostExecute()始终被调用。您可以从onPostExecute(result)的结果参数判断HTTP请求是否有任何返回。 示例:只需在单击某个Button时调用此方法,即可将一些文本从服务器加载到TextView中:
public static void runHTTP(final TextView targetView, final Button triggerBtn){

    //---new task----
    AsyncTask<Void,Void,String> task = new AsyncTask<Void, Void, String>(){
        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            //----disable button---
            triggerBtn.setEnabled(false);

            //---show message----
            targetView.setText("Loading...");
        }

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

            //----update result---
            targetView.setText(result);

            //----re-enable button---
            triggerBtn.setEnabled(true);
        }

        @Override
        protected String doInBackground(Void... voids) {

            //---default value--
            String result = "No Data";

            //--for safety--
            try{

            //-----do time consuming stuff here ---

            }catch (Exception e){

                //---error value--
                result = "Error fetching data";
            }

            return result;
        }
    };

    //----run task----
    task.execute();
}

进度对话框很糟糕...你看过Google+或Gmail应用中的进度对话框吗?更好的模式是将不确定的进度放在某个地方并禁用不需要的UI(例如此按钮),因此对于对话框打-1分,对于禁用按钮打+1分。 - Selvin
@Selvin 一个选项就是一个选项 :). 此外,Google 创建了 ProgressDialog,所以至少在某些情况下应该有一些用处。 - S.D.
@Selvin 当你的手机关机时,他们不能冒险让你在用户界面上做其他任何事情。 - S.D.

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