如何从Handler中删除所有的回调?

255

我有一个来自子Activity的Handler,它是由主Activity调用的。这个Handler被子类用来postDelay一些Runnables,但是我无法管理它们。现在,在onStop事件中,我需要在结束Activity之前删除它们(不知何故,我已经调用了finish(),但它仍然会一遍又一遍地调用)。有没有办法从Handler中删除所有回调?

9个回答

586

根据我的经验,调用这个函数非常好用!

handler.removeCallbacksAndMessages(null);

从removeCallbacksAndMessages的文档中可以看到...

删除所有等待执行的回调函数和发送的消息,它们的obj是token。 如果token为null,那么所有的回调和消息都将被删除。


2
@Malachiasz 我认为我会在onStop或onPause中使用它,以确保活动失去焦点后不处理任何消息。但这取决于回调/消息触发时需要执行什么操作。 - Boy
1
我相信之前在某些手机上执行此操作时曾经遇到过NPE,但已经有一段时间了。 - Matt Wolfe
4
我遇到了一些问题,使用 removeCallbacksAndMessages(null) 无法删除我的某些回调。 当我想要停止接收回调时,我会调用 handler.removeCallbacksAndMessages(null) 并将我的处理程序设置为 null,但是由于我仍然会得到回调,当我想要循环使用 handler.postDelayed() 时就会遇到 NPE。 - Snaker
1
@Snaker,你解决问题了吗?我也遇到了同样的问题,即使通过设置null来删除回调和消息,Handler.Callback仍然被调用。 - ShrimpCrackers
2
@ShrimpCrackers 我发现保持一个可运行实例并使用 yourHandler.removeCallbacks(yourRunnable) 是最可靠的方法。我今天仍在使用它。 - Snaker
显示剩余2条评论

20

对于任何特定的Runnable实例,请调用Handler.removeCallbacks()。请注意,它使用Runnable实例本身来确定要取消注册的回调函数,因此,如果每次进行发布时都创建一个新实例,则需要确保您有引用确切的Runnable以取消。例如:

Handler myHandler = new Handler();
Runnable myRunnable = new Runnable() {
    public void run() {
        //Some interesting task
    }
};
你可以调用myHandler.postDelayed(myRunnable, x)在代码的其他地方向消息队列发送另一个回调,同时使用myHandler.removeCallbacks(myRunnable)移除所有待处理的回调。
不幸的是,你不能简单地“清除”Handler的整个MessageQueue,即使你请求与它关联的MessageQueue对象,因为添加和移除项目的方法的访问权限是包可见的(只有android.os包内的类可以调用它们)。你可能需要创建一个细小的Handler子类来管理在发布/执行时的Runnable列表...或者考虑另一种范例来在每个Activity之间传递你的消息。
希望能帮到你!

谢谢,我知道这个。但是我有很多Runnable在许多子类中,管理它们都是一项艰巨的工作!有没有办法在onStop()事件中删除它们所有? - Luke Vo
明白了,我更新了答案并提供了更多信息。简而言之,您无法调用一个方法来广泛清除处理程序的消息队列... - devunwired

11

定义一个新的处理程序和可运行对象:

private Handler handler = new Handler(Looper.getMainLooper());
private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            // Do what ever you want
        }
    };

延迟调用post:

handler.postDelayed(runnable, sleep_time);

从您的处理程序中移除回调函数:

handler.removeCallbacks(runnable);

8

如果您没有Runnable引用,在第一个回调中获取消息的obj,并使用removeCallbacksAndMessages()来删除所有相关的回调。


8
请注意,应该在类范围内定义HandlerRunnable,以便只创建一次。除非定义多次,否则removeCallbacks(Runnable)将正常工作。请参考以下示例以更好地理解:

不正确的方式:

    public class FooActivity extends Activity {
           private void handleSomething(){
                Handler handler = new Handler();
                Runnable runnable = new Runnable() {
                   @Override
                   public void run() {
                      doIt();
                  }
               };
              if(shouldIDoIt){
                  //doIt() works after 3 seconds.
                  handler.postDelayed(runnable, 3000);
              } else {
                  handler.removeCallbacks(runnable);
              }
           }

          public void onClick(View v){
              handleSomething();
          }
    } 

如果您调用onClick(..)方法,在它被调用之前,doIt()方法将一直运行。因为每次都会创建一个新的HandlerRunnable实例。这样,您丢失了属于处理程序和可运行实例的必要引用。
正确的方式是:
 public class FooActivity extends Activity {
        Handler handler = new Handler();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                doIt();
            }
        };
        private void handleSomething(){
            if(shouldIDoIt){
                //doIt() works after 3 seconds.
                handler.postDelayed(runnable, 3000);
            } else {
                handler.removeCallbacks(runnable);
            }
       }

       public void onClick(View v){
           handleSomething();
       }
 } 

