handler笔记(sendXXpostXX延迟处理)

Posted 夜尽天明89

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了handler笔记(sendXXpostXX延迟处理)相关的知识,希望对你有一定的参考价值。

关于handler的post、延迟消息这2个点有点忘记了。这两天重看了下handler源码,学习笔记总结下

之前写过一篇,handler源码分析

这里只说下架 post、延迟消息。

测试代码很简单:(Kotlin)

    private var mHandler = object : Handler() 

        override fun handleMessage(msg: Message?) 
            super.handleMessage(msg)

            Log.e("收到消息 $msg?.what","时间:$System.currentTimeMillis()")
        
    

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mHandler.sendEmptyMessage(1)

        mHandler.sendEmptyMessageDelayed(2,5*1000)

        mHandler.post(Runnable()

            Log.e("handler","post 线程:$Thread.currentThread()")

        )

        mHandler.postDelayed(

            Log.e("handler","postDelayed 线程:$Thread.currentThread()")

        ,10*1000)

    

对应日志:

2021-04-06 09:15:07.237 E/收到消息 1: 时间:1617671707237
2021-04-06 09:15:07.237 E/handler: post 线程:Thread[main,5,main]

2021-04-06 09:15:12.204 E/收到消息 2: 时间:1617671712204
2021-04-06 09:15:17.204 E/handler: postDelayed 线程:Thread[main,5,main]

结论:
1、利用post发消息,可以直接跟回调处理代码
2、post完成后,处理代码的是主线程

postXX方法,最后也会都走到 sendMessageAtTime

引申个问题,handler 是怎么保证,延迟操作的正确性的?或者说,如果我指定了90秒后打印一句话,在90面之内,我改了手机系统时间,还会正常触发打印吗?答案是肯定的,会正常打印。是怎么保证延迟时间的正确性的?看下源码:

sendEmptyMessageDelayed、postDelayed 这些延迟代码
最后都会走到

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) 
	if (delayMillis < 0) 
		delayMillis = 0;
	
 	return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

可以看到2点:
1、如果我们制定了延迟操作的时间是负数。即:假设现在是10点,我要在9点发个消息,是无法实现的,延迟时间,最后都会被处理成非负数
2、延迟时间,前面加了个 SystemClock.uptimeMillis() ,这个明显不是时间戳

进去看下

/**
* Returns milliseconds since boot, not counting time spent in deep sleep.
* 返回自启动以来的毫秒数,不计算深度睡眠的时间
* @return milliseconds of non-sleep uptime since boot.
*/
@CriticalNative
native public static long uptimeMillis();

也就是说,用handler发出的延迟消息,是加在 手机启动时间(单位:毫秒) 上的,和手机的系统时间无关。这样的话,不管怎么改手机显示时间,也不会影响延迟消息的正确,当然,深度睡眠情况要单独考虑,不过在现实中,也几乎用不到深度睡眠情况

操作验证:
将代码改成:(send的延迟改成90秒)

mHandler.sendEmptyMessage(1)

mHandler.sendEmptyMessageDelayed(2,90*1000)

mHandler.post(Runnable()

	Log.e("handler","post 线程:$Thread.currentThread()")

)

mHandler.postDelayed(

	Log.e("handler","postDelayed 线程:$Thread.currentThread()")

,10*1000)

启动APP后(上午9点多),切换到系统设置里,把时间改成未来时间,我是改成了下午1点多。查看日志:

2021-04-06 09:17:40.831 E/收到消息 1: 时间:1617671860831
2021-04-06 09:17:40.831 E/handler: post 线程:Thread[main,5,main]

2021-04-06 09:17:50.842 E/handler: postDelayed 线程:Thread[main,5,main]
2021-04-06 13:19:02.348 E/收到消息 2: 时间:1617686342348

因为改了手机系统时间,时间戳也变了。如果算下 “收到消息”后面的时间戳差值,是4个多小时。但是根据其他表的观察,“收到消消息” 1和2的时间差,近90秒。

再次启动(这个时候,手机时间是未来时间),切换到系统设置,把时间改回来。查看日志:

2021-04-06 13:19:47.238 E/收到消息 1: 时间:1617686387238
2021-04-06 13:19:47.238 E/handler: post 线程:Thread[main,5,main]

2021-04-06 13:19:58.112 E/handler: postDelayed 线程:Thread[main,5,main]
2021-04-06 09:21:27.356 E/收到消息 2: 时间:1617672087356

可以看到,时间差对的上。

注:handler的延迟操作,不一定的正好的指定时间差,会有略微的偏差。即:发送消息1,延后5秒后发送消息2,1和2的差值,不一定是正好的5秒

