Handler的主要作用是收发消息和切线程

功能一:收发消息

简单流程介绍

希望你看完这篇文章后也可以把流程自己讲出来,并且每个环节还可以讲出很多细节

他的消息机制离不开Looper、MessageQueue

  • 其中 Looper 每个线程只能持有一个,主要负责循环查看 MessageQueue 里面是否有 msg 需要处理,并将需要处理的消息取出,交给 Handler
  • MessageQueue 是负责存放消息的,数据结构是一个单链表,这样就可以方便地插入或删除 msg

具体流程一般是:

  1. Handler 发送一条msg => 本质是向MessageQueue里插入一条msg,插入时候的依据是msg.when => SystemClock.uptimeMillis() + delayMillis

  2. 这条msgMessageQueue.next()返回并交给Handler去处理

    next()会在有同步屏障(msg.target==null)的时候遍历查找并返回最早的异步消息,并在移除屏障后,从头取出并返回消息

  3. Handler.dispatchMessage(msg)会优先处理msg.callback,如果msg.callback为空,就处理Handler.mCallback,然后处理是msg本身

    msg.callback是在调用Handler.post(Runnable)时,里面的Runnable(runOnUIThreadview.post(Runnable)也用的是Handler.post(Runnable)Runnable是一样的)

    这是在不新增Handler的情况下,另一种调用Handler的方式(如下)

class MyHandlerCallBack: Handler.Callback {
override fun handleMessage(msg: Message?): Boolean {
TODO("Not yet implemented")
}
}

可以看到他也有handleMessage这个方法

Looper是个死循环

(1)死循环的目的

目的就是让主线程一直卡在这个死循环里面

因为Looper的作用就是在这个死循环里面取出消息,然后交给Handler处理

Android的生命周期,你了解的onCreate,onStop,onStart...... 等等都是由Handler来处理的,都是在这个死循环里面运行的

所以什么Looper死循环卡死主线程怎么办???

必须给我卡住!!!不卡住的话,消息就没法整了!!!

看下Android启动的时候的源码

Activitythread.java >> main()

public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}

想想写java的时候,main最后一行执行完了,不就彻底玩完了嘛!!!

(2)死循环里干了啥

其实想都不用想,一直在看MessageQueue里面有没有消息呗,太简单了!调用的就是MessageQueue.next()

看下源码 MessageQueue.java >> loop()

       for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
}

很简单,next()返回Messagemsg.target.dispatchMessage() 处理Message

但是队列里没消息就会返回null,这是错误的!!!具体往下看

MessageQueue是个单链表

1.插队

Handler发消息的时候,目的就是对msg经过一系列操作,最终也只是调用enqueueMessage插入队列而已

看下源码 Handler>>enqueueMessage()

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

return直接调用Message的插入队列方法

2.出队

出队就是next()方法,之前已经见过了

(1)时间顺序

Message是按时间排序的,也就是msg.when => SystemClock.uptimeMillis() + delayMillis

msg.whenMessage期望被处理的时间

SystemClock.uptimeMillis()是开机到现在的时间,delayMills是延迟时间,这个在sendMessageDelayed方法里直接可以直接传参

next()就是按照时间顺序处理MessageQueue里面的消息的

但是next()里有个概念叫 同步屏障

(2)同步屏障

同步屏障,就是说,平时MessageQueue都是处理同步消息,也就是按顺序来,一个个出队

同步屏障就是阻挡同步消息的意思

就是msg.target == null 的时候,MessageQueue就会去找msg.isAsynchronous()返回truemsg

isAsynchronous,没错 ! 这是异步消息,就是优先级很高,需要立刻执行的消息,比如:更新View

(3)阻塞

值得注意的是,讲Looper的时候,源码next()后面官方给我们注释了 // might block可能阻塞,也就是说可能这个next()也许会执行好久

next()会阻塞?,什么时候阻塞?

now < msg.when也就是时间还没到,期望时间大于现在的时间

(4)退出

另外看第一行,只有ptr == 0,才会返回null

