在Android中延迟后如何调用方法


769

我希望能够在指定的延迟后调用以下方法。在目标c中,有类似以下内容:

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

android和java中的此方法是否等效?例如,我需要能够在5秒钟后调用一个方法。

public void DoSomething()
{
     //do something here
}

Answers:


1858

科特林

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


爪哇

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



108
此解决方案仅在UI线程上有用。否则,在普通线程上,您需要实现不是我认为最好的版本的looper
olivier_sdg 2013年

2
@olivier_sdg为什么需要实现循环程序?
djechlin

37
@djechlin必须始终将处理程序链接到Looper,该Looper实际上将处理您发布的Runnable()。UI线程已经附带了Looper,因此您只需在UI线程上创建一个新的Handler()并将Runnables直接发布到该线程即可。这些可运行对象在UI线程上执行。要使Runnables在另一个线程上执行,您需要创建一个新线程,然后创建Looper.prepare(),创建一个新的Handler(),然后创建Looper.loop()。发布到此新处理程序的所有Runnable将在此新线程上执行。如果不执行所有这些操作,则post()将引发异常。
Dororo

12
如果需要,您还可以通过调用取消执行,只要Runnable仍在消息队列removeCallbacks(Runnable r)中即可Handler
丹尼斯

9
应该import android.os.handler
KaKa

322

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

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

43
这比使用Handler的更好,因为当Handler不在UI线程上运行时,它没有Looper问题。
2013年

32
您应该保留对计时器的引用,以便在不再需要它时取消它,因为根据Android文档:“当不再需要计时器时,用户应该调用cancel(),这将释放计时器的线程和其他资源。未明确取消的计时器可能无限期地占用资源。”
ook

14
注意!这不能在UI线程上运行。在ui线程上运行此代码会导致致命错误:android.view.ViewRootImpl $ CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能触摸其视图。
vovahost

13
@vovahost这仅是因为您正在更新计时器块内的UI组件
Tim

10
请注意,在JDK 9中将不推荐使用java.util.Timer(和TimerTask)。TimerTask为不太好的任务创建新的线程。
Varvara Kalinina

183

注意:当问题未将Android指定为上下文时,将给出此答案。对于特定于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
Supuhstar 2014年

14
附带说明:这还允许您稍后取消任务,这在某些情况下可能会有所帮助。只需存储对ScheduledFuture<?>return by 的引用worker.schedule()并调用其cancel(boolean)方法。
丹尼斯

我认为这个答案已经过时了。.schedule似乎不再是Runnable的方法...?:/
beetree

5
@beetree这是一个方法ScheduledExecutorService
埃里克森

3
如果涉及ui线程对象,这将不起作用,您必须调用runOnUIThread(new runnable(){run()....}); 或在run(){}内部发布使用处理程序对象的可运行对象
Jayant Arora

107

要在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
我会在主
循环

40

您可以在UIThread中使用Handler:

runOnUiThread(new Runnable() {

    @Override
    public void run() {
         final Handler handler = new Handler();
         handler.postDelayed(new Runnable() {
           @Override
           public void run() {
               //add your code here
           }
         }, 1000);

    }
});

36

感谢所有出色的答案,我找到了最适合我需要的解决方案。

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秒后在Handler中删除此颜色...?
eRaisedToX

24

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感谢您的建议。我请。但是问题是How to call a method after a delay in Android。因此,我对此表示关注。要点。否则,java泄漏是开发人员需要单独理解的一个重要主题。
Khemraj

20

观看此演示:

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

20

如果必须使用处理程序,但是又进入另一个线程,则可以使用runonuithread它在UI线程中运行。这将使您免于引发的异常要求致电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
这行得通,由于愚蠢的SO规则(至少要编辑6个字符),我无法编辑您的帖子,但是在“新处理程序”之后缺少“()”,应该是“新处理程序()”
乔纳森·穆勒

2
您可以执行以下操作,而不是将所有内容放入UI线程中:new Handler(Looper.getMainLooper())
Tobliug

17

我更喜欢使用View.postDelayed()方法,下面是简单的代码:

mView.postDelayed(new Runnable() {
    @Override
    public void run() {
        // Do something after 1000 ms
    }
}, 1000);

1
它不会冻结ui元素本身,因为它将在视图处理程序中调度吗?
JacksOnF1re 2015年

1
不,发布的任务将在1秒钟内执行,但是在此第二UI线程期间,它将执行其他有用的工作
demaksee '18

14

这是我最短的解决方案:

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

10
final Handler handler = new Handler(); 
Timer t = new Timer(); 
t.schedule(new TimerTask() { 
    public void run() { 
        handler.post(new Runnable() { 
            public void run() { 
                //DO SOME ACTIONS HERE , THIS ACTIONS WILL WILL EXECUTE AFTER 5 SECONDS...
            }
        }); 
    } 
}, 5000); 

10

如果您使用的是Android Studio 3.0及更高版本,则可以使用lambda表达式。callMyMethod()2秒后调用该方法:

new Handler().postDelayed(() -> callMyMethod(), 2000);

如果您需要取消延迟的可运行对象,请使用以下方法:

Handler handler = new Handler();
handler.postDelayed(() -> callMyMethod(), 2000);

// When you need to cancel all your posted runnables just use:
handler.removeCallbacksAndMessages(null);

我们如何取消呢?
Damia Fuentes

我很惊讶这里有多少人会愉快地迁移到Kotlin上,却完全忽略了标准Java的Lambda表达式。
TomDK '19

6

我建议使用Timer,它允许您安排要在特定时间间隔内调用的方法。这不会阻止您的UI,并在执行该方法时使您的应用保持共振。

另一个选择是wait();。方法,这将在指定的时间长度内阻止当前线程。如果在UI线程上执行此操作,将导致UI停止响应。


2
Thread.sleep()比Object.wait()更好。等待表示您希望收到通知并正在围绕某些活动进行同步。睡眠表明您只是希望在指定的时间内不执行任何操作。如果您希望动作在以后的某个时间点异步发生,则可以使用计时器。
Tim Bender 2010年

1
那是真实的。那就是为什么我将其列为另一种选择;-)
内特(Nate)2010年

6

对于简单的行句柄发布延迟,可以执行以下操作:

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

我希望这有帮助


5

您可以将其用于最简单的解决方案:

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        //Write your code here
    }
}, 5000); //Timer is in ms here.

否则,下面可以是另一个干净有用的解决方案:

new Handler().postDelayed(() -> 
{/*Do something here*/}, 
5000); //time in ms


5

所以这里有几件事情要考虑,因为有很多方法可以给这只猫换皮。尽管答案已经全部选定。我认为重要的是要使用适当的编码指南重新进行研究,以避免任何人仅由于“多数选择的简单答案”而走错了方向。

因此,首先让我们讨论简单的“延迟发布”答案,这是该主题中获胜者整体选择的答案。

需要考虑的几件事。发布延迟之后,您可能会遇到内存泄漏,死对象,已经消失的生命周期等。因此,正确处理它也很重要。您可以通过两种方法来完成此操作。

为了现代发展,我将在KOTLIN中提供

这是在回调上使用UI线程并确认您的活动在命中回调时仍然有效的简单示例。

  Handler(Looper.getMainLooper()).postDelayed({
            if(activity != null && activity?.isFinishing == false){
                txtNewInfo.visibility = View.GONE
            }
        }, NEW_INFO_SHOW_TIMEOUT_MS)

但是,这仍然不是很完美,因为如果活动消失,则没有理由打回您的回调。因此更好的方法是保留对它的引用,并删除这样的回调。

    private fun showFacebookStylePlus1NewsFeedOnPushReceived(){
        A35Log.v(TAG, "showFacebookStylePlus1NewsFeedOnPushReceived")
        if(activity != null && activity?.isFinishing == false){
            txtNewInfo.visibility = View.VISIBLE
            mHandler.postDelayed({
                if(activity != null && activity?.isFinishing == false){
                    txtNewInfo.visibility = View.GONE
                }
            }, NEW_INFO_SHOW_TIMEOUT_MS)
        }
    }

并且当然要在onPause上进行清理,以免打回回调。

    override fun onPause() {
        super.onPause()
        mHandler.removeCallbacks(null)
    }

既然我们已经讨论了显而易见的内容,那么让我们来谈谈使用现代协程和kotlin的更清洁的选择:)。如果您还没有使用它们,那么您真的会错过。

   fun doActionAfterDelay() 
        launch(UI) {
            delay(MS_TO_DELAY)           
            actionToTake()
        }
    }

或者,如果您希望始终使用该方法启动UI,则可以执行以下操作:

  fun doActionAfterDelay() = launch(UI){ 
      delay(MS_TO_DELAY)           
      actionToTake()
  }

当然,就像PostDelayed一样,您必须确保处理取消,因此您可以在延迟调用之后进行活动检查,也可以像其他路线一样在onPause中取消它。

var mDelayedJob: Job? = null
fun doActionAfterDelay() 
   mDelayedJob = launch(UI) {
            try {
               delay(MS_TO_DELAY)           
               actionToTake()
            }catch(ex: JobCancellationException){
                showFancyToast("Delayed Job canceled", true, FancyToast.ERROR, "Delayed Job canceled: ${ex.message}")
            }
        }
   }
}

//处理清理

override fun onPause() {
   super.onPause()
   if(mDelayedJob != null && mDelayedJob!!.isActive) {
      A35Log.v(mClassTag, "canceling delayed job")
      mDelayedJob?.cancel() //this should throw CancelationException in coroutine, you can catch and handle appropriately
   }
}

如果将launch(UI)放入方法签名中,则可以在代码的调用行中分配作业。

因此,故事的寓意是为了确保您的延迟动作是安全的,请确保您删除了回调或取消了工作,当然要确认您有正确的生命周期来触摸延迟回调中已完成的项目。协程还提供可取消的动作。

