Search code examples
javaandroidhandlerwear-osrunnable

removeCallbacks() doesn't seems to delete the specified Runnable in Handler


I developed an standalone app for a smartwatch, and I want to have an OnTouchListner which detect if a Touch lasted for at least 3 sec and perform action only in that case.

Here is the code I use : (a simple boolean state which is set to false when MotionEvent is up and a PostDelayed action on the Handler)

private int longClickDuration = 3000;
private boolean isLongPress = false;
static Runnable longTouchRunnable;

mContainerView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {

                longTouchRunnable = new Runnable() {
                    @Override
                    public void run() {
                        if (isLongPress) {
                            Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
                            long[] vibrationPattern = {0, 500, 50, 300};
                            //-1 - don't repeat
                            final int indexInPatternToRepeat = -1;
                            vibrator.vibrate(vibrationPattern, indexInPatternToRepeat);
                            // set your code here
                            setStateStarting(); //Block the GUI, only one click and waiting for tablet
                            presenter.onStartClick();
                            Log.d("Long","LONG CLICK PERFORMED");
                            // Don't forgot to add <uses-permission android:name="android.permission.VIBRATE" /> to vibrate.
                        }

                    }
                };

                switch (currentState) {
                    case START: {
                        if (event.getAction() == MotionEvent.ACTION_DOWN) {
                            isLongPress = true;
                            Log.e("Long touch","Start");
                            myUIHandler.postDelayed(longTouchRunnable, longClickDuration);
                        } else if (event.getAction() == MotionEvent.ACTION_UP) {
                            isLongPress = false;
                            myUIHandler.removeCallbacks(longTouchRunnable);
                            Log.e("Long touch", "Stop");
                        }
                        return true;
                    }
                }
                return false;
            }


        });

My problem : Everything work perfectly expect that the Runnable doesn't seem to be delete when it's necessary. --> If I click quickly two times on the view and then a third time and holding it (So the boolean state is true for the three PostDelayed which came after 3 sec) I will have my specific action code executed 3 times --> the Runnable task wasn't removed with the ACTION_UP event.

Info : The message debug are prompt as expected...

How can I properly destroy a Runnable that was postdelayed in a handler ?

Thanks


Solution

  • handler.removeCallbacksAndMessages(null);
    

    In the docs for removeCallbacksAndMessages it says...

    Remove any pending posts of callbacks and sent messages whose obj is token. If a token is null, all callbacks and messages will be removed.

    removeCallbacks only stops pending messages (Runnables). If your runnable has already started, then there's no stopping it (at least not this way).

    Alternatively, you can extend the Runnable class and give it some kind of kill switch like this:

    public class MyRunnable implements Runnable
    {
       public boolean killMe = false;
    
       public void run()
       {
          if(killMe)
             return;
    
          /* do your work */
       }
    
       public void killRunnable()
       {
          killMe = true;
       }
    }
    

    This will only prevent it from starting, but you could occasionally check killMe and bail out. If you are looping the runnable (like some kind of background thread) you can say:

    while(!killMe) {
       /* do work */
    }