延迟一段时间后重复执行任务?

241

我的代码中有一个变量,比如说它叫做 "status"。

我想要根据这个变量的值在应用程序中显示一些文本。并且需要在特定的延迟时间后完成操作。

就像这样:

  • 检查状态变量的值

  • 显示一些文本

  • 等待10秒钟

  • 检查状态变量的值

  • 显示一些文本

  • 等待15秒钟

以此类推。延迟时间可能会有所不同,并且是在显示文本之后设置的。

我尝试过 Thread.sleep(延迟时间),但失败了。有更好的方法吗?


12个回答

483

您应该使用HandlerpostDelayed函数来实现这个目的。它将在主UI线程上以指定的延迟运行您的代码,因此您将能够更新UI控件。

private int mInterval = 5000; // 5 seconds by default, can be changed later
private Handler mHandler;

@Override
protected void onCreate(Bundle bundle) {

    // your code here

    mHandler = new Handler();
    startRepeatingTask();
}

@Override
public void onDestroy() {
    super.onDestroy();
    stopRepeatingTask();
}

Runnable mStatusChecker = new Runnable() {
    @Override 
    public void run() {
          try {
               updateStatus(); //this function can change value of mInterval.
          } finally {
               // 100% guarantee that this always happens, even if
               // your update method throws an exception
               mHandler.postDelayed(mStatusChecker, mInterval);
          }
    }
};

void startRepeatingTask() {
    mStatusChecker.run(); 
}

void stopRepeatingTask() {
    mHandler.removeCallbacks(mStatusChecker);
}

1
谢谢inazaruk,我成功了。发现了两个非常小的错别字(在顶部是“Handler”而不是“Handle”,在底部是“removeCallbacks”而不是“removecallback”)。但无论如何,这段代码正是我所需要的。我正在考虑怎样回报你的恩情。至少你赢得了我的尊重。敬礼,Aubrey Bourke。 - aubreybourke
25
程序很好,运行完全正常。但是startRepeatingTask()必须从onCreate方法/ UI线程中调用(我花了一些时间才意识到这一点!),也许这一点可以在某个地方提到。问候。 - gkris
在适配器的getView()方法中,有没有一种重复执行Runnable的方法? - Etienne Lawlor
1
在这里,当我们导入类时,应该导入哪个类? android.os.Handler 还是 java.util.logging.Handler? - E J Chathuranga
1
@inazruk,如果你必须从可运行的代码块内部运行stopRepeatingTask(),例如在检查变量值后,你的解决方案是不起作用的。因为你在finally块中postDelayed。 - likejudo
显示剩余3条评论

41

如果有人感兴趣,这是我创建的一个类,使用inazaruk的代码创建了所有需要的内容(我称之为UIUpdater,因为我用它来定期更新UI,但你可以随意更改名称):

import android.os.Handler;
/**
 * A class used to perform periodical updates,
 * specified inside a runnable object. An update interval
 * may be specified (otherwise, the class will perform the 
 * update every 2 seconds).
 * 
 * @author Carlos Simões
 */
public class UIUpdater {
        // Create a Handler that uses the Main Looper to run in
        private Handler mHandler = new Handler(Looper.getMainLooper());

        private Runnable mStatusChecker;
        private int UPDATE_INTERVAL = 2000;

        /**
         * Creates an UIUpdater object, that can be used to
         * perform UIUpdates on a specified time interval.
         * 
         * @param uiUpdater A runnable containing the update routine.
         */
        public UIUpdater(final Runnable uiUpdater) {
            mStatusChecker = new Runnable() {
                @Override
                public void run() {
                    // Run the passed runnable
                    uiUpdater.run();
                    // Re-run it after the update interval
                    mHandler.postDelayed(this, UPDATE_INTERVAL);
                }
            };
        }

        /**
         * The same as the default constructor, but specifying the
         * intended update interval.
         * 
         * @param uiUpdater A runnable containing the update routine.
         * @param interval  The interval over which the routine
         *                  should run (milliseconds).
         */
        public UIUpdater(Runnable uiUpdater, int interval){
            UPDATE_INTERVAL = interval;
            this(uiUpdater);
        }

