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

908

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

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

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

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

19

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

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

1
它不会冻结UI元素本身,因为它将在视图处理程序上进行调度。 - JacksOnF1re
1
不,发布的任务将在1秒钟内执行,但在此期间UI线程会执行其他有用的工作。 - demaksee

15

以下是我最简洁的解决方案:

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

13
如果您正在使用Android Studio 3.0及以上版本,则可以使用lambda表达式。方法callMyMethod()将在2秒后被调用。
new Handler().postDelayed(() -> callMyMethod(), 2000);

如果你需要取消延迟的 runnable,请使用以下代码:

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,却完全忽略了Lambda表达式,它们是标准Java。 - TomDK

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

7

那么在这里有几件事情需要考虑,因为有很多方法可以解决这个问题。尽管答案已经被选择和确定,但我认为重新审视这个问题并遵守适当的编码规范很重要,以避免因“大多数选择简单答案”而导致任何人走错方向。

首先让我们讨论简单的Post Delayed答案,这是整个主题中选择的获胜者。

有几件事情需要考虑。在延迟发布后,您可能会遇到内存泄漏、死对象、生命周期已过期等问题,因此正确处理很重要。您可以通过以下几种方式来做到这一点。

为了现代开发,请使用KOTLIN

这里是一个简单的示例,使用UI线程进行回调,并在触发回调时确认您的activity仍然存在且正常工作。

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

如果在方法签名中放置启动(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
没问题,Rajiv。我想再进一步提到使用LiveData时,协程可以感知生命周期并自我取消以避免清理调用,但是不想在一个答案中引入太多的学习曲线 ;) - Sam

7
我建议使用计时器(Timer),它允许您按照非常特定的间隔安排一个方法被调用。这不会阻塞您的用户界面(UI),并且在执行该方法时保持应用程序响应。
另一种选择是等待(wait);方法,它将阻塞当前线程指定的时间长度。如果在UI线程上执行此操作,则会导致您的UI停止响应。

2
Thread.sleep()比Object.wait()更好。wait意味着你期望被通知并且正在同步某些活动。Sleep表示你只是希望在一定时间内什么都不做。如果您希望在稍后的某个时间点异步发生操作,那么Timer就是正确的选择。 - Tim Bender
1
那是真的。这就是为什么我把它列为另一个选项的原因;-) - Nate

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

通过使用新引入的lambda表达式,您可以使代码更加简洁:

new Handler().postDelayed(() -> {/*your code here*/}, time);

4

使用Kotlin,我们可以通过以下方式实现

Handler().postDelayed({
    // do something after 1000ms 
}, 1000)

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