这样,您就不会丢失实际引用,并且removeCallbacks(runnable)可以成功工作。

关键句子是“在使用的 Activity Fragment 中将它们定义为全局变量”


2
如所说,可以起到作用。 但是为什么呢? 如果你查看源代码,就能更清楚地理解它。 有三种方法可以从处理程序(MessageQueue)中删除回调/消息:

  1. 通过回调(和令牌)删除
  2. 按message.what(和token)删除
  3. 通过令牌删除

Handler.java(留下一些重载方法)

/**
 * Remove any pending posts of Runnable <var>r</var> with Object
 * <var>token</var> that are in the message queue.  If <var>token</var> is null,
 * all callbacks will be removed.
 */
public final void removeCallbacks(Runnable r, Object token)
{
    mQueue.removeMessages(this, r, token);
}

/**
 * Remove any pending posts of messages with code 'what' and whose obj is
 * 'object' that are in the message queue.  If <var>object</var> is null,
 * all messages will be removed.
 */
public final void removeMessages(int what, Object object) {
    mQueue.removeMessages(this, what, object);
}

/**
 * Remove any pending posts of callbacks and sent messages whose
 * <var>obj</var> is <var>token</var>.  If <var>token</var> is null,
 * all callbacks and messages will be removed.
 */
public final void removeCallbacksAndMessages(Object token) {
    mQueue.removeCallbacksAndMessages(this, token);
}

MessageQueue.java负责实际工作:

void removeMessages(Handler h, int what, Object object) {
    if (h == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.
        while (p != null && p.target == h && p.what == what
               && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                if (n.target == h && n.what == what
                    && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

void removeMessages(Handler h, Runnable r, Object object) {
    if (h == null || r == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.
        while (p != null && p.target == h && p.callback == r
               && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                if (n.target == h && n.callback == r
                    && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

void removeCallbacksAndMessages(Handler h, Object object) {
    if (h == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.
        while (p != null && p.target == h
                && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                if (n.target == h && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

1

对我来说,这些解决方案都无效。但是我找到了一个有效的解决方案。

即使调用了 handler.removeCallbacksAndMessages(null),已经添加在处理程序队列中的可运行项仍将被执行。当我试图停止线程时,它会导致以下错误:

W/MessageQueue(6436): java.lang.RuntimeException: Handler (android.os.Handler) {416659f0} 将消息发送给一个已死亡的线程的处理程序

我的解决方案:

  • 要删除所有回调,您需要引用可以存储在ArrayList中的所有可运行项。

     private ArrayList<Runnable> runnableQueue=new ArrayList<Runnable>();
    
  • 然后,每次想要发布可运行项时,请将其存储在数组中,然后使用handler.post()发布数组项。

    private void postInHandler(){
      @override
      runnableQueue.add(new Runnable() {
          public void run() {   
              //your code 
          }
      });
      //发布数组中的最后一项
      handler.post(runnableQueue.get(runnableQueue.size()-1));
      }
    
  • 然后,要删除所有回调,请使用此方法,该方法将通过迭代数组来删除每个回调。

      private void removeHandlerCallbacks(){
         for(Runnable runnable:runnableQueue){
             networkHandler.removeCallbacks(runnable,null);
         }
         runnableQueue.clear();
     }
    
  • 太好了!队列已清除。但是,在清除数组之后,我们必须确保在停止线程之前不再发布任何可运行项到处理程序。因此,您必须声明:

    boolean allowPosting=true;

所以包括这个:

private void removeHandlerCallbacks(){           
    allowPosting=false;//add this line to stop posting after clearing the array
    for(Runnable runnable:runnableQueue){
        handler.removeCallbacks(runnable,null);
    }
     //Dont forget to clear the array
     runnableQueue.clear();
}

然后在处理程序中发布之前检查条件:

if(allowPosting){
    postInHandler();
}

就这些,现在队列已经清空,我们可以确定在清空队列后不会再有可运行的任务被加入。因此,可以安全地停止线程。


0

文档中说removeCallbacksAndMessages(null)会移除所有回调,但这并不总是正确的。可以试试这个:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    handler.removeCallbacksAndMessages("ACTION_NAME")
    handler.postDelayed(runnable, "ACTION_NAME", 10_000) //if you want restart runnable
} else {
    handler.removeCallbacksAndMessages(null)
    handler.postDelayed(runnable, 10_000) //if you want restart runnable
}

-1

移除特定的可运行项

handler.removeCallbacks(yourRunnable)

为了移除所有可运行项

handler.removeCallbacksAndMessages(null)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接