我很好奇这个方法的安全性,我考虑在我的Android应用程序中传递触摸事件(并测试我对Java中的并发性的理解)。这是基础知识:
我有一个SurfaceView连接到SurfaceHolder.Callback以获取用户输入事件,主要是onTouchEvent
回调。调用onTouchEvent
方法后,我会看到event.getAction() == MotionEvent.ACTION_UP
是否会调用我命名为postTouchEvent
的方法,这是我的应用程序线程的成员方法,用于更新应用程序的状态和画到画布上。
SurfaceView
@Override
public boolean onTouchEvent(final MotionEvent event)
{
mAppThread.postTouchEvent(event);
}
AppThread
private volatile MotionEvent mInputEvent;
public void postTouchEvent(final MotionEvent event)
{
this.mInputEvent = event;
}
@Override
public void run()
{
// while loop ...
if (this.mInputEvent != null)
{
final MotionEvent inputEvent = this.mInputEvent;
this.mInputEvent == null;
// update state based on inputEvent (not this.mInputEvent)
}
// draw to canvas
}
现在我明白它肯定不是原子的,但是因为我从框架中接收它后将其视为不可变的,这不会起作用吗? (而不是同步post方法和if语句,我没有问题,但我要学习。)
以下是我的想法。我知道我将对该对象有一个有效的引用,但我不确定我将实际看到该对象的状态。虽然测试一切都运行得很好,但我知道线程异常可能是多么罕见,即使某些东西被破坏了。
另外,我可以看到一个问题:如果另一个MotionEvent出现,run()
方法中的inputEvent可能会设置为与this.mInputEvent != null
时引用的事件不同的事件。已经过检查,但这确实不是问题。
那么,有什么我遗失的,或者就我的目的而言,这应该没问题吗?
答案 0 :(得分:16)
不,这不安全,但不是因为你可能期望的原因。
ViewRoot.java, line 1841。这是在视图层次结构中调度MotionEvent的代码。第1841行是finally
块的一部分,该块在刚刚调度的MotionEvent上调用recycle()
。
MotionEvents不像大多数对象那样被垃圾收集,它们被合并和回收,以避免在事件发送期间不必要的内存分配和垃圾收集。回收MotionEvent会将其返回到对象池,以便稍后在需要新的MotionEvent时再次使用。调用recycle()
后,应将MotionEvent视为无效。
您的示例代码可能最终会读取已被框架重用的MotionEvent对象,现在包含完全不同的数据。
如果您计划在onTouchEvent
返回后挂起到MotionEvent,请使用MotionEvent.obtain(event)
克隆它。静态obtain()
方法将从具有相同内容的对象池返回新的MotionEvent。
当你完成了recycle()
自己{1}}自己将MotionTvent返回池中时,你应该调用obtain()
。如果你忘记了这一步,这将不是一个大问题,它将成为常规Java垃圾,框架将在需要时创建新的MotionEvent。但是,系统可以非常快速地调度MotionEvents,以便与此优化协作可以在某些设备上产生非凡的性能差异。
答案 1 :(得分:1)
我认为这是“安全的”,模仿你已经确定丢失事件的点。
对于易失性和后续读取操作的写入操作存在“发生在之前”关系。这与单个线程中的操作中固有的“事先发生”关系相结合,应该意味着您的应用程序线程将在其引用写入volatile时查看事件对象的真实状态。
当然,如果事件侦听器线程在更新引用后更改了事件对象的状态,则无法保证应用程序线程将看到它们。同上,如果第三个线程更新事件对象。