很久之前也写过Handler的相关文章,现在回过头去看,理解的还是较为浅薄。于是乎,决定再来深入研究一遍Handler。

首先抛出一个问题:子线程到主线程的通信方式有哪些?子线程到主线程通信的原理是什么?

你可能会回答:RxJava,Handler,EventBus,广播。但是这些表象背后的本质都是一套机制,就是Handler。可以这么说,Android线程间通信的核心就是Handler。

首先我们来看Handler的使用,具体的我就不说的,大家想必都很清楚:

子线程:handler.sendMessage()

主线程:handler.handleMessage()

大概就是通过这样的方式就实现了线程间通信,我们先从源码角度,去思考原理。Handler的源码位置在frameworks/base/core/java/android/os下面,我看的是android9.0的。因为我平时学习用的都是Mac,所以看源码的工具我用的是Sublime Text。大家一定要把系统源码下下来,直接从AS工具里面很多源码其实是隐藏的。

在Handler里面我们看到了这一行代码:

public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);
}

继续看:

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

再继续看:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);
}

重点来了,我们在sendMessageAtTime里面看到了一个数据结构:MessageQueue。翻译过来就是消息队列。那我们就顺着源码进去看一把这个数据结构:

Message mMessages;

我们在Message里面看到了维护的Message,再进去看一下Messaage的数据结构:

/*package*/ int flags;/*package*/ long when;/*package*/ Bundle data;/*package*/ Handler target;/*package*/ Runnable callback;// sometimes we store linked lists of these things
/*package*/ Message next;

Mesage里面又有一个属性:Message next。很明显了,Message是一个链表的数据结构。我们在回到sendMessageAtTime方法,发现最终调用的是enqueueMessage方法:

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

最终调用的是MessageQueue的enqueueMessage方法:

boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}if (msg.isInUse()) {throw new IllegalStateException(msg + " This message is already in use.");}synchronized (this) {if (mQuitting) {IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");Log.w(TAG, e.getMessage(), e);msg.recycle();return false;}msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}

来重点看一下这一段代码:msg.target就是Handler本身对象,很明显不能为空。msg.isInUse对应的方法是msg.markInUse,如果这条消息被标记了,那么msg.isInUse就会返回true。mQuitting标记的是发送消息线程是否退出,如果退出了就没有继续走下去的必要了。当然前面都不是重点,我们来看if-else里面逻辑:

  • p==null很明显不成立,上两行代码Message p = mMessage可见,一般情况不会是第一条消息(如果是第一条消息,走if逻辑,将msg赋给mMessages);
  • when==0成立吗?有人会说:sendMessage里面调用了sendMessageDealyed不是传了0吗?再仔细看看,sendMessageDelayed里面做了什么,SystemClock.uptimeMillis() + delayMillis,这一段代码看到没有。然后层层传递下去,所以when更准确的理解是具体时间,而不是延迟时间;
  • when<p.when成立吗?一般情况下也是不成立的。

那我们暂时只需要关注else里面代码就好了,if里面我们后面回过头再看。接下来看链表追加msg操作,我们把代码单独拿出来吧:

                Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;

就是这段代码,我估计很多人都看不懂,但是,却是核心中的核心!我们先将p赋给prev,很明显这个p是原有的msg链表,然后将p.next赋给p,这个时候就为null了,跳出循环。

再为msg.next赋值p,就是赋值null,然后将prev链表的next指向msg。这样就完成了消息的发送整个过程,可见:消息的发送本质逻辑就是将消息存储到MessageQueue持有的Message链表里面

再来看一下Handler的构造:

public Handler(Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;
}

可见,在初始化Handler的时候,Handler就和Looper建立了关联。来看下myLooper方法:

public static @Nullable Looper myLooper() {return sThreadLocal.get();
}

我们不禁要想,我们可以get到一个Looper对象,那么这个对象是何时set进来的呢?

我们来看一个类:ActivityThead。这个类在frameworks/base/core/java/android/app下。

注意Main方法:

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");// CloseGuard defaults to true and can be quite spammy.  We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Set the reporter for event logging in libcoreEventLogger.setReporter(new EventLoggingReporter());// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();//1// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.// It will be in the format "seq=114"long startSeq = 0;if (args != null) {for (int i = args.length - 1; i >= 0; --i) {if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {startSeq = Long.parseLong(args[i].substring(PROC_START_SEQ_IDENT.length()));}}}ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();//2throw new RuntimeException("Main thread loop unexpectedly exited");}

代码很多,我们只需要关注标记1和标记2处。标记1处代码点进去看下:

(Looper里面的方法)

public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}
}