        /**
         * Starts the periodical update routine (mStatusChecker 
         * adds the callback to the handler).
         */
        public synchronized void startUpdates(){
            mStatusChecker.run();
        }

        /**
         * Stops the periodical update routine from running,
         * by removing the callback.
         */
        public synchronized void stopUpdates(){
            mHandler.removeCallbacks(mStatusChecker);
        }
}

您可以在类内部创建一个UIUpdater对象,并像这样使用它:

...
mUIUpdater = new UIUpdater(new Runnable() {
         @Override 
         public void run() {
            // do stuff ...
         }
    });

// Start updates
mUIUpdater.startUpdates();

// Stop updates
mUIUpdater.stopUpdates();
...
如果您想将此用作活动更新程序,请将启动调用放在onResume()方法中,将停止调用放在onPause()方法中,这样更新将根据活动的可见性开始和停止。

1
请将 UPDATE_INTERVAL = interval; 放在 UIUpdater(Runnable uiUpdater, int interval)this(uiUpdater); 之前(因为 UPDATE_INTERVAL 的值会被使用,应该是作为参数 interval 传递的值)。此外,在可能的情况下(几乎总是),请避免代码宽度超过80个字符。 - Mr_and_Mrs_D
6
这个类存在一些问题。首先,它应该在主线程中实例化以便能够更新GUI。你可以通过将主线程looper传递给handler构造函数来解决这个问题:new Handler(Looper.getMainLooper())。其次,它不验证参数,因此会忽略空的Runnables和负数间隔。最后,它没有考虑uiUpdater.run()行所花费的时间,也没有处理该方法可能抛出的异常。另外,它不是线程安全的,你应该将startstop方法设为同步方法。 - Mister Smith
2
由于我没有Eclipse来测试代码,因此编辑了参数验证部分。感谢您的反馈!这是您的意思吗?同步startUpdates和stopUpdates,并在Handler构造函数中放置一个Looper.getMainLooper()调用(希望您可以直接从字段声明中调用它)。 - ravemir
2
我得到了这个错误信息:error: call to this must be first statement in constructor,也许有一个简单的解决方法。 - msysmilu
4
投赞成票是为了拥有导入 - 在轻松编写 Java 代码时,需要花时间找出 Handler 来源。 - Roman Susi

25

我认为现在最流行的是使用ScheduledThreadPoolExecutor。用法如下:

private final ScheduledThreadPoolExecutor executor_ = 
        new ScheduledThreadPoolExecutor(1);
this.executor_.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
    update();
    }
}, 0L, kPeriod, kTimeUnit);

1
在这里,Executors.newSingleThreadScheduledExecutor() 可以是另一个选项。 - Gulshan

21

有三种方法可以实现:

使用ScheduledThreadPoolExecutor

这有点过度,因为你并不需要一个线程池

   //----------------------SCHEDULER-------------------------
    private final ScheduledThreadPoolExecutor executor_ =
            new ScheduledThreadPoolExecutor(1);
     ScheduledFuture<?> schedulerFuture;
   public void  startScheduler() {
       schedulerFuture=  executor_.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                //DO YOUR THINGS
                pageIndexSwitcher.setVisibility(View.GONE);
            }
        }, 0L, 5*MILLI_SEC,  TimeUnit.MILLISECONDS);
    }


    public void  stopScheduler() {
        pageIndexSwitcher.setVisibility(View.VISIBLE);
        schedulerFuture.cancel(false);
        startScheduler();
    }

使用计时器任务

旧的Android风格

    //----------------------TIMER  TASK-------------------------

    private Timer carousalTimer;
    private void startTimer() {
        carousalTimer = new Timer(); // At this line a new Thread will be created
        carousalTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                //DO YOUR THINGS
                pageIndexSwitcher.setVisibility(INVISIBLE);
            }
        }, 0, 5 * MILLI_SEC); // delay
    }

    void stopTimer() {
        carousalTimer.cancel();
    }

使用 Handler 和 Runnable

