在运行Android 2.2的设备上,我想检测用户何时按下屏幕一段时间。想象一下,发送莫尔斯码信息,短按(点)和长按(破折号)。我希望一旦用户抬起她的手指就会对短按钮作出反应,并且在(比方说)500毫秒之后进行更长时间的按压,即使她继续按住她的手指。
我已经查看了FutureTask和ScheduledExecutorService,但这些实现看起来有些过分。或者我可能只是冷静地直接处理线程,并查看处理它们所需的所有代码。
这是我在其他语言中完成此类操作的简化伪代码:
public boolean onTouch(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
timer = createObjectToCallback(callbackMethod, 500); // milliseconds
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (timer still exists) {
timer.kill();
// Do the short press thing
} else {
// Do nothing. It already happened when callbackMethod was triggered
}
}
}
public void callbackMethod() {
// Do the long press thing. The timer has already auto-destructed.
}
在Java中有哪些简单的方法?
==编辑以回应@zapl ==
的回答编写有效的代码是一回事。了解它是如何工作的另一个。
如果我理解正确,更新UI的线程已经在循环中运行。让我们想象一个非常简单的案例。
Main活动创建一个黑色画布,并包含onTouch
方法。当它启动时,它会调用setOnTouchListener
。主线程现在不断地监听屏幕输入。如果用户触摸屏幕的方式已更改,则会调用onTouch
方法,并提供有关更改的信息。
让我们说onTouch
方法在触摸点周围绘制一个绿色圆圈。使用属于主线程的循环绘制该圆。绘图完成后,主线程开始从屏幕检查新的更改。如果没有变化,则不会再次调用onTouch
,并且绿点不会移动。
当用户抬起手指时,屏幕会向主线程提供更改的信息,onTouch
方法中的代码会删除点。
Create interface
Has the screen detected a change? No: loop
Has the screen detected a change? No: loop
...
Has the screen detected a change? Yes: draw a green dot; loop
Has the screen detected a change? No: loop.
...
Has the screen detected a change? Yes: new position => redraw the green dot; loop
...
Has the screen detected a change? Yes: not touching => remove dot; loop
Has the screen detected a change? ...
如果用户的手指移动至少500毫秒,我想让点变成红色。没有移动意味着没有回调onTouch
。所以我可以设置一个Handler
,它将自己添加到主线程的循环中。主线程现在在其循环中有两个动作。
Create interface
Has the screen detected a change? No: loop
Has the screen detected a change? No: loop
...
Has the screen detected a change? Yes: a touch; draw a green dot; add Handler; loop
Has the screen detected a change? No;
Is it time for Handler to trigger? No: loop.
...
Has the screen detected a change? No;
Is it time for Handler to trigger? Yes: change dot color to red; remove Handler; loop.
Has the screen detected a change? No: loop.
...
Has the screen detected a change? Yes: not touching => remove dot; loop
Has the screen detected a change? ...
Handler
执行的任何代码都会阻止主线程完成
这是Handler
做什么的准确描述吗?
答案 0 :(得分:1)
当你实际上不需要并行时使用线程确实有点过分,因为它增加了它自己的一组问题。您需要的是安排将来运行的代码,但是在同一个线程上。 Android的Handler
可以做到这一点。您可以安排Runnable
或Message
到达。还有派生的CountDownTimer用于更简单的周期性事件调度。
但在这种情况下可能不需要,因为有GestureDetector。
它附带Android,可以区分几种类型的单,长和双击。它的行为也与系统的其他部分一致。您可能想要使用它。
有关http://developer.android.com/training/gestures/detector.html
的更多信息如果您真的想要实现自己的,或者只是想看一个如何使用Handler
的示例,请查看GestureDetector's source。它充满了您发布的代码(mHandler.hasMessages
,mHandler.removeMessages
,mHandler.sendEmptyMessageDelayed
)。
注意:Handler
不是标准Java类,因为在同一个线程中调度事件需要一个线程基于消息队列,并且没有标准的解决方案。它也是UI框架依赖于框架的线程安全性。如果您尝试从某个后台线程修改ui,Android会尝试抛出异常。但是,相同的方法应该适用于Swing的事件调度线程(SwingUtilities.invokeLater
)。
编辑:尝试解释Ui线程&处理程序:
处理程序执行的任何代码都会阻止主线程完成。
正确。 Android的主要线程非常简化,如下所示:
public BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
public void loop() {
while (true) {
Runnable currentTask = queue.take(); // blocks until something happens
currentTask.run();
// repeat.
}
}
public void enqueue(Runnable runnable) {
queue.put(runnable);
}
public static void main(String[] args) {
startThreadsThatReceiveSystemEvents();
enqueue(new Runnable() {
@Override
public void run() {
Activity activity = createStartActivity();
activity.onCreate();
activity.onResume();
}
});
loop(); // fun fact: an android app will never return from here
// it's process is simply killed by the system
}
现实世界的等价物经常出现在相当远的堆栈中:
E/AndroidRuntime(20941): at android.os.Looper.loop(Looper.java:130)
E/AndroidRuntime(20941): at android.app.ActivityThread.main(ActivityThread.jav a:3691)
Android在启动/停止活动,绘制屏幕等方面所做的一切都是入队的结果,以便运行循环在某个时刻对其进行评估。 Handler
使用完全相同的队列。你排队的所有东西都将被执行,其中包含已经发生的所有其他事情。一个线程不能做与自身并行的事情,所以它都是顺序的。这就是Handler任务阻止其他任务的原因。
您的触摸事件示例基本上是正确的,它只是没有主动查看触摸屏。它通过它与系统的连接得到通知。基本上有另一个线程(Binder线程,如果你曾经看过线程列表)监听来自系统的消息,一旦它们到达,所有这个线程需要做的就是将它们排入主循环。如果等待,这可以自动唤醒循环。
主线程队列实际上不是简单的BlockingQueue
,因为它也需要支持预定的事件。它是一个名为MessageQueue
的特定于Android的实现,部分在本机代码中实现。 loop
方法也在它自己的类中(Looper
)。并且队列没有直接使用Runnable
,它实际上是Message
s的队列 - 可以包含Runnable
(在隐藏字段中)和运行循环,当它找到Runnable时在消息中将像上面的示例代码一样执行它。
每个Handler
绑定到一个Looper
/ MessageQueue
组合(因此绑定到1个线程)。 Handler.post
/ sendMessage
做了构建正确信息的肮脏工作。排队。 Message有一个返回Handler的链接,因此循环知道Handler的handleMessage
方法要调用。
除了使用已经存在的主线程循环/队列之外,您还可以自由地为这些线程创建其他基于队列的线程和处理程序。 https://stackoverflow.com/a/13369215/995891包含了一个很小的例子。