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()
? 请记住, Handler
在Looper
中对消息进行排队。 发布将使消息排队并将其传递到下一帧。
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:
您必须等待对视图进行测量。 因此,您将动画委派给下一帧,然后用手指交叉,以使视图准备就绪。 使用此解决方案,有两点突出:
- You have no guarantee your view will be measured in the next frame. 您不能保证您的视图将在下一帧中进行测量。
- 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
,尤其是以下两个回调:
OnPreDrawListener
notifies you when a view is about to be laid out. At this point, the view has been measured当视图要布局时,
OnPreDrawListener
会通知您。 至此,视图已被测量OnGlobalLayoutListener
triggers an event whenever your view state changes每当您的视图状态更改时,
OnGlobalLayoutListener
触发一个事件
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