热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

linux线程handler,Handler从源码角度理解

上一个文章讲解了Handler的基本使用,同时也有一些问题没有解决,本篇带你从源码的角度理解。首先让我们来看看Handler的构造方法:i

上一个文章讲解了Handler的基本使用,同时也有一些问题没有解决,本篇带你从源码的角度理解。

首先让我们来看看Handler的构造方法:

这三个构造方法都用@hide修饰,这表明这三个都不是我们开发者可以调用的。

接下来我们来看其他几个:

/**

* Default constructor associates this handler with the {@link Looper} for the

* current thread.

*

* If this thread does not have a looper, this handler won't be able to receive messages

* so an exception is thrown.

*/

public Handler() {

this(null, false);

}

/**

* Constructor associates this handler with the {@link Looper} for the

* current thread and takes a callback interface in which you can handle

* messages.

*

* If this thread does not have a looper, this handler won't be able to receive messages

* so an exception is thrown.

*

* @param callback The callback interface in which to handle messages, or null.

*/

public Handler(Callback callback) {

this(callback, false);

}

/**

* Use the provided {@link Looper} instead of the default one.

*

* @param looper The looper, must not be null.

*/

public Handler(Looper looper) {

this(looper, null, false);

}

/**

* Use the provided {@link Looper} instead of the default one and take a callback

* interface in which to handle messages.

*

* @param looper The looper, must not be null.

* @param callback The callback interface in which to handle messages, or null.

*/

public Handler(Looper looper, Callback callback) {

this(looper, callback, false);

}

通过代码我们发现所有的构造方法最终都会调用到以下两个:

1.当构造Handler时候没有主动传递Looper

/**

* Use the {@link Looper} for the current thread with the specified callback interface

* and set whether the handler should be asynchronous.

*

* Handlers are synchronous by default unless this constructor is used to make

* one that is strictly asynchronous.

*

* Asynchronous messages represent interrupts or events that do not require global ordering

* with respect to synchronous messages. Asynchronous messages are not subject to

* the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.

*

* @param callback The callback interface in which to handle messages, or null.

* @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for

* each {@link Message} that is sent to it or {@link Runnable} that is posted to it.

*

* @hide

*/

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 that has not called Looper.prepare()");

}

mQueue = mLooper.mQueue;

mCallback = callback;

mAsynchronous = async;

}

2.当构造函数中传递了Looper

/**

* Use the provided {@link Looper} instead of the default one and take a callback

* interface in which to handle messages. Also set whether the handler

* should be asynchronous.

*

* Handlers are synchronous by default unless this constructor is used to make

* one that is strictly asynchronous.

*

* Asynchronous messages represent interrupts or events that do not require global ordering

* with respect to synchronous messages. Asynchronous messages are not subject to

* the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.

*

* @param looper The looper, must not be null.

* @param callback The callback interface in which to handle messages, or null.

* @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for

* each {@link Message} that is sent to it or {@link Runnable} that is posted to it.

*

* @hide

*/

public Handler(Looper looper, Callback callback, boolean async) {

mLooper = looper;

mQueue = looper.mQueue;

mCallback = callback;

mAsynchronous = async;

}

从代码的注释中可以看出传递Looper与不传递Looper区别是如果没有传递则使用当前Handler在哪个线程中声明就使用哪个线程的Looper。

接下来再看Hanlder中几个重要属性

从运行接口看出如果我们在构建Handler时传递了Callback则发送消息后不会回调Handler的handleMessage方法。

如果我们的Callback的hanldeMessage方法返回的结果是false呢的结果是啥子呢? 不错您答对了如果返回false会调用handler的handleMessage方法

我们用代码来证码,代码最会说实话:

如果是空则判断handler的mCallback属性是否为空,如果不为空则执行mCallback的handleMessage方法且如果返回true则return否则调用Handler的hanleMesssage。

我们再来看另外两个属性,mLooper和mQueue,要想弄懂这两个属性我们先要弄明白Handler是如何发送和处理消息的。处理消息的流程在上面我们已经讲明白了,下面我们讲发送消息的流程。

上一篇我们讲过发送消息的两种post和sendMessage。我们先来看post

这下就很清楚了三,runable参数设置给了Message的callback所有,所有调用post方法不会调用Handler的handleMessage方法。

接下来我们再看sendMessage方法:

/**

* Enqueue a message into the message queue after all pending messages

* before the absolute time (in milliseconds) uptimeMillis.

* The time-base is {@link android.os.SystemClock#uptimeMillis}.

* Time spent in deep sleep will add an additional delay to execution.

* You will receive it in {@link #handleMessage}, in the thread attached

* to this handler.

*

* @param uptimeMillis The absolute time at which the message should be

* delivered, using the

* {@link android.os.SystemClock#uptimeMillis} time-base.

*

* @return Returns true if the message 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 message will be processed -- if

* the looper is quit before the delivery time of the message

* occurs then the message will be dropped.

*/

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);

}

我们来看这个方法中有一个uptimeMillis那这个时间时用来干嘛的呢,这个时间用来指定发送这个消息的时间,相当于一种延时效果。

现在我们再来看将消息加入消息队列的方法:

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();//设置当前Message是正在使用标志位

msg.when = when;//设置message的发送时间

Message p = mMessages;//获取当前的message

boolean needWake;

if (p == null || when == 0 || 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

break;

}

if (needWake && p.isAsynchronous()) {

needWake = false;

}

}

msg.next = p; // invariant: p == prev.next

prev.next = msg;

}

// We can assume mPtr != 0 because mQuitting is false.

if (needWake) {

nativeWake(mPtr);

}

}

return true;

}

1.如果当前msg的target为Null就会抛出异常这是为什么呢?因为target就是处理这个消息的Handler如果都没有人处理这个消息那这个消息就是不正确的消息。

2.如果当前消息正在使用中代表正在队列中或者马上被处理,也会抛出异常

3.当前handler已经没有与线程绑定也会抛出异常

4.根据发送时间来放置Message

5.调用native方法来唤醒Linux的epoll(消息循环)

从源码中我们可以看出MessageQuene中并没有任何保存Message的集合或者数组,其实这个消息队列的实现是通过一个链表来的每一个Message相当于一个结点,它是一个单向链表根据发送时间来排序,所以在牵涉到Message的时候又牵涉到了数据结构和算法,这个会在以后讲。

现在消息已经发送给并且保存到MessageQuene了但是又是如何将消息取出来的呢?我们接着往下面看MessageQuene有下面这个方法:

}

Message next() {

// Return here if the message loop has already quit and been disposed.

// This can happen if the application tries to restart a looper after quit

// which is not supported.

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) {

// Try to retrieve the next message. Return if found.

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

// 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 {

// Got a message.

mBlocked = false;

if (prevMsg != null) {

prevMsg.next = msg.next;

} else {

mMessages = msg.next;

}

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;

}

// If first time idle, then get the number of idlers to run.

// Idle handles only run if the queue is empty or if the first message

// in the queue (possibly a barrier) is due to be handled in the future.

if (pendingIdleHandlerCount <0

&& (mMessages &#61;&#61; null || now

pendingIdleHandlerCount &#61; mIdleHandlers.size();

}

if (pendingIdleHandlerCount <&#61; 0) {

// No idle handlers to run. Loop and wait some more.

mBlocked &#61; true;

continue;

}

if (mPendingIdleHandlers &#61;&#61; null) {

mPendingIdleHandlers &#61; new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];

}

mPendingIdleHandlers &#61; mIdleHandlers.toArray(mPendingIdleHandlers);

}

// Run the idle handlers.

// We only ever reach this code block during the first iteration.

for (int i &#61; 0; i

final IdleHandler idler &#61; mPendingIdleHandlers[i];

mPendingIdleHandlers[i] &#61; null; // release the reference to the handler

boolean keep &#61; false;

try {

keep &#61; idler.queueIdle();

} catch (Throwable t) {

Log.wtf(TAG, "IdleHandler threw exception", t);

}

if (!keep) {

synchronized (this) {

mIdleHandlers.remove(idler);

}

}

}

// Reset the idle handler count to 0 so we do not run them again.

pendingIdleHandlerCount &#61; 0;

// While calling an idle handler, a new message could have been delivered

// so go back and look again for a pending message without waiting.

nextPollTimeoutMillis &#61; 0;

}

}

首先我们来看第一个if判断

它是MessageQuene中的一个属性&#xff0c;是提供给native方法使用的