所以上面才说next()不会因为没消息而返回null,原来返回null的时候在这呢!

看下源码,MessageQueue.java >> next()

 @UnsupportedAppUsage
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
} int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
} nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
...
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
} // Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
...
}
...
}
}

代码简略了还是有点多,别着急,慢慢看

pre什么时候就是0了呢?

答:quit()了之后

看下源码,Looper.java

   public void quit() {
mQueue.quit(false);
} public void quitSafely() {
mQueue.quit(true);
}

可以看到只是一个传参不同而已,下面看看这个参数是干嘛的

看下源码,MessageQueue.java >> quit()

 void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
} synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true; if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
} // We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}

可以看到,safe == true,就移除未来的Message

safe == false,就移除所有的Message

mQuiting变成了true,记住他我们一会儿会用到

而改变ptr的地方在这里

next()里面

里面有个dispose,找不到可以ctrl+F找一下

这里只有在mQuiting == true的时候,才会调用

这就是改mPtr的地方,然后下次next()的时候就会返回null

Handler流程

(1)post过来的msg

我们已经知道了在Looper的死循环里面,会将next()返回的msg交给Handler,调用dispatchMessage()

dispatchMessage()里面会先判断msg是不是被post过来的,因为post要执行的逻辑在msg.callback里面,callback是一个Runnable,这可能不是很好理解

你可以想想runOnUIThread(Runnable),这里的Runnable就是上面的callback

他们都是调用了Handler.post(Runnable)

至于为啥起个名叫callback,我也纳闷儿

(2)send过来的msg

这些msg是会的逻辑是你重写的handleMessage那里的逻辑

如果实现了Handler.Callback这个Interface,就会处理mCallbackhandleMessage

而不是Handler自己的handleMessage

这是一个优先级策略,没什么好奇怪的

我们看下源码 => Handler.java >> dispatchMessage()

    public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

这就是Handler的消息机制了

接下来我们讲讲Handler的另一个功能,切线程

功能二:切线程

Handler切线程使用的是ThreadLocal

(1)ThreadLocal

ThreadLocal是线程里面的一个数据储存类,用法类似mapkey就是thread

但是他没有提供,根据key来找ThreadLocalValues的方法,所以暴露的api就只能让你去get当前线程的ThreadLocalValues对象而已,就是key——你自己没法作为参数传进去,只能是currentThread

如果你没用过ThreadLocal,我给你举个例子

fun main() {
val booleanThreadLocal = ThreadLocal<Boolean>()
booleanThreadLocal.set(true) println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}") thread(name = "thread#001") {
booleanThreadLocal.set(false)
println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")
} thread(name = "thread#002") {
println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")
}
}

结果是这样的:你可以自己运行看看

