[android小知识]removeCallbacks与postDelayed的恩怨史(removeCallbacks所谓的无效, View和Handler 的postDelayed)...

本文详细介绍了如何使用Android中的postDelayed方法实现任务的延迟执行,并解释了如何通过removeCallbacks方法取消延迟任务。同时,文章提供了实际代码示例,展示了在不同情况下如何正确地使用这些方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

常见的.postDelayed(runnable, time)方法就是常见的延迟处理的方法之一,postDelayed(mRun,time);当想要需要取消的时候,调用removeCallbacks(mRun);即可,就可以取消postDelayed的方法里面的runnable,当然如果里面runnable已经执行了,那就不可能撤回或者中止了。

http://blog.youkuaiyun.com/bluewindtalker/article/details/51984300

下面是布局文件

<span style="font-size:18px;"> <Button
                android:id="@+id/btn_demo_removecallback_start"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="开始postdelayed" />

            <Button
                android:id="@+id/btn_demo_removecallback_stop"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="测试removecallback" />

            <TextView
                android:id="@+id/tv_demo_removecallback_test_onAnimationEnd"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="测试removecallback runnable效果" /></span>

以下是测试代码。

<span style="font-size:18px;">final TextView mRemovecallbackTV = (TextView)findViewById(R.id.tv_demo_removecallback_test_onAnimationEnd);

         mRun = new Runnable() {
            @Override
            public void run() {
                if(DirectionMarqueeDemoActivity.this.isFinishing()){
                    BlueLog.e(TAG,"DirectionMarqueeDemoActivity  isFinishing ");
                    mMarqueeAnimationTV.clearAnimation();
                    mRemovecallbackTV.removeCallbacks(mRun);
                }
                BlueLog.e(TAG, "Runnable   " + DirectionMarqueeDemoActivity.this);
                mRemovecallbackTV.postDelayed(this, 3000);
            }
        };

        findViewById(R.id.btn_demo_removecallback_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mRemovecallbackTV.postDelayed(mRun, 3000);
                BlueLog.e(TAG, " postDelayed " );
            }
        });
        findViewById(R.id.btn_demo_removecallback_stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mRemovecallbackTV.removeCallbacks(mRun);
                BlueLog.e(TAG, "removeCallbacks ");
            }
        });</span>

其中mRun为Activity的成员变量。


运行的时候我们发现


即使activity 结束了调用了onDestory方法后这里仍然运行,问题在于代码中,先removeCallBacks,再postDelayed,这里加一个return就好,



正确的代码大家就在removeCallBacks加入return即可,下面是logcat的打印



所以如要要循环调用postDelayed的时候,一样要进行判断是否所属的activity已经退出了。将结束方法一定要体现出来



而postDelayed(runnable, time)有view.postDelayed(mRun, time);和new Handler().postDelayed(mRun,time);这两种,

对于view.postDelayed我们通过源码发现

 public boolean postDelayed(Runnable action, long delayMillis) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.postDelayed(action, delayMillis);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
        return true;
    }
return attachInfo.mHandler.postDelayed(action, delayMillis);最后进入了Handler的postDelayed

public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

而ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);进入ViewRootImpl.java中,

static final class RunQueue {
        private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();

        void post(Runnable action) {
            postDelayed(action, 0);
        }

        void postDelayed(Runnable action, long delayMillis) {
            HandlerAction handlerAction = new HandlerAction();
            handlerAction.action = action;
            handlerAction.delay = delayMillis;

            synchronized (mActions) {
                mActions.add(handlerAction);
            }
        }

        void removeCallbacks(Runnable action) {
            final HandlerAction handlerAction = new HandlerAction();
            handlerAction.action = action;

            synchronized (mActions) {
                final ArrayList<HandlerAction> actions = mActions;

                while (actions.remove(handlerAction)) {
                    // Keep going
                }
            }
        }

