停止在Android视图中使用Post / PostDelayed

本文揭示了Android中的 post 和 postDelayed API 的潜在风险,这些API虽然方便,但在视图上使用时可能带来问题。通过探讨View类的内部工作原理,作者警告开发者要谨慎使用这些方法,以免造成意料之外的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Since its beginning, Android has provided a Handler API. As the documentation states, it allows you to deliver messages from a queue on a Looper’s thread.

从一开始,Android就提供了Handler API。 如文档所述,它允许您从Looper线程上的队列传递消息。

Handler().postDelayed({
doSomething()
}, delay)

This API is handy and yet so sneaky. Don’t let yourself be fooled when using it on a view. To understand where the danger lies, we need to dig deeper into the View class.

这个API很方便,但是却很偷偷摸摸。 在视图上使用它时,不要让自己上当。 要了解危险所在,我们需要更深入地研究View类。

视图处理程序 (Handlers for Views)

View in Android leverages the Handler API. It acts as a passthrough with an inner Handler and exposes both post() and postDelayed() functions.

Android中的View利用Handler API。 它充当内部Handler postDelayed()并公开post()postDelayed()函数。

Have a closer look at its implementation (at the time I’m writing the article):

仔细研究其实现(在撰写本文时):

/**
 * <p>Causes the Runnable to be added to the message queue, to be run
 * after the specified amount of time elapses.
 * The runnable will be run on the user interface thread.</p>
 *
 * @param action The Runnable that will be executed.
 * @param delayMillis The delay (in milliseconds) until the Runnable
 *        will be executed.
 *
 * @return true if the Runnable was successfully placed in to the
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the Runnable will be processed --
 *         if the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 *
 * @see #post
 * @see #removeCallbacks
 */
public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }
// Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().postDelayed(action, delayMillis);
    return true;
}

I want you to pay attention to the Javadoc comment. The removeCallbacks line hints at something about removing the callback.

我希望您注意Javadoc注释。 removeCallbacks行暗示了有关删除回调的信息。

Let’s see it in action:

让我们来看看它的作用:

myView.postDelayed({
myView.animateSomething()
}, delay)

In this example, I want to animate my view when a given delay expires.

在此示例中,当给定延迟到期时,我想为视图设置动画。

Once the delay expired, what tells me my view still exists? That it’s still visible to the user?

延迟过期后,什么告诉我我的观点仍然存在? 用户仍然可以看到它吗?

You see, views have lifecycles. They may be destroyed at any time, either by the system or because your user navigates inside your app.

您会看到,视图具有生命周期。 它们可能随时被系统破坏,或者因为用户在您的应用内导航而被破坏。

Since you queued a message inside a Looper, the system will deliver it if you don’t tell it to do otherwise. You expose your app to the possibility of a crash with a NullPointerException.

由于您已在Looper中将消息排队,因此,如果您不告诉它,则系统将传递该消息。 使用NullPointerException使您的应用程序可能崩溃。

You understand now how fundamental this little comment is. I’ve seen so many crashes due to this API because developers failed to handle lifecycles.

你明白这一点评论现在怎么根本的是。 由于该开发人员无法处理生命周期,因此我已经看到过如此多的崩溃。

如果要延迟对视图的操作怎么办? (What If I Want to Delay an Action on a View?)

You could still use this API and extract the Runnable declared inside your Handler. You’ll need to remove the callback whenever it’s relevant in your code, as hinted in the method’s comment.

您仍然可以使用此API并提取在Handler声明的Runnable 。 正如方法注释中所暗示的,您需要在代码中与回调相关的任何时候都将其删除。

private val animateRunnable = Runnable {
myView.animateSomething()
}myView.postDelayed(animateRunnable, delay)// Somewhere in your code
myView.removeCallback(animateRunnable)

I advocate banning these methods because they’re misused or inappropriate.

我主张禁止使用这些方法,因为它们被滥用或不合适。

For instance, if you’re using RxJava, you won’t need them anymore. Making a stream with either a delay or a timer is trivial. And you can dispose of your stream with ease.

例如,如果您使用的是RxJava,则不再需要它们。 使用延迟或计时器制作流是微不足道的。 而且,您可以轻松处理流。

val disposable = Observable.timer(delay, TimeUnit.MILLISECONDS)
.subscribe {
myView.animateSomething()
}// Somewhere in your code
disposable.dispose()

如果我需要等待另一帧怎么办? (What If I Need to Wait for Another Frame?)

So far, I’ve only mentioned postDelayed(). To me, post() is by far the most misused API I’ve ever seen when applied to a View.

到目前为止,我只提到过postDelayed() 。 对我来说, post()是迄今为止应用到View时见过最滥用的API。

Why use post() when there’s no delay attached to it? Remember Handler is queuing messages in a Looper. Posting will queue the message and deliver it to the next frame.

在没有延迟的情况下为什么要使用post() ? 请记住, HandlerLooper中对消息进行排队。 发布将使消息排队并将其传递到下一帧。

Usually, I’ve seen developers using this because the view was not laid out. Imagine you want to animate a view as soon as it appears. You need its position and/or its size, depending on which animation you intent to achieve.

通常,我见过开发人员使用此视图,因为视图没有布置。 假设您要在视图出现后立即对其进行动画处理。 您需要确定其位置和/或大小,这取决于要实现的动画。

You must wait for the view to be measured. So you delegate the animation to the next frame and cross your fingers that the view will be ready. With this solution, two things stand out:

您必须等待对视图进行测量。 因此,您将动画委派给下一帧,然后用手指交叉,以使视图准备就绪。 使用此解决方案,有两点突出:

  1. You have no guarantee your view will be measured in the next frame.

    您不能保证您的视图将在下一帧中进行测量。
  2. This code looks patchy as heck.

    这段代码看起来有些杂乱无章。

Like postDelayed(), there are more reliable mechanisms. You should use ViewTreeObserver instead, in particular, the two following callbacks:

postDelayed() ,有更可靠的机制。 您应该改用ViewTreeObserver ,尤其是以下两个回调:

You must be careful when using those methods. You’re likely to create memory leaks if you forget to remove the listener once you performed your action.

使用这些方法时必须小心。 如果您在执行操作后忘记删除监听器,则很可能造成内存泄漏。

myView.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
myView.animateSomething()
myView.viewTreeObserver.removeOnPreDrawListener(this)
return true
}
})myView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onPreDraw(): Boolean {
myView.animateSomething()
myView.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})

Even better, use KTX extensions, and let them take care of the boilerplate for you.

更好的是,使用KTX扩展,让它们为您处理样板。

myView.doOnPreDraw {    myView.animateSomething()
}myView.doOnLayout { myView.animateSomething()
}

That’s all folks! I may sound harsh when advocating to ban this mechanism. At the very least, I encourage you to chase them out and ponder whether they’re suited to the task.

那是所有人! 在提倡禁止这种机制时,我可能听起来很刺耳。 至少,我鼓励您将它们赶出去,并考虑它们是否适合任务。

翻译自: https://medium.com/better-programming/stop-using-post-postdelayed-in-your-android-views-9d1c8eeaadf2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值