接下来,需要单独说下 postAtTime 这个方法。这个方法是特别的,所有我上面才会说:
1、postXX方法,最后也会都走到 sendMessageAtTime
2、sendEmptyMessageDelayed、postDelayed 这些延迟代码,最后都会走到 sendMessageDelayed。

先看下源码:

    /**
     * Causes the Runnable r to be added to the message queue, to be run
     * at a specific time given by <var>uptimeMillis</var>.
     * <b>The time-base is @link android.os.SystemClock#uptimeMillis.</b>
     * Time spent in deep sleep will add an additional delay to execution.
     * The runnable will be run on the thread to which this handler is attached.
     *
     * @param r The Runnable that will be executed.
     * @param uptimeMillis The absolute time at which the callback should run,
     *         using the @link android.os.SystemClock#uptimeMillis time-base.
     *  
     * @return Returns true if the Runnable was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.  Note that a
     *         result of true does not mean the Runnable will be processed -- if
     *         the looper is quit before the delivery time of the message
     *         occurs then the message will be dropped.
     */
    public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) 
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    

多余的翻译,我就不复制了,就说2个单词:
specific:特殊的,特定的;明确的;详细的
absolute:绝对的

这个方法,有2个特别的地方:
1、时间是特定的,需要明确指定;2、没有延迟时间判负情况。
注意到了吗?之前的方法,会有延迟时间小于0时,置为0的操作。这里没有,也就是说,传进来负数,也会被直接使用

测试代码:

mHandler.postAtTime(

	val sdf = SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒")
	
	Log.e(
		"handler 延迟 5",
		"postAtTime $sdf.format(Date(System.currentTimeMillis())) ; 线程:$Thread.currentThread()"
	)

, 5 * 1000)
//--------------------------
mHandler.postAtTime(
	
	val sdf = SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒")
	
	Log.e(
		"handler 延迟 -5",
		"postAtTime $sdf.format(Date(System.currentTimeMillis())) ; 线程:$Thread.currentThread()"
	)
	
, -5 * 1000)
//--------------------------
mHandler.postAtTime(
	
	val sdf = SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒")
	
	Log.e(
		"handler 手机启动时间延迟 5",
		"postAtTime $sdf.format(Date(System.currentTimeMillis())) ; 线程:$Thread.currentThread()"
	)
	
, SystemClock.uptimeMillis() + 5 * 1000)
//--------------------------
mHandler.postAtTime(
	
	val sdf = SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒")
		
	Log.e(
		"handler 手机启动时间延迟 -5",
		"postAtTime $sdf.format(Date(System.currentTimeMillis())) ; 线程:$Thread.currentThread()"
	)
	
, SystemClock.uptimeMillis() - 5 * 1000)

日志:

2021-04-06 10:27:20.597 E/handler 延迟 -5: postAtTime 2021年04月06日 10时27分20秒 ; 线程:Thread[main,5,main]

2021-04-06 10:27:20.597 E/handler 延迟 5: postAtTime 2021年04月06日 10时27分20秒 ; 线程:Thread[main,5,main]

2021-04-06 10:27:20.598 E/handler 手机启动时间延迟 -5: postAtTime 2021年04月06日 10时27分20秒 ; 线程:Thread[main,5,main]

2021-04-06 10:27:25.569 E/handler 手机启动时间延迟 5: postAtTime 2021年04月06日 10时27分25秒 ; 线程:Thread[main,5,main]

我是在 27分20秒的时候启动的APP,触发打印日志。(这里多说句:视觉上,打印日志会比界面展示快一点,这是对的,因为界面绘制需要时间,打印日志,是onCreate里直接打印的)。

由此可得出结论:
1、调用 postAtTime ,需要自己明确指定时间;
2、时间可以传负数(可以的意思是:传负数 不报错、不崩溃)
3、延迟时间的基础,必须是基于 APP启动时间
4、如果传了错误时间(负数时间、在启动时间上减去一个时间),会立刻走回调方法,延迟操作不起任何作用

最后:在上面的代码中,我尝试把 “handler 手机启动时间延迟 5”这段代码的延迟时间改成了 System.currentTimeMillis() + 5 * 1000。启动后,就再没看到日志。应该是延迟时间非常长了(个人猜测,请自行验证)。

以上是关于handler笔记(sendXXpostXX延迟处理)的主要内容,如果未能解决你的问题,请参考以下文章

Android 用主线程的Handler 延迟执行任务

hbase笔记

Web Api 2 Controller 和 Handler 之间有 5 秒的延迟

Handler相关

handler message messagequeue详解

《世界是数字的》读书笔记 四