如何在Android中设置计时器?

376

有人能给出每秒更新文本字段的简单示例吗?

我想制作一个飞行的球,并且需要每秒计算/更新球的坐标,这就是为什么我需要某种类型的计时器。

我从这里什么都没学到。


15
这个类可能会有所帮助: http://developer.android.com/reference/android/os/CountDownTimer.html - Paramvir Singh
这会有所帮助。http://sampleprogramz.com/android/chronometer.php - Ashokchakravarthi Nagarajan
24个回答

498

好的,既然这个问题还没有解决,那么有三种简单的方法来处理它。下面是一个展示所有三种方法的示例,底部还有一个示例,展示了我认为更可取的方法。同时,请记得在onPause中清理你的任务,并在必要时保存状态。


import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Handler.Callback;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class main extends Activity {
    TextView text, text2, text3;
    long starttime = 0;
    //定义Handler对象h并设置回调函数Callback,该回调函数会在主线程中更新textfield控件
   final Handler h = new Handler(new Callback() {
@Override public boolean handleMessage(Message msg) { long millis = System.currentTimeMillis() - starttime; int seconds = (int) (millis / 1000); int minutes = seconds / 60; seconds = seconds % 60;
text.setText(String.format("%d:%02d", minutes, seconds)); return false; } }); //定义Handler对象h2以及Runnable对象run,该runnable对象会在主线程中更新textfield3控件 Handler h2 = new Handler(); Runnable run = new Runnable() {
@Override public void run() { long millis = System.currentTimeMillis() - starttime; int seconds = (int) (millis / 1000); int minutes = seconds / 60; seconds = seconds % 60;
text3.setText(String.format("%d:%02d", minutes, seconds));
h2.postDelayed(this, 500); } };
//定义TimerTask子类firstTask,用于告诉handler发送一个空消息 class firstTask extends TimerTask {
@Override public void run() { h.sendEmptyMessage(0); } };
//定义TimerTask子类secondTask,用于在主线程中更新textfield2控件 class secondTask extends TimerTask {
@Override public void run() { main.this.runOnUiThread(new Runnable() {
@Override public void run() { long millis = System.currentTimeMillis() - starttime; int seconds = (int) (millis / 1000); int minutes = seconds / 60; seconds = seconds % 60;
text2.setText(String.format("%d:%02d", minutes, seconds)); } }); } };
Timer timer = new Timer(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
//获取三个textfield控件以及一个button控件 text = (TextView)findViewById(R.id.text); text2 = (TextView)findViewById(R.id.text2); text3 = (TextView)findViewById(R.id.text3);
Button b = (Button)findViewById(R.id.button); b.setText("start"); //按钮绑定点击事件,开始或停止计时器 b.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) { Button b = (Button)v; if(b.getText().equals("stop")){//停止计时器 timer.cancel(); timer.purge(); h2.removeCallbacks(run); b.setText("start"); }else{//启动计时器 starttime = System.currentTimeMillis(); timer = new Timer(); timer.schedule(new firstTask(), 0,500); timer.schedule(new secondTask(), 0,500); h2.postDelayed(run, 0); b.setText("stop"); } } }); }
//当Activity进入后台时,停止计时器 @Override public void onPause() { super.onPause(); timer.cancel(); timer.purge(); h2.removeCallbacks(run); Button b = (Button)findViewById(R.id.button); b.setText("start"); } }

记住的主要事情是UI只能从主UI线程修改,所以使用Handler或activity.runOnUIThread(Runnable r)。

以下是我认为的首选方法。


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

public class TestActivity extends Activity {
TextView timerTextView; long startTime = 0;
//通过在可运行的结束处重新发布此处理程序而无需计时器 Handler timerHandler = new Handler(); Runnable timerRunnable = new Runnable() {
@Override public void run() { long millis = System.currentTimeMillis() - startTime; int seconds = (int) (millis / 1000); int minutes = seconds / 60; seconds = seconds % 60;
timerTextView.setText(String.format("%d:%02d", minutes, seconds));
timerHandler.postDelayed(this, 500); } };
//创建活动时调用 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.test_activity);
//获取计时器文本视图: timerTextView = (TextView) findViewById(R.id.timerTextView);
//获取按钮并设置其文字和单击侦听器: Button b = (Button) findViewById(R.id.button); b.setText("开始"); b.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) { Button b = (Button) v; if (b.getText().equals("停止")) { timerHandler.removeCallbacks(timerRunnable); b.setText("开始"); } else { startTime = System.currentTimeMillis(); timerHandler.postDelayed(timerRunnable, 0); b.setText("停止"); } } }); }
//当活动不再处于前台时调用 @Override public void onPause() { super.onPause(); timerHandler.removeCallbacks(timerRunnable); Button b = (Button)findViewById(R.id.button); b.setText("开始"); }
}

