如何在Android中延迟调用一个方法

908

我希望能在指定的延迟时间后调用下面的方法。 在Objective-C中,有一个类似于以下代码的方法:

[self performSelector:@selector(DoSomething) withObject:nil afterDelay:5];

在安卓Java中是否有此方法的等效方法?例如,我需要在5秒后调用一个方法。

public void DoSomething()
{
     //do something here
}
36个回答

2188

Kotlin

Handler(Looper.getMainLooper()).postDelayed({
    //Do something after 100ms
}, 100)

Java

final Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something after 100ms
    }
}, 100);

需要导入的类是 android.os.handler


113
该解决方案仅在UI线程上有用。否则,在普通线程上,您需要实现Looper,但我认为这不是最佳版本。 - olivier_sdg
3
为什么您需要实现“looper”? - djechlin
41
一个 Handler 必须始终与一个 Looper 相关联,后者将实际处理您 post() 的 Runnable。UI 线程已经带有一个 Looper,因此您可以在 UI 线程上创建一个新的 Handler(),并直接将 Runnables post() 到它上面。这些 Runnables 在 UI 线程上执行。要使 Runnables 在另一个线程上执行,需要创建一个新线程,然后调用 Looper.prepare() 方法、创建一个新的 Handler() 和调用 Looper.loop() 方法。任何 post() 到该新 Handler 上的 Runnables 都将在该新线程上执行。如果不执行这些步骤,post() 将抛出异常。 - Dororo
14
如果需要的话,只要Runnable仍在消息队列中,你也可以通过在Handler上调用removeCallbacks(Runnable r)取消执行。 - Dennis
2
我是这样使用它的:new Handler().postDelayed(() -> { //100毫秒后执行某些操作 }, 100); - Choletski
显示剩余15条评论

372

在我的情况下,我无法使用其他任何答案。我使用了本地的Java定时器。

new Timer().schedule(new TimerTask() {          
    @Override
    public void run() {
        // this code will be executed after 2 seconds       
    }
}, 2000);

48
这比使用 Handler 的方式更好,因为当 Handler 不在 UI 线程上运行时,它没有 Looper 问题。 - Ben H
33
根据安卓文档,当不再需要计时器时,您应该保留对计时器的引用以便取消它:“当计时器不再需要时,用户应该调用cancel(),这将释放计时器的线程和其他资源。未经明确取消的计时器可能会无限期地持有资源。” - Pooks
18
注意!这不能在UI线程上运行。在UI线程上运行会导致致命错误:android.view.ViewRootImpl$CalledFromWrongThreadException: 只有创建视图层次结构的原始线程才能触摸其视图。 - vovahost
15
@vovahost这只是因为你在计时器块内更新了UI组件。 - Tim
10
请注意,java.util.Timer(和TimerTask)将在JDK 9中被弃用。 TimerTask为任务创建新的线程,这不是很好。 - Varvara Kalinina
显示剩余6条评论

188

注意: 当时问题没有指定Android作为上下文,因此给出的这个答案并不特指于Android UI线程。有关特定于Android的UI线程的答案,请在这里查看


看起来Mac OS API允许当前线程继续执行,并安排任务以异步方式运行。在Java中,等效的函数由java.util.concurrent包提供。我不确定Android可能会施加什么限制。

private static final ScheduledExecutorService worker = 
  Executors.newSingleThreadScheduledExecutor();

void someMethod() {
  ⋮
  Runnable task = new Runnable() {
    public void run() {
      /* Do something… */
    }
  };
  worker.schedule(task, 5, TimeUnit.SECONDS);
  ⋮
}

3
这对我从未调用Runnable。 - Ky -
14
顺便说一下:这也允许您稍后取消任务,在某些情况下可能会有帮助。只需存储对worker.schedule()返回的ScheduledFuture <?>的引用,并调用其cancel(boolean)方法即可。 - Dennis
5
@beetree 是 ScheduledExecutorService 中的一个方法。 - erickson
4
如果涉及UI线程对象,则此方法无效,你必须调用runOnUIThread(new runnable(){ run()....}); 或者在run(){}内部使用handler对象发布一个runnable。 - Jayant Arora
我可以使用这种方法来在点击某个项目时获得触摸反馈吗?例如,view.setColor(some_color),然后在计划程序中在x秒后将此颜色删除...? - eRaisedToX
显示剩余4条评论

117

在5秒后在UI线程执行某个操作:

new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something here
    }
}, 5000);

8
确认,这是防止调用looper.prepare并将整个过程放入UI线程的最佳解决方案。 - Tobliug
谢谢,这帮助我解决了Looper的问题 :) - Tia
1
我会小心地在主线程上创建处理程序,因为在这个线程中不应该执行长时间的任务。 - Shayan_Aryan