如果mPtr为0则返回null。那么mPtr是什么&#xff1f;值为0又意味着什么&#xff1f;在MessageQueue构造方法中调用了native方法并返回了mPtrmPtr &#61; nativeInit();&#xff1b;在dispose()方法中将其值置0mPtr &#61; 0;并且调用了nativeDestroy()。而dispose()方法又在finalize()中被调用。另外每次mPtr的使用都调用了native的方法&#xff0c;其本身又是long类型&#xff0c;因此推断它对应的是C/C&#43;&#43;的指针。因此可以确定&#xff0c;mPtr为一个内存地址&#xff0c;当其为0说明消息队列被释放了。

我们再看其中的另外一个if判断

这里的意思也很明显&#xff0c;当这个消息队列退出的时候&#xff0c;返回空。而且在返回前调用了dispose()方法&#xff0c;显然这意味着该消息队列将被释放。

我们来看剩下的代码&#xff0c;但是这段代码太长&#xff0c;我们来进行筛除

第一个要减的就是pendingIdleHandlerCount&#xff0c;这个局部变量初始为-1&#xff0c;后面被赋值mIdleHandlers.size();。这里的mIdleHandlers初始为new ArrayList()&#xff0c;在addIdleHander()方法中增加元素&#xff0c;在removeIdleHander()方法中移除元素。而我们所用的Handeler并未实现IdleHandler接口&#xff0c;因此在next()方法中pendingIdleHandlerCount的值要么为0&#xff0c;要么为-1&#xff0c;因此可以看出与该变量相关的部分代码运行情况是确定的&#xff0c;好的&#xff0c;把不影响循环控制的代码减掉。

第二个要减的是Binder.flushPendingCommands()这个代码看源码说明&#xff1a;

Flush any Binder commands pending in the current thread to the kernel driver. This can be useful to call before performing an operation that may block for a long time, to ensure that any pending object references have been released in order to prevent the process from holding on to objects longer than it needs to.

这段话啥意不懂也没关系&#xff0c;这里只需要知道&#xff1a;Binder.flushPendingCommands()方法被调用说明后面的代码可能会引起线程阻塞。然后把这段减掉。