        void executeActions(Handler handler) {
            synchronized (mActions) {
                final ArrayList<HandlerAction> actions = mActions;
                final int count = actions.size();

                for (int i = 0; i < count; i++) {
                    final HandlerAction handlerAction = actions.get(i);
                    handler.postDelayed(handlerAction.action, handlerAction.delay);
                }

                actions.clear();
            }
        }

最后仍然执行了handler.postDelayed(handlerAction.action, handlerAction.delay),所以两个内容是相差无几的。


其中HandlerAction来自于view调用invalidate时,父View传入的。



<think>首,用户的问题是关于Android中的Handler.postDelayed()Runnable在实现定时任务时的用法区别,以及它们的原理使用场景。用户特别提到了参考之前的引用,所以我需要整合引用中的信息。引用内容总结:-[^1]:展示了使用Handler.postDelayed()实现定时任务的代码:通过在任务结束后再次调用postDelayed()来循环任务。-[^2]:类似,用Kotlin代码展示如何在run()中递归调用postDelayed()实现定时任务。-[^3]:基本例子,展示了postDelayed()用于单次延时任务。-[^4]:提到一个问题,如果Handler是在主线程创建的,在子线程调用postDelayed()可能导致任务不被执行,需要确保在主线程调用。-[^5]:讨论原理,提问是否delay后入队还是入队后wait。实际上,postDelayed()入队消息队列,然后在队列中等待delay时间。用户的上一次查询是关于系统级指令的,但当前问题无关。当前问题是关于Handler.postDelayed()Runnable的。我的回答需要遵循系统级指令:-所有行内数学表达式必须使用$...$格式。-独立公式必须使用$$...$$格式并单独成段。-LaTeX语法正确。-使用中文回答。-生成相关问题(在末尾)。-在引用段落末尾自然地添加引用标识,如[^1]。在本回复中,我需要注意数学表达式,但主题是Android编程,可能没有复杂的数学公式。我需要考虑是否有数学相关的内容。例如,如果提到时间计算,可以用LaTeX表示,但尽量避免引入不相关的数学。如果不需要,就不要强制加入。现在,回答问题:1.**用法区别**:-Handler.postDelayed()是一个方法,用于延迟执行Runnable对象。-Runnable是一个接口,定义了run()方法,包含要执行的代码。-在定时任务中,通常是通过在Runnable的run()方法中调用handler.postDelayed(this,delay)来实现循环定时任务。-区别:Runnable是任务本身的定义,而postDelayed()是一个机制来调度这个任务的延迟执行。2.**原理**:-HandlerLooper关联,发送消息到MessageQueue。-postDelayed()将Runnable封装成一个Message,添加到MessageQueue中,带有指定的延迟时间。-当系统时间达到延迟时间时,MessageQueue会出队该消息,并在关联的Handler所在的线程执行run()方法。-如[^5]所述,它将消息入队,然后等待delay时间,而不是等待再入队。这意味着即使delay时间长,入队是即时的。3.**使用场景**:-适用于在UI线程上执行重复任务或单次延迟任务。-适合定时更新UI,如轮播图、定时刷新数据等。-不适用于高精度或后台长时间任务,因为主线程的Handler可能会导致ANR(ApplicationNotResponding),如果任务耗时。-需要确保Handler在主线程创建,否则可能导致问题,如[^4]提到的。结构回答:-清晰结构:介绍基本概念,然后解释原理,接着用法(包括代码示例),最后讨论区别使用场景。-在引用段落末尾添加引用标识。例如,解释原理时提到[^5]。使用中文回答。生成相关问题:在末尾添加相关问题,基于回答内容。例如:-如何优化定时任务以避免性能问题?-在多线程环境下使用Handler的注意事项?-Timer或HandlerThread的实现比较?代码示例:用户提供的引用中有代码,我可以参考,但需要用Markdown代码块格式。指令要求行内数学表达式用$...$,但代码块用```格式。数学表达式:如果谈到时间,比如delay时间,可以用LaTeX,如$\text{delay}=1000,\text{ms}$,但简单点避免混淆。可能不需要,除非必要。完整回答结构:1.简要介绍Handler.postDelayed()Runnable。2.解释原理:消息队列机制。3.展示用法:代码示例,实现定时任务。4.讨论区别:Runnable是任务,postDelayed()是调度。5.使用场景:UI相关任务,注意事项。6.在引用信息时添加标识。7.结束生成相关问题。相关问题应基于回答,比如:-Handler.postDelayed()的替代方案有哪些?-如何处理定时任务中的内存泄漏?-在后台线程中使用Handler的BestPractice?确保回答真实可靠,基于Android文档引用内容。</think> ### Handler.postDelayed() Runnable 在 Android 定时任务中的原理、用法区别 在 Android 开发中,`Handler.postDelayed()` `Runnable` 是用于实现定时任务的常用机制。它们在 UI 线程或指定线程中执行延迟或周期性任务,但它们的角色使用方式有所不同。我将基于您的引用内容,逐步解释原理、用法、区别使用场景,确保回答真实可靠。 #### 1. 基本概念原理 `Runnable` 是一个 Java 接口(在 Kotlin 中用函数式接口表示),用于定义要执行的代码块。它的核心是 `run()` 方法,其中封装了任务逻辑。例如,一个简单的 `Runnable` 任务可能是更新 UI 或执行计算。 `Handler.postDelayed()` 是一个方法,用于延迟执行一个 `Runnable` 对象。它的实现依赖于 Android 的消息处理机制: - **原理**:`Handler` `Looper` `MessageQueue` 关联。当调用 `postDelayed(Runnable r, long delayMillis)` 时,系统将 `Runnable` 封装成一个 `Message`,添加至关联线程的 `MessageQueue` 中。消息队列根据指定的延迟时间(以毫秒为单位)调度任务的执行。实际延迟是基于系统时钟:消息入队后,等待指定的延迟时间($ \text{delayMillis} $),然后在消息出队时执行 `run()` 方法[^5]。 引用示例中强调,`postDelayed()` 是将消息入队再等待延迟,而非等待再入队。这意味着任务被即时安排,延迟在队列内部处理[^5]。 关键区别:`Runnable` 定义了“任务内容”,而 `postDelayed()` 提供了“调度机制”,用于控制任务何时执行。 #### 2. 用法示例 在定时任务中,常用方式是结合两者实现循环执行:在 `Runnable.run()` 中调用 `postDelayed()` 再次调度自身,形成递归延迟任务。以下是一个完整示例(基于 Java,类似引用中的代码[^1][^2][^3])。 ```java private Handler handler = new Handler(Looper.getMainLooper()); // 在主线程创建 Handler private Runnable task; private int delay = 1000; // 延迟时间,单位毫秒,例如 $ \Delta t = 1000 \, \text{ms} $ // 开启定时任务 private void startTask() { stopTask(); // 停止现有任务,避免重复 handler.postDelayed(task = new Runnable() { @Override public void run() { // 具体任务逻辑,如打印日志或更新 UI Log.d("Timer", "任务执行..."); // 再次调用 postDelayed() 实现周期性执行(循环) handler.postDelayed(this, delay); } }, delay); } // 停止定时任务 private void stopTask() { if (task != null) { handler.removeCallbacks(task); // 从队列中移除任务 task = null; } } ``` - **用法解析**: - **启动任务**:调用 `startTask()`,它会通过 `postDelayed()` 延迟执行 `Runnable.run()`,在 `run()` 内部再次调度自身(通过 `postDelayed(this, delay)`),形成定时循环[^1][^2]。 - **停止任务**:调用 `stopTask()` 使用 `removeCallbacks()` 清除任务,避免内存泄漏[^1]。 - **注意事项**: - **线程问题**:`Handler` 必须在目标线程创建(如主线程),否则在子线程调用可能导致任务不执行(引用中提到,子线程调用主线程 Handler 的 `postDelayed()` 可能失效,需通过主线程调度[^4])。 - **延迟时间**:$ \text{delay} $ 是相对时间,从调度时刻开始计算,实际执行时间受系统负载影响,非实时精准。 #### 3. 主要区别 - **角色区别**: - `Runnable`:是任务的载体,定义了 `run()` 方法中的具体操作。它是内容的体现。 - `Handler.postDelayed()`:是任务调度器,控制任务在指定延迟后执行。它扩展了普通 `post()` 方法,增加了时间延迟功能。 - **依赖性**:`postDelayed()` 需要 `Runnable` 作为参数才能工作,即调度器依赖于任务定义。但 `Runnable` 本身独立,可通过 `Executors` 或 `Thread` 运行。 - **执行方式**:`postDelayed()` 始终在 Handler 关联的线程执行 `run()`(通常是 UI 线程),而 `Runnable` 可运行在任何线程,若直接执行。 #### 4. 使用场景优缺点 - **使用场景**: - **UI 更新定时任务**:例如每 $ 1000 \, \text{ms} $ 更新进度条或动画(主线程安全)[^1][^2]。 - **周期性操作**:如轮询网络状态或数据刷新($ \text{周期} = \text{delay} $)[^2]。 - **单次延迟任务**:如按钮点击后延迟操作(使用 `postDelayed()` 不带递归)[^3]。 - **优点**: - **简单高效**:集成在 Android 框架,资源占用低,适合短时任务。 - **线程安全**:在主线程 Handler 上运行,确保 UI 操作合法。 - **缺点**: - **不精准**:受 UI 线程阻塞影响,延迟时间可能延长(如主线程繁忙时)。 - **ANR 风险**:如果 `run()` 任务耗时较长(超过 $ 5 \, \text{秒} $),可能导致 Application Not Responding[^4]。 - **内存泄漏**:未清理任务可能导致持有 context 引用,需在 Activity 销毁时调用 `stopTask()`[^1]。 #### 5. 优化建议 - 对于后台定时任务(如服务中),考虑 `HandlerThread` 或 `ScheduledExecutorService` 替代,避免 UI 线程负载。 - 使用弱引用或 AndroidX 的 `Lifecycle` 组件管理 Handler Runnable,防止泄漏。 - 测试不同设备的延迟精度,必要时用 `SystemClock.elapsedRealtime()` 修正时间计算($ t = t_0 + \text{delay} $)[^5]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值