同样值得注意的是,您通常应该处理协程可能附带的各种异常。例如,取消,异常,超时,无论您决定使用什么。如果您决定真正开始使用协程,这是一个更高级的示例。

   mLoadJob = launch(UI){
            try {
                //Applies timeout
                withTimeout(4000) {
                    //Moves to background thread
                    withContext(DefaultDispatcher) {
                        mDeviceModelList.addArrayList(SSDBHelper.getAllDevices())
                    }
                }

                //Continues after async with context above
                showFancyToast("Loading complete", true, FancyToast.SUCCESS)
            }catch(ex: JobCancellationException){
                showFancyToast("Save canceled", true, FancyToast.ERROR, "Save canceled: ${ex.message}")
            }catch (ex: TimeoutCancellationException) {
                showFancyToast("Timed out saving, please try again or press back", true, FancyToast.ERROR, "Timed out saving to database: ${ex.message}")
            }catch(ex: Exception){
                showFancyToast("Error saving to database, please try again or press back", true, FancyToast.ERROR, "Error saving to database: ${ex.message}")
            }
        }

1
没问题,拉吉夫,我将更进一步,并提到使用实时数据,协程可以感知生命周期并自动取消以避免清理调用,但是不想将太多学习曲线变成一个答案;)
山姆

3

我创建了更简单的方法来调用此方法。

public static void CallWithDelay(long miliseconds, final Activity activity, final String methodName)
    {
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                try {
                    Method method =  activity.getClass().getMethod(methodName);
                    method.invoke(activity);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }, miliseconds);
    }

要使用它,只需调用: .CallWithDelay(5000, this, "DoSomething");


3
反思这样的基本任务?
Max Ch

由于问题的调用方法类似于iOS performSelector。这是最好的方法。
HelmiB

3

当您获得以下作品时,

java.lang.RuntimeException:无法在尚未调用Looper.prepare()的线程内创建处理程序

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



2

如果使用RxAndroid,则线程和错误处理将变得更加容易。延迟后执行以下代码

   Observable.timer(delay, TimeUnit.SECONDS)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(aLong -> {
           // Execute code here
        }, Throwable::printStackTrace);

1

每个人似乎都忘了在发布新的可运行对象或消息之前先清理处理程序。否则,它们可能会积累并导致不良行为。

handler.removeMessages(int what);
// Remove any pending posts of messages with code 'what' that are in the message queue.

handler.removeCallbacks(Runnable r)
// Remove any pending posts of Runnable r that are in the message queue.

1

这是另一种棘手的方法:当可运行的更改UI元素时,它不会引发异常。

public class SimpleDelayAnimation extends Animation implements Animation.AnimationListener {

    Runnable callBack;

    public SimpleDelayAnimation(Runnable runnable, int delayTimeMilli) {
        setDuration(delayTimeMilli);
        callBack = runnable;
        setAnimationListener(this);
    }

    @Override
    public void onAnimationStart(Animation animation) {

    }

    @Override
    public void onAnimationEnd(Animation animation) {
        callBack.run();
    }

    @Override
    public void onAnimationRepeat(Animation animation) {

    }
}

您可以像这样调用动画:

view.startAnimation(new SimpleDelayAnimation(delayRunnable, 500));

动画可以附加到任何视图。



1

我喜欢更干净的东西:这是我的实现,在您的方法内部使用的内联代码

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

0

android中的合适解决方案:

private static long SLEEP_TIME = 2 // for 2 second
.
.
MyLauncher launcher = new MyLauncher();
            launcher.start();
.
.
private class MyLauncher extends Thread {
        @Override
        /**
         * Sleep for 2 seconds as you can also change SLEEP_TIME 2 to any. 
         */
        public void run() {
            try {
                // Sleeping
                Thread.sleep(SLEEP_TIME * 1000);
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
            //do something you want to do
           //And your code will be executed after 2 second
        }
    }

0

类似的解决方案,但使用起来更清洁

将此功能写在类之外

fun delay(duration: Long, `do`: () -> Unit) {

    Handler().postDelayed(`do`, duration)

}

用法:

delay(5000) {
    //Do your work here
}

`做什么`?
Skizo-ozᴉʞS

只是个名字,什么都保留。do是一种内置方法,因此我们必须使用`来将其用作变量名
Manohar Reddy

谢谢,但是为什么使用这个变量名呢?我的意思是它的功能是什么。
Skizo-ozᴉʞS

1
我觉得像do这样3秒的延迟后
马诺哈尔·雷迪

0

在Android中,我们可以编写以下kotlin代码来延迟执行任何功能

class MainActivity : AppCompatActivity() {

private lateinit var handler: Handler

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    handler= Handler()
    handler.postDelayed({
        doSomething()
    },2000)
}

private fun doSomething() {
    Toast.makeText(this,"Hi! I am Toast Message",Toast.LENGTH_SHORT).show()
}
}
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.