继续看:

(Looper里面的方法)

private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed));
}

到这里,我们已经知道了ThreadLocal里面的Looper对象是何时set进来的了。

我们再来看标记2处,调用了Looper的loop方法。

public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();// Allow overriding a threshold with a system prop. e.g.// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'final int thresholdOverride =SystemProperties.getInt("log.looper."+ Process.myUid() + "."+ Thread.currentThread().getName()+ ".slow", 0);boolean slowDeliveryDetected = false;for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}final long traceTag = me.mTraceTag;long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;if (thresholdOverride > 0) {slowDispatchThresholdMs = thresholdOverride;slowDeliveryThresholdMs = thresholdOverride;}final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);final boolean needStartTime = logSlowDelivery || logSlowDispatch;final boolean needEndTime = logSlowDispatch;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;final long dispatchEnd;try {msg.target.dispatchMessage(msg);dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (logSlowDelivery) {if (slowDeliveryDetected) {if ((dispatchStart - msg.when) <= 10) {Slog.w(TAG, "Drained");slowDeliveryDetected = false;}} else {if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",msg)) {// Once we write a slow delivery log, suppress until the queue drains.slowDeliveryDetected = true;}}}if (logSlowDispatch) {showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);}if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}// Make sure that during the course of dispatching the// identity of the thread wasn't corrupted.final long newIdent = Binder.clearCallingIdentity();if (ident != newIdent) {Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+ Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);}msg.recycleUnchecked();}}

我们来分析一下这个方法:

  • 通过myLooper拿到主线程Looper对象和Looper对象维护的MessageQueue;
  • 通过一个死循环不断轮询消息队列(queue.next());
  • msg.target.dispatchMessage(msg)。

重点来看下步骤3吧,我们知道msg.target就是Handler对象本身,于是我们再来看Handler的dispatchMessage方法:

    public void handleMessage(Message msg) {}/*** Handle system messages here.*/public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}

diapatchMsg最终调用的是handleMessage。这样发送消息的流程到接受消息的流程就形成了闭环。回过头来,再看一下:

子线程到主线程的通信方式有哪些?子线程到主线程通信的原理是什么?

给出简要的答案:子线程通过handler向消息队列发送消息,并且通过msg.target标志位保存handler对象引用,主线程通过Looper.loop不断轮询消息队列,并最终调用发送消息的handler的handleMessage方法。其实本质问题在于MessageQueue消息队列里面的Message链表数据结构,这一块在堆内存里面,属于线程共享区。

我们经常会听到Handler内存泄漏的问题,那么问题来了:Handler内存泄漏的原因是什么?

我们上面分析了这么长的源码,我们来看一下引用链:

MainActivity <- Handler <-Message(msg.target) <- MessageQueue <- Looper。

我们知道只要应用程序是活的,Looper对象都会在,而Handler是Acticity的内部类,内部类默认持有外部类引用,这样一来,引用链就不会断。根据GC可达性算法,MainActivity就不会被回收,引发内存泄露。注意:内存泄漏发生在子线程发送消息,而主线程还未收到消息的时候退出Acticity,比如说发送一个delay消息。如果主线程收到消息了,就会执行msg的recycleUnchecked()方法:

void recycleUnchecked() {// Mark the message as in use while it remains in the recycled object pool.// Clear out all other details.flags = FLAG_IN_USE;what = 0;arg1 = 0;arg2 = 0;obj = null;replyTo = null;sendingUid = -1;when = 0;target = null;callback = null;data = null;synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {next = sPool;sPool = this;sPoolSize++;}}
}

会将target置null,这样引用链也会在Handler <- Message这里断掉。

