【问题标题】:How to delay events for next observers?如何为下一个观察者延迟事件?
【发布时间】:2019-11-17 20:05:42
【问题描述】:
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {

    if (event.action == KeyEvent.ACTION_DOWN) {
        val status = operation() // operation takes time
        return status
    }
    return super.onKeyDown(keyCode, event)
}

当一个事件发生时,上面的处理程序被调用。现在如果需要时间来决定是否将truefalse 状态传递到 if 块内的下一层(超级),如何正确设计流程。我需要得到结果asynchronously,因为决定返回值的时间(即truefalse)可能会更长,并且函数不应该让主线程等待。所以,我需要想办法让超级调用延迟。

解决这个问题的正确方法是什么?有什么特定的设计模式来处理这类问题吗?

请忽略语言。

更新 1

我一直在考虑存储keyCodeevent 并立即返回true(意味着事件已被其他观察者重新消费),然后在operation() 完成后我有status 可用,现在我可以使用相同的 keyCodeevent 重新触发挂起的存储事件。但并非所有事件都提供手动触发的功能。对于无法手动触发的事件,我该怎么做。

我提出的解决方案

private fun doOperation(callback: (status:Boolean) -> Unit) {
    Handler().postDelayed({
        callback.invoke( arrayOf(true, false).random() )
    }, 5000)
}

var pendingEvent: KeyEvent? = null

override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
    if (event.action == KeyEvent.ACTION_DOWN) {
        doOperation {
            if (it && pendingEvent != null){
                dispatchKeyEvent(pendingEvent)
                pendingEvent = null
            } else {
                // do nothing
            }
        }
        return true // let other know I consumed it
    }
    return super.onKeyDown(keyCode, event)
}

这是正确的方法吗?这种想法有什么坏处?

