如何按定义的时间间隔在Android中运行可运行线程?


351

我开发了一个应用程序,用于在Android模拟器屏幕中按定义的间隔显示一些文本。我正在Handler上课。这是我的代码片段:

handler = new Handler();
Runnable r = new Runnable() {
    public void run() {
        tv.append("Hello World");               
    }
};
handler.postDelayed(r, 1000);

当我运行此应用程序时,该文本仅显示一次。为什么?


109
我永远都不记得该怎么做,所以我总是去看你的文章:))
Adrian Sicaru

2
哈哈,这里的伴侣是如此真实
NoXSaeeD

1
lambdas是现在大多数时候都应该走的路;)
Xerus

@AdrianSicaru:相同
Leng Sovandara

Answers:


534

您的示例的简单解决方法是:

handler = new Handler();

final Runnable r = new Runnable() {
    public void run() {
        tv.append("Hello World");
        handler.postDelayed(this, 1000);
    }
};

handler.postDelayed(r, 1000);

或者我们可以使用普通线程(例如原始Runner):

Thread thread = new Thread() {
    @Override
    public void run() {
        try {
            while(true) {
                sleep(1000);
                handler.post(this);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};

thread.start();

您可以将可运行对象视为可以发送到消息队列以执行的命令,而将处理程序视为仅用于发送该命令的辅助对象。

更多详细信息请参见http://developer.android.com/reference/android/os/Handler.html


亚历克斯,我有一个小疑问。现在线程正在正常运行并连续显示文本,如果我想停止这意味着我必须要做的事情?请帮助我。
拉贾潘迪安

11
您可以定义布尔变量_stop,并在要停止时将其设置为“ true”。并将'while(true)'更改为'while(!_ stop)',或者如果使用了第一个示例,则只需更改为'if(!_ stop)handler.postDelayed(this,1000)'。
alex2k8

如果我想重新启动消息怎么办?
Sonhja 2011年

如果我需要一个runnable来将8个不同的ImageView依次设置为可见,然后以相同的方式将它们全部设置为不可见,依此类推(以创建“闪烁”动画),我该怎么做?
Droidman

1
如果要确保将Handler附加到主线程,则应按以下方式对其进行初始化:handler = new Handler(Looper.getMainLooper());
Yair Kukielka,2015年

47
new Handler().postDelayed(new Runnable() {
    public void run() {
        // do something...              
    }
}, 100);

2
如果要确保将Handler附加到主线程,则应按以下方式对其进行初始化:new Handler(Looper.getMainLooper());
Yair Kukielka,2015年

1
这个解决方案不等于原始帖子吗?它只会在100毫秒后运行一次Runnable。
特曼

@YairKukielka的答案是解决方案!您需要附加MainLooper。如此救命的人!
Houssem Chlegou

40

我认为可以改善Alex2k8的第一个解决方案,以便每秒更新一次

1.原代码:

public void run() {
    tv.append("Hello World");
    handler.postDelayed(this, 1000);
}

2.分析

  • 在上述成本中,假设tv.append("Hello Word")成本T毫秒,显示500次后的延迟时间为500 * T毫秒
  • 长时间运行会增加延迟

3.解决方案

为了避免这种情况,只需更改postDelayed()的顺序,以避免延迟:

public void run() {
    handler.postDelayed(this, 1000);
    tv.append("Hello World");
}

6
-1假设您在run()中执行的任务每次运行的成本是恒定的,如果这是对动态数据进行的操作(通常是这样),那么最终将在多个run()处发生一旦。这就是为什么postDelayed通常放在末尾的原因。
杰伊

1
@Jay不幸的是你错了。处理程序与单个线程(和Looper(该线程的运行方法)和一个MessageQueue)关联。每次发布消息时,它都会排队,而下次循环程序检查队列时,它将执行所发布的Runnable的run方法。由于这一切都是在一个线程中发生的,因此您不能同时执行多个线程。同样先执行postDelayed会使每次执行的时间接近1000ms,因为内部它使用当前时间+ 1000作为执行时间。如果您在发布之前放置代码,则会增加其他延迟。
zapl 2012年

1
@zapl感谢您提供有关处理程序的技巧,我认为它将执行多个可运行对象,因此将执行多个线程。但是在内部,当运行持续时间小于或等于1000ms时,诸如((currenttime-lastruntime)> 1000)之类的条件会很好地工作,但是,如果超过此时间,则肯定会以非线性间隔发生计时器完全取决于运行方法的执行时间(因此,我的观点是不可预测的计算费用)
Jay

如果您想要一个固定的时间,没有冲突,请在开始工作之前测量开始时间,并相应地调整延迟时间。如果cpu忙,您仍然会看到一点延迟,但是它可以让您有更严格的时间,并检测系统是否过载(可能表示低优先级的东西要退出)。
Ajax

27

对于重复任务,您可以使用

new Timer().scheduleAtFixedRate(task, runAfterADelayForFirstTime, repeaingTimeInterval);

称它为

new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {

            }
        },500,1000);

上面的代码将在半秒(500)第一次运行,并在每秒(1000)之后重复一次

哪里

任务是要执行的方法

初始执​​行时间 之后

间隔重复执行的时间)

其次

如果要执行一个Task次数,也可以使用CountDownTimer

    new CountDownTimer(40000, 1000) { //40000 milli seconds is total time, 1000 milli seconds is time interval

     public void onTick(long millisUntilFinished) {
      }
      public void onFinish() {
     }
    }.start();