现代 Android 风格

    //----------------------HANDLER-------------------------

    private Handler taskHandler = new android.os.Handler();

    private Runnable repeatativeTaskRunnable = new Runnable() {
        public void run() {
            //DO YOUR THINGS
        }
    };

   void startHandler() {
        taskHandler.postDelayed(repeatativeTaskRunnable, 5 * MILLI_SEC);
    }

    void stopHandler() {
        taskHandler.removeCallbacks(repeatativeTaskRunnable);
    }

具有活动/上下文的非泄漏处理程序

在您的Activity/Fragment类中声明一个内部Handler类,该类不会泄漏内存

/**
     * Instances of static inner classes do not hold an implicit
     * reference to their outer class.
     */
    private static class NonLeakyHandler extends Handler {
        private final WeakReference<FlashActivity> mActivity;

        public NonLeakyHandler(FlashActivity activity) {
            mActivity = new WeakReference<FlashActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            FlashActivity activity = mActivity.get();
            if (activity != null) {
                // ...
            }
        }
    }

在您的Activity/Fragment类中声明一个可运行的任务,该任务将执行您的重复操作

   private Runnable repeatativeTaskRunnable = new Runnable() {
        public void run() {
            new Handler(getMainLooper()).post(new Runnable() {
                @Override
                public void run() {

         //DO YOUR THINGS
        }
    };

在您的Activity / Fragment中初始化Handler对象(这里FlashActivity是我的activity类)

//Task Handler
private Handler taskHandler = new NonLeakyHandler(FlashActivity.this);

定时重复执行一个任务:

taskHandler.postDelayed(repeatativeTaskRunnable , DELAY_MILLIS);

停止任务的重复执行:

taskHandler .removeCallbacks(repeatativeTaskRunnable );

更新:在Kotlin中:

    //update interval for widget
    override val UPDATE_INTERVAL = 1000L

    //Handler to repeat update
    private val updateWidgetHandler = Handler()

    //runnable to update widget
    private var updateWidgetRunnable: Runnable = Runnable {
        run {
            //Update UI
            updateWidget()
            // Re-run it after the update interval
            updateWidgetHandler.postDelayed(updateWidgetRunnable, UPDATE_INTERVAL)
        }

    }

 // SATART updating in foreground
 override fun onResume() {
        super.onResume()
        updateWidgetHandler.postDelayed(updateWidgetRunnable, UPDATE_INTERVAL)
    }


    // REMOVE callback if app in background
    override fun onPause() {
        super.onPause()
        updateWidgetHandler.removeCallbacks(updateWidgetRunnable);
    }

1
“现代 Android 风格”有些东西缺失。 - Fattie
精彩的答案。 - yasirkula

15

计时器功能正常。在这里,我使用计时器在1.5秒后搜索文本并更新UI。希望这有所帮助。

private Timer _timer = new Timer();

_timer.schedule(new TimerTask() {
    @Override
    public void run() {
        // use runOnUiThread(Runnable action)
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                search();
            }
        });
    }
}, timeInterval);

你把时间间隔放在哪里了? - Nathiel Barros
1
嗨,Nathiel,我刚刚更新了我的帖子,希望它有所帮助!Interval time是Timer.schedule()的第二个参数。 - Kai Wang
如何取消正在运行的程序? - gumuruh

8

使用 Kotlin 和它的协程是相当容易的,首先在您的类中声明一个作业(最好在您的 viewModel 中),如下所示:

private var repeatableJob: Job? = null

那么当您想创建并启动它时,请执行以下操作:
repeatableJob = viewModelScope.launch {
    while (isActive) {
         delay(5_000)
         loadAlbums(iImageAPI, titleHeader, true)
    }
}
repeatableJob?.start()

如果您想完成它:

repeatableJob?.cancel()

注: viewModelScope 仅适用于视图模型,您可以使用其他协程作用域,如 withContext(Dispatchers.IO)

更多信息: 这里


6

计时器是完成任务的另一种方式,但如果您正在处理UI,请务必添加runOnUiThread

    import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;
import android.app.Activity;

public class MainActivity extends Activity {

 CheckBox optSingleShot;
 Button btnStart, btnCancel;
 TextView textCounter;