第三个要减的是一个log语句if (DEBUG) Log.v(TAG, "Returning message: " &#43; msg);

筛减后的代码

Message next() {

int nextPollTimeoutMillis &#61; 0;

for (;;) {

nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {

// Try to retrieve the next message. Return if found.

final long now &#61; SystemClock.uptimeMillis();

Message prevMsg &#61; null;

Message msg &#61; mMessages;

if (msg !&#61; null && msg.target &#61;&#61; null) {

// Stalled by a barrier. Find the next asynchronous message in the queue.

do {

prevMsg &#61; msg;

msg &#61; msg.next;

} while (msg !&#61; null && !msg.isAsynchronous());

}

if (msg !&#61; null) {

if (now

// Next message is not ready. Set a timeout to wake up when it is ready.

nextPollTimeoutMillis &#61; (int) Math.min(msg.when - now, Integer.MAX_VALUE);

} else {

// Got a message.

mBlocked &#61; false;

if (prevMsg !&#61; null) {

prevMsg.next &#61; msg.next;

} else {

mMessages &#61; msg.next;

}

msg.next &#61; null;

msg.markInUse();

return msg;

}

} else {

// No more messages.

nextPollTimeoutMillis &#61; -1;

}

// Process the quit message now that all pending messages have been handled.

if (mQuitting) {

dispose();

return null;

}

if (pendingIdleHandlerCount <&#61; 0) {//上面分析过该变量要么为0要么为-1

mBlocked &#61; true;

continue;

}

}

nextPollTimeoutMillis &#61; 0;

}

}

先获取第一个同步的message。如果它的when不晚与当前时间&#xff0c;就返回这个message&#xff1b;否则计算当前时间到它的when还有多久并保存到nextPollTimeMills中&#xff0c;然后调用nativePollOnce()来延时唤醒(Linux的epoll&#xff0c;有兴趣的自行百度)&#xff0c;唤醒之后再照上面那样取message&#xff0c;如此循环。代码中对链表的指针操作占了一定篇幅&#xff0c;其他的逻辑很清楚。

那么问题又来了&#xff0c;是谁在调用这个方法呢&#xff0c;那当然是我们还有一个没有讲的Looper洛&#xff0c;我们来看Looper的源码&#xff0c;同样我们只保留主流程的代码&#xff1a;

/**

* Run the message queue in this thread. Be sure to call

* {&#64;link #quit()} to end the loop.

*/

public static void loop() {

final Looper me &#61; myLooper();

if (me &#61;&#61; null) {

throw new RuntimeException("No Looper; Looper.prepare() wasn&#39;t called on this thread.");

}

final MessageQueue queue &#61; me.mQueue;

for (;;) {

Message msg &#61; queue.next(); // might block

if (msg &#61;&#61; null) {

// No message indicates that the message queue is quitting.

return;

}

try {

msg.target.dispatchMessage(msg);

} finally {

if (traceTag !&#61; 0) {

Trace.traceEnd(traceTag);

}

}

msg.recycleUnchecked();

}

}

1.首先判断Looper如果为Null则会抛出异常&#xff0c;为null的情况是在异步线程声明Handler时没有调用Looper.prepare()&#xff0c;当然根本不会出现因为如果在异步线程中没有调用该方法运行时会直接报错

2.一个死循环从消息队列中循环消息&#xff0c;如果出现消息为null的情况说明已经是被回收了的

msg.target.dispatchMessage(msg)&#xff0c;这部很关键我们知道msg的targe是Handler所以这是在调用handler的dispatchMessage方法进行处理&#xff0c;这也是为什么Handler在哪个线程声明中就在哪个线程中处理消息和哪个handler发送消息哪个handler处理的由来&#xff0c;

上面就是消息从发送到处理的流程&#xff0c;通过文字理解起来比较抽象&#xff0c;我们来画个图具体的描述下

实现了Parcelable接口 这是android种用来进行数据传输的&#xff0c;先记住后面的章节会具体的讲

我们平时声明Message对象有两种方法直接new或者Message.obtain()这个方法有以下的几种重写

我们接着看无参构造方法&#xff1a;

/**

* Return a new Message instance from the global pool. Allows us to

* avoid allocating new objects in many cases.

*/

public static Message obtain() {

synchronized (sPoolSync) {

if (sPool !&#61; null) {

Message m &#61; sPool;

sPool &#61; m.next;

m.next &#61; null;

m.flags &#61; 0; // clear in-use flag

sPoolSize--;

return m;

}

}

return new Message();

}

看到里面有一把锁&#xff0c;这个是线程同步&#xff0c;如果当前message不null则把当前的赋值给一个新的引用&#xff0c;sPoolSize是指message池的大小最大为50

var cpro_id = "u6885494";

推荐阅读
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 学习SLAM的女生,很酷
    本文介绍了学习SLAM的女生的故事,她们选择SLAM作为研究方向,面临各种学习挑战,但坚持不懈,最终获得成功。文章鼓励未来想走科研道路的女生勇敢追求自己的梦想,同时提到了一位正在英国攻读硕士学位的女生与SLAM结缘的经历。 ... [详细]
  • 树莓派Linux基础(一):查看文件系统的命令行操作
    本文介绍了在树莓派上通过SSH服务使用命令行查看文件系统的操作,包括cd命令用于变更目录、pwd命令用于显示当前目录位置、ls命令用于显示文件和目录列表。详细讲解了这些命令的使用方法和注意事项。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • Python语法上的区别及注意事项
    本文介绍了Python2x和Python3x在语法上的区别,包括print语句的变化、除法运算结果的不同、raw_input函数的替代、class写法的变化等。同时还介绍了Python脚本的解释程序的指定方法,以及在不同版本的Python中如何执行脚本。对于想要学习Python的人来说,本文提供了一些注意事项和技巧。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • Linux磁盘的分区、格式化的观察和操作步骤
    本文介绍了如何观察Linux磁盘的分区状态,使用lsblk命令列出系统上的所有磁盘列表,并解释了列表中各个字段的含义。同时,还介绍了使用parted命令列出磁盘的分区表类型和分区信息的方法。在进行磁盘分区操作时,根据分区表类型选择使用fdisk或gdisk命令,并提供了具体的分区步骤。通过本文,读者可以了解到Linux磁盘分区和格式化的基本知识和操作步骤。 ... [详细]
  • 本文介绍了Linux系统中正则表达式的基础知识,包括正则表达式的简介、字符分类、普通字符和元字符的区别,以及在学习过程中需要注意的事项。同时提醒读者要注意正则表达式与通配符的区别,并给出了使用正则表达式时的一些建议。本文适合初学者了解Linux系统中的正则表达式,并提供了学习的参考资料。 ... [详细]
  • Ubuntu 9.04中安装谷歌Chromium浏览器及使用体验[图文]
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
author-avatar
Mickey-洁de妈咪_212
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有