//Above codes run 40 times after each second

您也可以使用runnable来做到这一点。创建一个像

Runnable runnable = new Runnable()
    {
        @Override
        public void run()
        {

        }
    };

并以两种方式调用它

new Handler().postDelayed(runnable, 500 );//where 500 is delayMillis  // to work on mainThread

要么

new Thread(runnable).start();//to work in Background 

对于选项3,我该如何暂停/恢复并永久停止?
2016年

创建一个类似于Handler handler = new Handler()的Handler实例,并像handler.removeCallbacksAndMessages(null);一样删除它。
Zar E Ahmer '16

24

我认为对于这种典型情况,即以固定的间隔运行某些内容,Timer更为合适。这是一个简单的示例:

myTimer = new Timer();
myTimer.schedule(new TimerTask() {          
@Override
public void run() {
    // If you want to modify a view in your Activity
    MyActivity.this.runOnUiThread(new Runnable()
        public void run(){
            tv.append("Hello World");
        });
    }
}, 1000, 1000); // initial delay 1 second, interval 1 second

使用Timer具有以下优点:

  • 初始延迟和间隔可以在schedule函数参数中轻松指定
  • 只需调用即可停止计时器 myTimer.cancel()
  • 如果只想运行一个线程,请记住myTimer.cancel() 安排新线程之前先调用(如果myTimer不为null)

7
我不相信计时器更合适,因为它不考虑android的生命周期。当您暂停并继续时,没有保证计时器将正确运行。我认为,奔跑是更好的选择。
Janpan 2014年

1
这是否意味着当应用程序置于后台时,处理程序将被暂停?当它重新获得焦点时,它将继续(或多或少)好像什么都没有发生?
Andrew Gallasch 2015年

17
Handler handler=new Handler();
Runnable r = new Runnable(){
    public void run() {
        tv.append("Hello World");                       
        handler.postDelayed(r, 1000);
    }
}; 
handler.post(r);

5
这应该给出一个错误。在第二行中,您将调用r尚未定义的变量。
2014年

如果要确保将Handler附加到主线程,则应按以下方式对其进行初始化:handler = new Handler(Looper.getMainLooper());
Yair Kukielka,2015年

只是重复回答!
哈米德

如何使用imageview click暂停/恢复可运行对象?
2013年

4

如果我正确理解Handler.post()方法的文档:

使Runnable r添加到消息队列中。可运行对象将在此处理程序所连接的线程上运行。

因此,即使运行正常,@ alex2k8提供的示例也不相同。如果Handler.post()使用,则不会创建新线程。您只需Runnable将其发布到Handler要由EDT执行的线程。此后,EDT仅执行Runnable.run(),而没有其他执行。

请记住: Runnable != Thread


1
诚然。永远不要每次都创建新线程。Handler和其他执行池的重点是让一个或两个线程将任务拖出队列,以避免线程创建和GC。如果您的应用程序确实泄漏,则额外的GC可能有助于掩盖内存不足的情况,但是在两种情况下,更好的解决方案是避免创建过多的工作。
Ajax

因此,更好的方法是使用基于alex2k8答案的普通线程?
Compaq LE2202x

4

科特林

private lateinit var runnable: Runnable
override fun onCreate(savedInstanceState: Bundle?) {
    val handler = Handler()
    runnable = Runnable {
        // do your work
        handler.postDelayed(runnable, 2000)
    }
    handler.postDelayed(runnable, 2000)
}

爪哇

Runnable runnable;
Handler handler;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    handler = new Handler();
    runnable = new Runnable() {
        @Override
        public void run() {
            // do your work
            handler.postDelayed(this, 1000);
        }
    };
    handler.postDelayed(runnable, 1000);
}

1

一个有趣的示例是,您可以不断看到计数器/秒表在单独的线程中运行。同时显示GPS位置。主要活动用户界面线程已存在。

摘抄:

try {    
    cnt++; scnt++;
    now=System.currentTimeMillis();
    r=rand.nextInt(6); r++;    
    loc=lm.getLastKnownLocation(best);    

    if(loc!=null) { 
        lat=loc.getLatitude();
        lng=loc.getLongitude(); 
    }    

    Thread.sleep(100); 
    handler.sendMessage(handler.obtainMessage());
} catch (InterruptedException e) {   
    Toast.makeText(this, "Error="+e.toString(), Toast.LENGTH_LONG).show();
}

要查看代码,请参见此处:

显示与主活动的用户界面线程一起运行的GPS位置和当前时间的线程示例


1
提示:如果您想使答案有用,请在此处了解如何格式化输入。该预览窗口存在是有原因的。
GhostCat

0

现在在Kotlin中,您可以通过以下方式运行线程:

class SimpleRunnable: Runnable {
    public override fun run() {
        println("${Thread.currentThread()} has run.")
    }
}
fun main(args: Array<String>) {
    val thread = SimpleThread()
    thread.start() // Will output: Thread[Thread-0,5,main] has run.
    val runnable = SimpleRunnable()
    val thread1 = Thread(runnable)
    thread1.start() // Will output: Thread[Thread-1,5,main] has run
}

0

Kotlin与协程

在Kotlin中,可以使用协程执行以下操作:

CoroutineScope(Dispatchers.Main).launch { // Main, because UI is changed
    ticker(delayMillis = 1000, initialDelayMillis = 1000).consumeEach {
        tv.append("Hello World")
    }
}

在这里尝试!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.