2
@Gautam 我认为上面所有的方法都差不多。我个人更喜欢上面描述的使用 run Runnable 和 h2 Handler 的 handler 方法,因为它是 Android 开发者网站推荐的方法,而且在我看来也是最优雅的。 - Dave.B
8
将您的首选方法与其他代码分开会更好。例如,您可以展示一个例子展示您的首选方式,再展示另外一个例子来展示其他替代方法。将这三种方法放在一起使得理解变得更加困难(尤其是对于像我这样的 Android 新手)。不过可能要求太高了 :) - Jesse Aldridge
5
好主意。我已经添加了仅使用首选方法的代码。 - Dave.B
1
@bluesm,老实说我没有想到,但是这个方法完全可行。 - Dave.B
3
给那些发明Java/Android的人一个提示:为什么你们让像计时器这样简单的事情变得如此困难。你会认为你可以有类似Timer().schedule(..代码块.., 毫秒);这样的东西,然后就完成了... - quemeful
显示剩余13条评论

99

很简单!你可以创建一个新的计时器。

Timer timer = new Timer();

然后你扩展计时器任务

class UpdateBallTask extends TimerTask {
   Ball myBall;

   public void run() {
       //calculate the new position of myBall
   }
}

然后使用一些更新间隔将新任务添加到计时器中。

final int FPS = 40;
TimerTask updateBall = new UpdateBallTask();
timer.scheduleAtFixedRate(updateBall, 0, 1000/FPS);

免责声明:这不是理想的解决方案。这是使用Timer类的解决方案(正如OP所请求的)。在Android SDK中,建议使用Handler类(已在接受的答案中提供示例)。


4
当然。原帖想要使用TimerTask实现这个功能,但我不建议在游戏中使用TimerTask。 - Kiril Kirilov
4
啥?原帖子里没有指明要如何完成。他们只是贴了一篇使用TimerTask的文章,但并没有要求按照那种方式来做。 - ToolmakerSteve
1
帮了很多忙,谢谢 @fiction - Naveed Ahmad
1
很棒的答案,易于理解。 - Maduro
1
因为这个诚实的免责声明,我投了一票支持这个。 - Jose_GD
显示剩余3条评论

73

如果你还需要在UI线程上运行代码(而不是定时器线程),请查看这篇博客:http://steve.odyfamily.com/?p=12

public class myActivity extends Activity {
private Timer myTimer;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.main);

    myTimer = new Timer();
    myTimer.schedule(new TimerTask() {          
        @Override
        public void run() {
            TimerMethod();
        }

    }, 0, 1000);
}

private void TimerMethod()
{
    //This method is called directly by the timer
    //and runs in the same thread as the timer.

    //We call the method that will work with the UI
    //through the runOnUiThread method.
    this.runOnUiThread(Timer_Tick);
}


private Runnable Timer_Tick = new Runnable() {
    public void run() {

    //This method runs in the same thread as the UI.               

    //Do something to the UI thread here

    }
};
}