再探Handler(上)(Handler核心原理最全解析)相关推荐

  1. 速递 | 在线教育行业 12 大核心场景案例全解析!

    以神策 5 年来服务多家在线教育类客户的经验而言,大多在线教育企业较为重视"用户""内容"两大主体,在这两大主体下关注用户来源.学习目的.产品交互.学习偏好.课 ...

  2. 在线教育行业 12 大核心场景案例全解析!

    以神策 5 年来服务多家在线教育类客户的经验而言,大多在线教育企业较为重视"用户""内容"两大主体,在这两大主体下关注用户来源.学习目的.产品交互.学习偏好.课 ...

  3. 收藏!Java 500 道核心面试题全解析

    每个技术人都有个大厂梦,我觉得这很正常,并不是饭后的谈资而是每个技术人的追求.像阿里.腾讯.美团.字节跳动.京东等等的技术氛围与技术规范度还是要明显优于一些创业型公司/小公司,如果说能够在这样的公司锻 ...

  4. Android权限管理原理,Android手机Root授权原理细节全解析

    首先关于Root的方式,这里不做详解,可以有很多漏洞,比如利用uid溢出后归为0,得到Root权限,然后操作文件系统等. 手机Root后,最重要的是,给手机安装了su程序和superuser apk. ...

  5. android handler的机制和原理_一文搞懂handler:彻底明白Android消息机制的原理及源码

    提起Android消息机制,想必都不陌生.其中包含三个部分:Handler,MessageQueue以及Looper,三者共同协作,完成消息机制的运行.本篇文章将由浅入深解析Android消息机制的运 ...

  6. RxSwift学习--核心逻辑再探

    前言 通过上一篇内容RxSwift学习--核心逻辑初探,对RxSwift有了些初步的认知,下面通过源码来看一下RxSwift到底有多骚 RxSwift核心逻辑再探 先把上篇中的例子代码搬过来: //第 ...

  7. 简历上写精通Nginx/OpenResty详解,Nginx的核心原理应该不过分吧

    Nginx的核心原理 本节为大家介绍Nginx的核心原理,包含Reactor模型.Nginx的模块化设计.Nginx的请求处理阶段. 虽然本节的知识有一定的理论深度,但是与另一个有名的Java底层通信 ...

  8. Android Handler消息机制原理最全解读(持续补充中)

     本文主要详细去解读Android开发中最常使用的Handler,以及使用过程中遇到的各种各样的疑问. Handler  在Android开发的过程中,我们常常会将耗时的一些操作放在子线程(work ...

  9. Android中handler的使用及原理---学习笔记

    Handler类的基本介绍以及使用: Android中UI操作是线程不安全的操作,如果有多个线程并发操作UI组件,就会出现线程安全问题,所以Android中制定了一个规则:在Android中只允许主线 ...

最新文章

  1. 2019.07.16
  2. AndroidMVP
  3. 职场七种最致命的想法
  4. 红橙Darren视频笔记 手写ButterKnife(Android Studio4.2.2 gradle-6.7.1 )
  5. eclipse下生成Java类图和时序图,生成UML图
  6. 《暗时间》这本书内容丰富,思路明晰,是学习思维方法的好参考书
  7. oracle client安装与配置
  8. Halcon 仿射变换
  9. 服务器安装虚拟声卡,虚拟声卡安装使用 虚拟声卡注意事项
  10. 工业嵌入式移动软件设计
  11. ElasticSearch Cause: Cluster state has not been recovered yet, cannot write to the [null] index
  12. 计划的主体部分应有哪些内容_知道智慧树应用写作技能与规范网课答案
  13. 2020春招 / 2021秋招阿里、腾讯、字节、快手、美团 JAVA 开发岗面试高频问题总结
  14. Linux积累 - scp 远程复制 加端口
  15. 【mmdeploy】mmseg转ONNX/TensorRT,附推理代码
  16. 小白如何轻松建站?(详细教程)
  17. iOS仿微信聊天页面长按气泡弹窗
  18. 本周最新文献速递20210516
  19. Web安全之SQL注入漏洞学习(七)-堆叠注入
  20. HDU2255-KM

热门文章

  1. 随机信号处理笔记 - ING
  2. 企业打造顶级研究院的最优路径是什么?
  3. 【论文阅读】Underwater Image Enhancement: Using Wavelength Compensation and Image Dehazing(WCID)
  4. android工程师培训价格,徐州android工程师培训基地
  5. java笔试题:海量数据找最大或最小的k个数(堆排序)
  6. 安卓支持App内文字翻译
  7. 苹果x电池容量_苹果iPhone X电池老化严重:越来越多用户收到性能劣化影响
  8. 蚁群聚类算法matlab,【转】蚁群聚类算法及其源码[matlab]
  9. 整理一些es6的基础东西。记录成长
  10. Python小白到老司机,快跟我上车!基础篇(十八)