 Timer timer;
 MyTimerTask myTimerTask;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  optSingleShot = (CheckBox)findViewById(R.id.singleshot);
  btnStart = (Button)findViewById(R.id.start);
  btnCancel = (Button)findViewById(R.id.cancel);
  textCounter = (TextView)findViewById(R.id.counter);

  btnStart.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {

    if(timer != null){
     timer.cancel();
    }

    //re-schedule timer here
    //otherwise, IllegalStateException of
    //"TimerTask is scheduled already" 
    //will be thrown
    timer = new Timer();
    myTimerTask = new MyTimerTask();

    if(optSingleShot.isChecked()){
     //singleshot delay 1000 ms
     timer.schedule(myTimerTask, 1000);
    }else{
     //delay 1000ms, repeat in 5000ms
     timer.schedule(myTimerTask, 1000, 5000);
    }
   }});

  btnCancel.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    if (timer!=null){
     timer.cancel();
     timer = null;
    }
   }
  });

 }

 class MyTimerTask extends TimerTask {

  @Override
  public void run() {
   Calendar calendar = Calendar.getInstance();
   SimpleDateFormat simpleDateFormat = 
     new SimpleDateFormat("dd:MMMM:yyyy HH:mm:ss a");
   final String strDate = simpleDateFormat.format(calendar.getTime());

   runOnUiThread(new Runnable(){

    @Override
    public void run() {
     textCounter.setText(strDate);
    }});
  }

 }

}

and xml is...

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:autoLink="web"
    android:text="http://android-er.blogspot.com/"
    android:textStyle="bold" />
<CheckBox 
    android:id="@+id/singleshot"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Single Shot"/>

另一种使用CountDownTimer的方法

new CountDownTimer(30000, 1000) {

     public void onTick(long millisUntilFinished) {
         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
     }

     public void onFinish() {
         mTextField.setText("done!");
     }
  }.start();

安排一个倒计时,到未来某个时间,并在途中按照间隔定期通知。例如,在文本字段中显示 30 秒倒计时的示例:

详细信息


1
处理程序比计时器更受欢迎。请参见TImer vs Handler - Suragch

4

4

基于上面关于ScheduledThreadPoolExecutor的帖子,我设计了一个适合我的需求的实用工具(想每3秒钟触发一次方法):

class MyActivity {
    private ScheduledThreadPoolExecutor mDialogDaemon;

    private void initDebugButtons() {
        Button btnSpawnDialogs = (Button)findViewById(R.id.btn_spawn_dialogs);
        btnSpawnDialogs.setVisibility(View.VISIBLE);
        btnSpawnDialogs.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                spawnDialogs();
            }
        });
    }

    private void spawnDialogs() {
        if (mDialogDaemon != null) {
            mDialogDaemon.shutdown();
            mDialogDaemon = null;
        }
        mDialogDaemon = new ScheduledThreadPoolExecutor(1);
        // This process will execute immediately, then execute every 3 seconds.
        mDialogDaemon.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // Do something worthwhile
                    }
                });
            }
        }, 0L, 3000L, TimeUnit.MILLISECONDS);
    }
}

4

在我的情况下,如果以下任一条件成立,我需要执行一个过程:如果先前的过程已完成或者5秒钟已经过去。所以,我按照以下方式操作,并且效果非常好:

private Runnable mStatusChecker;
private Handler mHandler;

class {
method() {
  mStatusChecker = new Runnable() {
            int times = 0;
            @Override
            public void run() {
                if (times < 5) {
                    if (process1.isRead()) {
                        executeProcess2();
                    } else {
                        times++;
                        mHandler.postDelayed(mStatusChecker, 1000);
                    }
                } else {
                    executeProcess2();
                }
            }
        };

        mHandler = new Handler();
        startRepeatingTask();
}

    void startRepeatingTask() {
       mStatusChecker.run();
    }

    void stopRepeatingTask() {
        mHandler.removeCallbacks(mStatusChecker);
    }


}

如果process1被读取,它会执行process2。如果没有,它会增加变量times,并使处理程序在一秒钟后执行。它保持循环直到读取了process1或times为5。当times为5时,意味着经过了5秒钟,在每一秒钟内,将执行process1.isRead()的if子句。


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