为了完整起见,您可以提及如何停止计时器,以及可能的重新启动。 (我在这里找到了必要的信息:https://dev59.com/R2gu5IYBdhLWcg3wJDyb) - RenniePet
4
直接在TimerTask的run方法中调用runOnUIThread有什么原因不能这样做吗?似乎可以正常工作并且可以减少一层嵌套。 - RichieHH
当然,这只是一种教学方法,用于理解所有步骤。我建议使用这个标准来编写易读的代码。 - Meir Gerenstadt

53

如果只想安排一个倒计时,在未来的某个时间点定期获取通知,可以使用自 API 等级 1 起就可用的 CountDownTimer 类。

new CountDownTimer(30000, 1000) {
    public void onTick(long millisUntilFinished) {
        editText.setText("Seconds remaining: " + millisUntilFinished / 1000);
    }

    public void onFinish() {
        editText.setText("Done");
    }
}.start();

3
只有在你知道想让计时器在几次执行后消失时,CountDownTimer 才有意义。这不是典型的、也不是特别灵活的方法。更常见的是永久重复的计时器(在不再需要时取消),或者只运行一次的处理程序,如果将来再次需要,则会重新启动自身。请参见其他答案。 - ToolmakerSteve
1
你是完全正确的。从类名可以看出,它提供了一个一次性倒计时器,在完成之前会不断地进行滴答声,当然它在实现中使用了Handler。 - Ahmed Hegazy
如何显示毫秒?格式为SS:MiMi?谢谢。 - Ruchir Baronia
这个答案正是我所寻找的。 - Avinash

31

这是一个简单的计时器代码:

Timer timer = new Timer();
TimerTask t = new TimerTask() {       
    @Override
    public void run() {

        System.out.println("1");
    }
};
timer.scheduleAtFixedRate(t,1000,1000);

如果我们只想在凌晨4点运行该计时器对象,怎么办? - gumuruh

14

我认为你可以以 Rx 的方式完成它,例如:

 timerSubscribe = Observable.interval(1, TimeUnit.SECONDS)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Action1<Long>() {
                @Override
                public void call(Long aLong) {
                      //TODO do your stuff
                }
            });

并取消这个喜欢:

timerSubscribe.unsubscribe();

Rx计时器 http://reactivex.io/documentation/operators/timer.html


timerSubscribe 的数据类型是什么? - Ahmed Elsayed

13
我很惊讶没有人提到使用RxJava2解决方案。它非常简单,并提供了在Android中设置定时器的简便方法。
首先,如果您还没有这样做,需要设置Gradle依赖项:
implementation "io.reactivex.rxjava2:rxjava:2.x.y"

由于我们只有一个简单的、非重复的任务,我们可以使用Completable对象:

(将xy替换为current version number

Completable.timer(2, TimeUnit.SECONDS, Schedulers.computation())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(() -> {
            // Timer finished, do something...
        });

对于重复任务,您可以以类似的方式使用Observable

Observable.interval(2, TimeUnit.SECONDS, Schedulers.computation())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(tick -> {
            // called every 2 seconds, do something...
        }, throwable -> {
            // handle error
        });

Schedulers.computation() 确保我们的计时器在后台线程上运行,.observeOn(AndroidSchedulers.mainThread()) 表示计时器完成后我们要运行的代码将在主线程上执行。

为避免不必要的内存泄漏,在 Activity/Fragment 销毁时应确保取消订阅。


5
这是最简洁的方法! - Constantin
如何取消它们?即当用户在 UI 上按下 [STOP] 按钮并且 Completable 在执行之前被取消。 - Someone Somewhere
@SomeoneSomewhere 只需将 .subscribe() 方法返回的 Subscription 保存在变量中,然后在想要停止计时器时调用 subscription.unsubscribe() 即可。 - Micer

12
因为这个问题仍然吸引着许多来自谷歌搜索(关于Android计时器)的用户,所以我想发表我的看法。
首先,Timer类将在Java 9中被弃用(请阅读被接受的答案)官方建议使用ScheduledThreadPoolExecutor,它比Timer类更加有效和功能丰富,可以在给定延迟后调度命令运行,或者定期执行。此外,它还提供了ThreadPoolExecutor的额外灵活性和功能。
以下是使用普通功能的示例。
  1. Create executor service:

    final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1);
    
  2. Just schedule you runnable:

    final Future<?> future = SCHEDULER.schedule(Runnable task, long delay,TimeUnit unit);
    
  3. You can now use future to cancel the task or check if it is done for example:

    future.isDone();
    
希望这对于在Android中创建任务有所帮助。
完整示例:
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Future<?> sampleFutureTimer = scheduler.schedule(new Runnable(), 120, TimeUnit.SECONDS);
if (sampleFutureTimer.isDone()){
    // Do something which will save world.
}

8

对于想要使用 Kotlin 实现此操作的人:

val timer = fixedRateTimer(period = 1000L) {
            val currentTime: Date = Calendar.getInstance().time
            runOnUiThread {
                tvFOO.text = currentTime.toString()
            }
        }

停止计时器可以使用以下代码:
timer.cancel()

这个函数还有许多其他选项,试试看吧。


4
import kotlin.concurrent.fixedRateTimer

val timer = fixedRateTimer("Tag", false, 1000, 2500) { /* Your code here */ }

使用 Kotlin 很简单


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