54

KotlinJava多种方式

1. 使用Handler

Handler().postDelayed({
    TODO("Do something")
    }, 2000)

2. 使用 TimerTask

Timer().schedule(object : TimerTask() {
    override fun run() {
        TODO("Do something")
    }
}, 2000)

甚至可以更短

Timer().schedule(timerTask {
    TODO("Do something")
}, 2000)

最短的方式是:

Timer().schedule(2000) {
    TODO("Do something")
}

3. 使用 Executors

Executors.newSingleThreadScheduledExecutor().schedule({
    TODO("Do something")
}, 2, TimeUnit.SECONDS)

在Java中

1. 使用Handler

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something
    }
}, 2000);

2. 使用 Timer

new Timer().schedule(new TimerTask() {          
    @Override
    public void run() {
        // Do something
    }
}, 2000);

3. 使用 ScheduledExecutorService

private static final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();

Runnable runnable = new Runnable() {
  public void run() {
      // Do something
  }
  };
worker.schedule(runnable, 2, TimeUnit.SECONDS);

1
@JanRabe 感谢您的建议。我很感激。然而问题是“如何在Android中延迟调用方法”。所以我专注于这个问题。直截了当地说。否则,Java泄漏是一个需要开发人员单独理解的大主题。 - Khemraj Sharma
1
Handler().postDelayed({ }, 2000) 显示为已弃用。 - Tim

45
可以在UIThread内部使用Handler:
runOnUiThread(new Runnable() {
                
    @Override
    public void run() {
        new Handler().postDelayed(new Runnable() {
           @Override
           public void run() {
               //add your code here
          }
        }, 1000);           
    }
});

40

感谢所有的好答案,我找到了最适合我的解决方案。

Handler myHandler = new DoSomething();
Message m = new Message();
m.obj = c;//passing a parameter here
myHandler.sendMessageDelayed(m, 1000);

class DoSomething extends Handler {
    @Override
    public void handleMessage(Message msg) {
      MyObject o = (MyObject) msg.obj;
      //do something here
    }
}

我可以使用这种方法在点击某个项目时获得触摸反馈吗?例如,view.setColor(some_color),然后在x秒后的处理程序中删除此颜色...? - eRaisedToX

27

更安全 - 使用 Kotlin Coroutine

大多数答案使用 Handler,但我提供了一种不同的解决方案来处理活动、片段和视图模型中的延迟,使用 Android 生命周期扩展。 这种方法在生命周期销毁时会自动取消,避免内存泄漏或应用崩溃

在活动或片段中:

lifecycleScope.launch { 
  delay(DELAY_MS)
  doSomething()
}

在ViewModel中:

viewModelScope.lanch {
  delay(DELAY_MS)
  doSomething()
}

在挂起函数中:(Kotlin协程)
suspend fun doSomethingAfter(){
    delay(DELAY_MS)
    doSomething()
}

如果你在使用lifecycleScope时出现找不到的错误!-请将此依赖项导入应用程序的 gradle 文件中:

implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"

3
我认为协程方法更好,特别是在范围被限制在具有生命周期的组件活动、片段、自定义组件时。大多数情况下的要求是在主机存活期间执行某种方法。我还建议获取Job实例以支持逻辑取消。 例如: val job = scope.launch {....} .... .... // 在执行前取消延迟任务 job.cancel() - S.Javed

23

请看这个演示:

import java.util.Timer;
import java.util.TimerTask;

class Test {
     public static void main( String [] args ) {
          int delay = 5000;// in ms 

          Timer timer = new Timer();

          timer.schedule( new TimerTask(){
             public void run() { 
                 System.out.println("Wait, what..:");
              }
           }, delay);

           System.out.println("Would it run?");
     }
}

21

如果你必须使用 Handler,但是却在另一个线程中,你可以使用 runonuithread 在 UI 线程中运行 Handler。这样可以避免出现需要调用 Looper.Prepare() 抛出的异常。

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //Do something after 1 second
            }
        }, 1000);
    }
});

看起来很凌乱,但这是其中一种方法。


4
可以,这段话的意思是:这个方法可以正常工作,但是由于 Stack Overflow 规定编辑时至少要有 6 个字符,所以我无法编辑你的帖子。不过,在 'new Handler' 后面缺少一对括号,正确的写法应该是 'new Handler()'。请注意修改。 - Jonathan Muller
2
不要把所有东西都放在UI线程中,你可以这样做: new Handler(Looper.getMainLooper()) - Tobliug

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