【问题讨论】:

  • 你的意思是你不知道onKeyDown应该返回true还是false?
  • @Blackbelt 我将知道需要延迟的异步操作(比如说来自网络应用程序的服务器调用)之后的返回值。但这是一个 f/w 回调,需要同步并立即传递 true 或 false。
  • AFAIK,这是不可能的。你不能在不阻塞主线程的情况下延迟返回语句。
  • @karandeepsingh 我知道这是不可能的。我一直在寻找解决办法。
  • 现在,一分钟后,长时间运行的动画停止了,onAnimationStopped 处理程序被调用。在此处理程序中,您首先取消订阅 animationStopped 事件。然后检查按下了哪个键来决定启动哪个动画并启动适当的新动画。现在你已经等待而没有阻塞任何东西。存储第一个事件的结果或事件参数,以便延迟操作(事件处理程序ònAnimationCompleted`)稍后可以使用它。

标签: android events design-patterns event-handling observer-pattern


【解决方案1】:

使用观察者模式可能会对您有所帮助。 You can use Debounce operator (debounce(DEBOUNCE_TIMEOUT.toLong(), TimeUnit.MILLISECONDS)) to delay the event. only emit an item from an Observable if a particular timespan has passed without it emitting another item Check the official documentation for how to use

编辑 1 代码sn -p

RxView.clicks(mButton) .debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) .subscribe(...)

【讨论】:

  • 如何在纯 android 中使用 debounce?任何代码 sn-p 示例?谢谢
  • Debounce 是 RxAndroid 库的一部分。请检查答案的编辑 1 部分。
【解决方案2】:

因为事件是一劳永逸的(没有返回值或 void),所以应该清楚异步事件或返回值的事件处理程序本身就是一个矛盾或悖论。当异步意味着“等待而不阻塞”和事件“通知而不等待”时,您显然会产生比解决方案更多的问题。返回值还意味着调用者正在等待操作完成并对结果感兴趣。

我建议重组您的应用程序。
事件处理程序永远不应返回值或异步。

如果观察者需要更多时间来决定它是否消耗呢? 你怎么处理?

这个决定(或一般的决定)总是取决于至少一个变量的状态。 有两种情况

  1. 在观察者收到通知或
  2. 的那一刻就知道状态
  3. 此时状态未知(观察者需要更多时间)。

情况 1) 不需要等待,但 情况 2) 需要。
情况2)的情况下,状态的改变总是由操作触发。该操作的执行时间决定了等待时间的长短。当相关状态发生变化时,此操作必须引发事件。

一般来说,你有三个等待的选择:

  1. 保持旋转直到满足轮询等条件(例如无限 循环): while(true){}。
  2. 使用计时器并在经过的时间后执行操作
  3. 在需要等待时使用事件。

前两个选项将阻塞一个线程。如果线程与 observable 线程相同,那么您将阻塞 observable 和所有其他等待的观察者。如果该线程是 UI 线程,则 UI 将停止并变得无响应。事件是一种可以解决阻塞问题的模式。


让我们想象一下下面的场景:你想开始一个特定的动画。您有两个限制条件:动画的类型取决于按下哪个键,并且在开始新动画之前,您必须等到第一个动画完成。例如。当 TAB 被按下时,从左到右移动一个矩形。当 ENTER 被按下时,从上到下移动一个矩形。

这引入了两种等待情况:按键按下和动画完成。为了处理等待,您将为每个潜在的等待情况创建并关联一个事件:keyPressedanimationStopped 事件:

键盘按键事件

等待特定键被按下的观察者实现的接口:

interface IKeyPressedListener {
  void onKeyPressed(int keyCode);
}

由暴露和引发事件的可观察对象实现的事件接口:

interface IKeyPressedEvent {
  void subscribeToKeyPressedEvent(IKeyPressedListener listener);
  void unsubscribeToKeyPressedEvent(IKeyPressedListener listener);
}

动画事件

等待动画停止的观察者要实现的接口:

interface IAnimationStoppedListener {
  void onAnimationStopped();
}

由暴露和引发事件的可观察对象实现的事件接口:

interface IAnimationStoppedEvent {
  void subscribeToAnimationStoppedEvent(IAnimationStoppedListener listener);
  void unsubscribeToAnimationStoppedEvent(IAnimationStoppedListener listener);
}

实际的事件监听器

在按键按下时播放动画的类的实现:

class AnimationController implements IKeyPressedListener, IAnimationStoppedListener 
{
  // store the key that was pressed,
  // so that an event that will be raised at a later can process it
  private int keyCodeOfLastKeyPressed = 0;

  // The reference to the class that exposes
  // the keyPressedEvent by implementing IKeyPressedEvent 
  KeyboardController keyboardController;

  // The reference to the class that exposes
  // the animationStoppedEvent by implementing IAnimationStoppedEvent 
  AnimationPlayer animationPlayer;

  // Constructor
  public AnimationController() {
    this.keyboardController = new KeyboardController();
    this.animationPlayer = new AnimationPlayer();

    // Subscribe to the key pressed event
    this.keyboardController.subscribeToKeyPressedEvent(this);
  }

  @Override
  public void onKeyPressed(int keyCode) {
    if (this.animationPlayer.hasPlayingAnimation) {
      // Instead of waiting that the animation completes
      // subscribe to an event and store the relevant data
      this.keyCodeOfLastKeyPressed  = keyCode;
      this.animationPlayer.subscribeToAnimationStoppedEvent(this);      
    }
    else {
      // There is no playing animation, so no need to wait
      this.animationPlayer.playAnimation(keyCode);
    }
  }

  // After a while this handler will be invoked by the event source.
  @Override
  public void onAnimationStopped() {
    // To avoid memory leaks unsubscribe first
    this.animationPlayer.unsubscribeToAnimationStoppedEvent(this);

    // Since we stored the key code earlier, we can continue to process it
    // and start a new animation that maps to a specific key
    this.animationPlayer.playAnimation(this.keyCodeOfLastKeyPressed);
  }
}

遵循观察者模式可以避免线程阻塞等待时间。应用程序可以离开上下文并在事件发生时返回(在本例中为AnimationStopped 事件)。为了存储一个事件的变化值(事件参数),引入了一个私有的共享字段,以便第二个事件处理程序可以访问并最终处理它。

【讨论】:

  • 感谢您提供如此详尽的回答。你能看看我的更新并回顾一下我的想法吗?
猜你喜欢
  • 1970-01-01
  • 2021-06-26
  • 2023-04-06
  • 1970-01-01
  • 1970-01-01
  • 2020-08-09
  • 1970-01-01
  • 2011-01-23
  • 1970-01-01
相关资源
最近更新 更多