in Thread[main] booleanThreadLocal value = true
in Thread[thread#001] booleanThreadLocal value = false
in Thread[thread#002] booleanThreadLocal value = null
(2)切线程的细节

话说回来,Handler怎么通过ThreadLocal切线程的呢?

答案是:Looper是放在ThreadLocal里的

回顾片头的流程,Handler将消息插入MessageQueue,然后Looper取出来,再还给Handler,这种设计不止是为了让msg可以按顺序处理,还可以让外部接口只有Handler

最关键的是,LooperHandler的触发关系只有Looper触发HandlerHandler不会触发Looper

因此Handler把消息放在MessageQueue之后,就在等着Looper来给自己派发任务(msg

举个例子:

线程A调用主线程Handler发一个消息

Handler将这个消息插入MessageQueue,此时其实还在线程A

只有Loopernext()调用msg.target.dispatchMessage()时,就变成了主线程

仅仅是因为Looper主线程 而已

OVER

Android - Handler原理的更多相关文章

  1. Android Handler 原理

    在android中提供了一种异步回调机制Handler,使用它,我们可以在完成一个很长时间的任务后做出相应的通知 handler基本使用: 在主线程中,使用handler很简单,new一个Handle ...

  2. android handler工作原理

    android handler工作原理 作用 便于在子线程中更新主UI线程中的控件 这里涉及到了UI主线程和子线程 UI主线程 它很特别.通常我们会认为UI主线程将页面绘制完成,就结束了.但是它没有. ...

  3. Handler 原理分析和使用(二)

    在上篇 Handler 原理分析和使用(一)中,介绍了一个使用Handler的一个简单而又常见的例子,这里还有一个例子,当然和上一篇的例子截然不同,也是比较常见的,实例如下. import andro ...

  4. Handler 原理分析和使用(一)

    我为什么写Handler,原因主要还在于它在整个 Android 应用层面非常之关键,他是线程间相互通信的主要手段.最为常用的是其他线程通过Handler向主线程发送消息,更新主线程UI. 下面是一个 ...

  5. Handler 原理分析和使用之HandlerThread

    前面已经提到过Handler的原理以及Handler的三种用法.这里做一个非常简单的一个总结: Handler 是跨线程的Message处理.负责把Message推送到MessageQueue和处理. ...

  6. Android Handler 机制总结

    写 Handler 原理的文章很多,就不重复写了,写不出啥新花样.这篇文章的主要是对 handler 原理的总结. 1.Android消息机制是什么? Android消息机制 主要指 Handler ...

  7. 深入理解之 Android Handler

    深入理解之 Android Handler   一,相关概念 在Android中如果通过用户界面(如button)来来启动线程,然后再线程中的执行代码将状态信息输出到用户界面(如文本框),这时候就会抛 ...

  8. Android Handler leak 分析及解决办法

    In Android, Handler classes should be static or leaks might occur, Messages enqueued on the applicat ...

  9. Android Handler练习

    package com.example.myact12; import java.util.Random; import android.support.v7.app.ActionBarActivit ...

随机推荐

  1. 深入了解typeof与instanceof的使用场景及注意事项

    JavaScript中的数据类型分为两类,undefined,number,boolean,string,symbol,bigint,null[1]组成的基础类型和Object.Function.Ar ...

  2. 9.[完]其他常用的rabbitmq的参数和设置

    作者 微信:tangy8080 电子邮箱:914661180@qq.com 更新时间:2019-08-12 20:42:25 星期一 欢迎您订阅和分享我的订阅号,订阅号内会不定期分享一些我自己学习过程 ...

  3. 解决宝塔面板没有命令行问题 && 查看宝塔面板项目环境

    # 宝塔面板没有命令行,无法查看错误输出 利用ssh.比如xshell,MObaxtern .输入ip,username,password就可以进入服务器的命令行. # 查看项目的环境 服务器默认的p ...

  4. vue 如何重绘父组件,当子组件的宽度变化时候

    vue 如何重绘父组件,当子组件的宽度变化时候 vue & dynamic el-popover position demo https://codepen.io/xgqfrms/pen/wv ...

  5. Apple Watch Series 6 屏幕误触放大后无法还原问题和解决方案

    Apple Watch Series 6 屏幕误触放大后无法还原问题和解决方案 shit Apple,只能放大,不能缩小! 解决方案 关闭缩放功能 https://support.apple.com/ ...

  6. KMP 算法 & 字符串查找算法

    KMP算法 Knuth–Morris–Pratt algorithm 克努斯-莫里斯-普拉特 算法 algorithm kmp_search: input: an array of character ...

  7. Learning JavaScript with MDN (call, apply, bind)

    Learning JavaScript with MDN (call, apply, bind) call, apply, bind Object.prototype.toString() 检测 js ...

  8. Apache 低版本不支持 WebSocket

    Apache 低版本不支持 WebSocket Apache HTTP Server Version 2.4 Apache Module mod_proxy_wstunnel https://http ...

  9. FileReader, readAsText

    readastext filereader FileReader.readAsText() https://developer.mozilla.org/zh-CN/docs/Web/API/FileR ...

  10. CentOS 7.6.1810 运行pupperteer

    故障排除 安装puppeteer,使用cnpm 解决依赖 $ yum -y update $ yum install -y pango libXcomposite libXcursor libXdam ...