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. docker+prom+grafana+altermanager

    docker基础 docker run -it --name centos -v $HOME:/tmp -p 8080:8080 centos docker inspect container #查看 ...

  2. woj1008feedinganimals2-贪心-网络流

    title: woj1008feedinganimals2-贪心-网络流 date: 2020-03-07 categories: acm tags: [acm,woj,网络流,贪心] 中等题. 标准 ...

  3. leetcode 36 有效的数独 哈希表 unordered_set unordersd_map 保存状态 leetcode 37 解数独

    leetcode 36 感觉就是遍历. 保存好状态,就是各行各列还有各分区divide的情况 用数组做. 空间小时间大 class Solution { public: bool isValidSud ...

  4. Steam 钓鱼模拟器

    Steam 钓鱼模拟器 Fishing Planet Fishing Planet 是一个独特和高度现实的在线第一人称多人钓鱼模拟器,由狂热的钓鱼爱好者钓鱼给你带来实际钓鱼充分刺激开发! 选择你的诱饵 ...

  5. Apple Watch Series 6 无法使用截屏问题和解决方案

    Apple Watch Series 6 无法使用截屏问题和解决方案 shit Apple,无法使用截屏, TMD 根本就不存在 相机胶卷 ! 不好使 解决方案 ??? https://support ...

  6. Fetch & Headers & CSRF

    Fetch & Headers & CSRF https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetc ...

  7. MacBook Pro 关闭触控板

    MacBook Pro 关闭触控板 https://support.apple.com/zh-cn/HT204895 https://support.apple.com/zh-cn/HT203171 ...

  8. 听说USDN最近很火,它究竟是什么?

    最近USDN在各大社区沸沸扬扬,甚至很多人都说USDN将会打破稳定币市场的格局,那么USDN究竟是怎样的一种稳定币呢?小编今天就帮助大家了解一下,新一代算法型稳定币USDN. USDN是基于公链NGK ...

  9. NGK项目与其他项目相比有哪些优势?

    一个项目运行这么久,难免不被其他项目比来比去.NGK项目之所以能被很多人关注,是因为NGK具有独特的优势,NGK具有很高的性能,在智能合约上有多种应用,而且NGK具有独特的跨链技术.转账没有手续费,在 ...

  10. BGV暴涨千倍,未来或将超越YFI领跑DeFi全场!

    毫无疑问,YFI在2020年上半年以一己之力掀翻了DeFi市场的热潮.迄今为止,YFI的新鲜资讯从不缺席,最近也是频频登上各大知名媒体热搜.其币价远远超过比特币价格,也让资本市场注意到DeFi市场原来 ...