【问题标题】:How to run a Runnable thread in Android at defined intervals?如何在定义的时间间隔在 Android 中运行 Runnable 线程?
【发布时间】:2009-12-17 12:42:03
【问题描述】:

我开发了一个应用程序以在 Android 模拟器屏幕上以定义的时间间隔显示一些文本。我正在使用Handler 类。这是我的代码中的一个 sn-p:

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

当我运行这个应用程序时,文本只显示一次。为什么?

【问题讨论】:

  • 我永远不记得如何做一个可运行的,所以我总是访问你关于如何做的帖子:))
  • lambdas 是现在大部分时间要走的路;)

标签: android multithreading


【解决方案1】:

您的示例的简单修复是:

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

【讨论】:

  • Alex,我有一个小疑问。现在线程运行正常并且连续显示文本,如果我想停止这意味着我必须做什么?请帮助我。
  • 你可以定义布尔变量_stop,当你想停止时设置为'true'。并将 'while(true)' 更改为 'while(!_stop)',或者如果使用了第一个示例,只需更改为 'if(!_stop) handler.postDelayed(this, 1000)'。
  • 如果你想确保 Handler 会附加到主线程,你应该像这样初始化它: handler = new Handler(Looper.getMainLooper());
  • @alex2k8 不应该是第二个sn-p中的handler.post(r)而不是handler.post(this)吗?此外,如果您想将解决方案与 Thread 一起使用,您还必须从 Runnable r 中删除 handler.postDelayed(this, 1000),因为延迟已经在 Thread 对象的 run() 方法内部执行。
  • @alex2k8 - 嗨,兄弟!请告诉我如何在运行时手动停止第二个 sn-p Thread
【解决方案2】:
new Handler().postDelayed(new Runnable() {
    public void run() {
        // do something...              
    }
}, 100);

【讨论】:

  • 如果你想确保 Handler 会附加到主线程,你应该像这样初始化它: new Handler(Looper.getMainLooper());
  • 这个解决方案不是和原帖一样吗?它只会在 100 毫秒后运行 Runnable 一次。
【解决方案3】:

我认为可以改进 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");
}

【讨论】:

  • -1 您假设您在 run() 中执行的任务是每次运行的成本恒定,如果这是对动态数据的操作(通常是这样),那么您最终会得到一次发生多个 run() 。这就是为什么 postDelayed 通常放在最后。
  • @Jay 不幸的是你错了。处理程序与单个线程(以及作为该线程的运行方法的 Looper)+ 消息队列相关联。每次您发布消息时,您都会将其排入队列,并且下次循环器检查队列时,它会执行您发布的 Runnable 的 run 方法。由于这一切都发生在一个线程中,因此您不能同时执行超过 1 个线程。同样先执行 postDelayed 将使您更接近每次执行 1000 毫秒,因为在内部它使用当前时间 + 1000 作为执行时间。如果您在帖子之前添加代码,则会增加额外的延迟。
  • @zapl 感谢有关处理程序的提示,我认为它将执行多个可运行对象,因此执行多个线程。虽然在内部,当运行持续时间小于或等于 1000 毫秒时,诸如 if ((currenttime - lastruntime)>1000) 之类的条件将正常工作,但是,当超过此时间时,定时器肯定会以非线性间隔发生完全取决于 run 方法的执行时间(因此我的观点是不可预测的计算费用)
  • 如果你想要一个固定的时间,没有冲突,在工作之前测量开始时间,并相应地调整你的延迟。如果 cpu 繁忙,您仍然会看到一点延迟,但它可以让您有更严格的时间,并检测系统是否过载(可能是低优先级的信号退出)。
【解决方案4】:

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

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,我如何暂停/恢复并永久停止?
  • 像 Handler handler = new Handler() 一样创建 Handler 的实例,并像 handler.removeCallbacksAndMessages(null) 一样删除它;
【解决方案5】:

我相信对于这种典型情况,即以固定间隔运行某些东西,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 不为空)

【讨论】:

  • 我不相信计时器更合适,因为它不考虑android生命周期。当您暂停和恢复时,无法保证计时器将正确运行。我认为 runnable 是更好的选择。
  • 这是否意味着当应用程序进入后台时,处理程序将被暂停?当它重新集中注意力时,它会继续(或多或少)就好像什么都没发生过一样?
【解决方案6】:
Handler handler=new Handler();
Runnable r = new Runnable(){
    public void run() {
        tv.append("Hello World");                       
        handler.postDelayed(r, 1000);
    }
}; 
handler.post(r);

【讨论】:

  • 这应该给出一个错误。在您的第二行中,您正在调用尚未定义的变量 r
  • 如果你想确保 Handler 会附加到主线程,你应该像这样初始化它: handler = new Handler(Looper.getMainLooper());
  • 只是重复回答!
【解决方案7】:

科特林

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

Java

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

【讨论】:

  • 我们如何在 Runnable 中执行其他函数?
【解决方案8】:

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

使 Runnable r 添加到消息队列中。 runnable 将在附加此处理程序的线程上运行。

所以@alex2k8 提供的示例,即使工作正常,也不相同。 如果使用Handler.post(),则不会创建新线程。您只需将Runnable 发布到Handler 的线程,由EDT 执行。 之后,EDT 只执行Runnable.run(),没有别的。

记住: Runnable != Thread.

【讨论】:

  • 确实如此。永远不要每次都创建新线程。 Handler 和其他执行池的全部意义在于让一个或两个线程从队列中拉出任务,以避免线程创建和 GC。如果您的应用程序确实存在漏洞,额外的 GC 可能有助于掩盖 OutOfMemory 情况,但在这两种情况下更好的解决方案是避免创建超出您需要的工作量。
  • 那么更好的方法是使用基于 alex2k8 答案的普通线程?
【解决方案9】:

Kotlin 与协程

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

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

试试看here

【讨论】:

    【解决方案10】:

    一个有趣的例子是你可以连续看到一个计数器/秒表在单独的线程中运行。还显示 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();
    }
    

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

    Thread example displaying GPS Location and Current Time runnable alongside main-activity's User Interface Thread

    【讨论】:

    • 提示:如果你想让你的答案有用 - 在此处了解如何格式化输入。 preview 窗口的存在是有原因的。
    【解决方案11】:

    现在在 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
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-09
      相关资源
      最近更新 更多